1<!doctype html> 2<html class="js full-height" lang="de"> 3 <head> 4 <meta charset="utf-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width,initial-scale=1"> 7 <meta name="theme-color" content=""> 8 <link rel="canonical" href="https://balkonkraftwerk-express.de/password"><link rel="icon" type="image/png" href="//balkonkraftwerk-express.de/cdn/shop/files/bkw_favicon.png?crop=center&height=32&v=1727098671&width=32"><link rel="preconnect" href="https://fonts.shopifycdn.com" crossorigin><title>Balkonkraftwerk-Express</title> 9 10 <meta name="description" content=""> 11 12 13 14<meta property="og:site_name" content="Balkonkraftwerk-Express"> 15<meta property="og:url" content="https://balkonkraftwerk-express.de"> 16<meta property="og:title" content="Balkonkraftwerk-Express"> 17<meta property="og:type" content="website"> 18<meta property="og:description" content="Hochwertige Balkonkraftwerke für zuhause!: Plug-&-Play-Systeme! â Einfach! â Nachhaltige Stromerzeugung â Jetzt kaufen!"><meta property="og:image" content="http://balkonkraftwerk-express.de/cdn/shop/files/BKE-logo-high.jpg?height=628&pad_color=ffffff&v=1691081304&width=1200"> 19 <meta property="og:image:secure_url" content="https://balkonkraftwerk-express.de/cdn/shop/files/BKE-logo-high.jpg?height=628&pad_color=ffffff&v=1691081304&width=1200"> 20 <meta property="og:image:width" content="1200"> 21 <meta property="og:image:height" content="628"><meta name="twitter:card" content="summary_large_image"> 22<meta name="twitter:title" content="Balkonkraftwerk-Express"> 23<meta name="twitter:description" content="Hochwertige Balkonkraftwerke für zuhause!: Plug-&-Play-Systeme! â Einfach! â Nachhaltige Stromerzeugung â Jetzt kaufen!"> 24 25 26
26<script>window.performance && window.performance.mark && window.performance.mark('shopify.content_for_header.start');</script>
26<meta name="google-site-verification" content="Y2kziul2JQywvNKrw2cdP6JXST2V-5oI_Ot_4d0kW5c"> 27<meta id="shopify-digital-wallet" name="shopify-digital-wallet" content="/55579017562/digital_wallets/dialog"> 28<meta id="in-context-paypal-metadata" data-shop-id="55579017562" data-venmo-supported="false" data-environment="production" data-locale="de_DE" data-paypal-v4="true" data-currency="EUR"> 29<link rel="ucp" href="/.well-known/ucp" version="2026-08-25">
29<script async="async" src="/checkouts/internal/preloads.js?locale=de-DE"></script>
29 30<link rel="preconnect" href="https://shop.app" crossorigin="anonymous">
31<script async="async" src="https://shop.app/checkouts/internal/preloads.js?locale=de-DE&shop_id=55579017562" crossorigin="anonymous"></script>
vendor: 1 bytes, line 31
31
32<script id="apple-pay-shop-capabilities" type="application/json">{"shopId":55579017562,"countryCode":"DE","currencyCode":"EUR","merchantCapabilities":["supports3DS"],"merchantId":"gid:\/\/shopify\/Shop\/55579017562","merchantName":"Balkonkraftwerk-Express","requiredBillingContactFields":["postalAddress","email"],"requiredShippingContactFields":["postalAddress","email"],"shippingType":"shipping","supportedNetworks":["visa","masterCard","maestro"],"total":{"type":"pending","label":"Balkonkraftwerk-Express","amount":"1.00"},"shopifyPaymentsEnabled":true,"supportsSubscriptions":true}</script>
vendor: 1 bytes, line 32
32
33<script>var Shopify = Shopify || {}; 34Shopify.shop = "e94e42.myshopify.com"; 35Shopify.locale = "de"; 36Shopify.currency = {"active":"EUR","rate":"1.0"}; 37Shopify.country = "DE"; 38Shopify.theme = {"name":"Theme 10.May 2026","id":193346863450,"schema_name":"Dawn","schema_version":"14.0.0","theme_store_id":887,"role":"main"}; 39Shopify.theme.handle = "null"; 40Shopify.theme.style = {"id":null,"handle":null}; 41Shopify.cdnHost = "balkonkraftwerk-express.de/cdn"; 42Shopify.routes = Shopify.routes || {}; 43Shopify.routes.root = "/"; 44Shopify.shopJsCdnBaseUrl = "https://cdn.shopify.com/shopifycloud/shop-js"; 45Shopify.SignInWithShop = Shopify.SignInWithShop || {}; 46Shopify.SignInWithShop.User = Shopify.SignInWithShop.User || {}; 47Shopify.SignInWithShop.User.recognized = false;</script>
vendor: 1 bytes, line 47
47
48<script type="module">!function(o){(o.Shopify=o.Shopify||{}).modules=!0}(window);</script>
vendor: 1 bytes, line 48
48
49<script>!function(o){function n(){var o=[];function n(){o.push(Array.prototype.slice.apply(arguments))}return n.q=o,n}var t=o.Shopify=o.Shopify||{};t.loadFeatures=n(),t.autoloadFeatures=n()}(window);</script>
vendor: 1 bytes, line 49
49
50<script> 51 window.ShopifyPay = window.ShopifyPay || {}; 52 window.ShopifyPay.apiHost = "shop.app\/pay"; 53 window.ShopifyPay.redirectState = null; 54</script>
vendor: 1 bytes, line 54
54
55<script> 56 window.Shopify = window.Shopify || {}; 57 window.Shopify.SignInWithShop = window.Shopify.SignInWithShop || {}; 58 window.Shopify.SignInWithShop.assetMetrics = { sampleRate: 0.25 }; 59 window.Shopify.SignInWithShop.eligible = true; 60</script>
vendor: 1 bytes, line 60
60
61<script id="shop-js-analytics" type="application/json">{"pageType":"password"}</script>
vendor: 1 bytes, line 61
61
62<script defer="defer" async type="module" src="https://cdn.shopify.com/shopifycloud/shop-js/modules/v2/loader.init-shop-cart-sync.de.esm.js"></script>
vendor: 1 bytes, line 62
62
63<script type="module"> 64 await import("https://cdn.shopify.com/shopifycloud/shop-js/modules/v2/loader.init-shop-cart-sync.de.esm.js"); 65 66 window.Shopify.SignInWithShop?.initShopCartSync?.({"fedCMEnabled":true,"windoidEnabled":true}); 67 68</script>
vendor: 1 bytes, line 68
68
69<script> 70 window.Shopify = window.Shopify || {}; 71 if (!window.Shopify.featureAssets) window.Shopify.featureAssets = {}; 72 window.Shopify.featureAssets['shop-js'] = {"avatar":["modules/v2/loader.avatar.de.esm.js"],"checkout-modal":["modules/v2/loader.checkout-modal.de.esm.js"],"init-customer-accounts-sign-up":["modules/v2/loader.init-customer-accounts-sign-up.de.esm.js"],"init-customer-accounts":["modules/v2/loader.init-customer-accounts.de.esm.js"],"init-fed-cm":["modules/v2/loader.init-fed-cm.de.esm.js"],"init-shop-cart-sync":["modules/v2/loader.init-shop-cart-sync.de.esm.js"],"init-shop-email-lookup-coordinator":["modules/v2/loader.init-shop-email-lookup-coordinator.de.esm.js"],"init-shop-for-new-customer-accounts":["modules/v2/loader.init-shop-for-new-customer-accounts.de.esm.js"],"init-shop-user-recognition":["modules/v2/loader.init-shop-user-recognition.de.esm.js"],"init-windoid":["modules/v2/loader.init-windoid.de.esm.js"],"lead-capture":["modules/v2/loader.lead-capture.de.esm.js"],"listener":["modules/v2/loader.listener.de.esm.js"],"pay-button":["modules/v2/loader.pay-button.de.esm.js"],"payment-terms":["modules/v2/loader.payment-terms.de.esm.js"],"shop-button":["modules/v2/loader.shop-button.de.esm.js"],"shop-cart-sync":["modules/v2/loader.shop-cart-sync.de.esm.js"],"shop-cash-offers":["modules/v2/loader.shop-cash-offers.de.esm.js"],"shop-follow-button":["modules/v2/loader.shop-follow-button.de.esm.js"],"shop-login-button":["modules/v2/loader.shop-login-button.de.esm.js"],"shop-login":["modules/v2/loader.shop-login.de.esm.js"],"shop-toast-manager":["modules/v2/loader.shop-toast-manager.de.esm.js"],"shop-user-recognition":["modules/v2/loader.shop-user-recognition.de.esm.js"]}; 73</script>
vendor: 1 bytes, line 73
73
74<script id="__st">var __st={"a":55579017562,"offset":7200,"reqid":"5823bc87-814a-4b96-beff-79d5c90e35af-1791586216","pageurl":"balkonkraftwerk-express.de\/password","u":"9885d72964f7","p":"password"};</script>
vendor: 1 bytes, line 74
74
75<script>window.ShopifyPaypalV4VisibilityTracking = true;</script>
vendor: 1 bytes, line 75
75
76<script id="captcha-bootstrap">!function(){'use strict';const t='contact',e='account',n='new_comment',o=[[t,t],['blogs',n],['comments',n],[t,'customer']],c=[[e,'customer_login'],[e,'guest_login'],[e,'recover_customer_password'],[e,'create_customer']],r=t=>t.map((([t,e])=>`form[action*='/${t}']:not([data-nocaptcha='true']) input[name='form_type'][value='${e}']`)).join(','),a=t=>()=>t?[...document.querySelectorAll(t)].map((t=>t.form)):[];function s(){const t=[...o],e=r(t);return a(e)}const i='password',u='form_key',d=['recaptcha-v3-token','g-recaptcha-response','h-captcha-response',i],f=()=>{try{return window.sessionStorage}catch{return}},m='__shopify_v',_=t=>t.elements[u];function p(t,e,n=!1){try{const o=window.sessionStorage,c=JSON.parse(o.getItem(e)),{data:r}=function(t){const{data:e,action:n}=t;return t[m]||n?{data:e,action:n}:{data:t,action:n}}(c);for(const[e,n]of Object.entries(r))t.elements[e]&&(t.elements[e].value=n);n&&o.removeItem(e)}catch(o){console.error('form repopulation failed',{error:o})}}const l='form_type',E='cptcha';function T(t){t.dataset[E]=!0}const w=window,h=w.document,L='Shopify',v='ce_forms',y='captcha';let A=!1;((t,e)=>{const n=(g='f06e6c50-85a8-45c8-87d0-21a2b65856fe',I='https://cdn.shopify.com/shopifycloud/storefront-forms-hcaptcha/ce_storefront_forms_captcha_hcaptcha.v1.5.3.iife.js',D={infoText:'Durch hCaptcha geschützt',privacyText:'Datenschutz',termsText:'Allgemeine Geschäftsbedingungen'},(t,e,n)=>{const o=w[L][v],c=o.bindForm;if(c)return c(t,g,e,D).then(n);var r;o.q.push([[t,g,e,D],n]),r=I,A||(h.body.append(Object.assign(h.createElement('script'),{id:'captcha-provider',async:!0,src:r})),A=!0)});var g,I,D;w[L]=w[L]||{},w[L][v]=w[L][v]||{},w[L][v].q=[],w[L][y]=w[L][y]||{},w[L][y].protect=function(t,e){n(t,void 0,e),T(t)},Object.freeze(w[L][y]),function(t,e,n,w,h,L){const[v,y,A,g]=function(t,e,n){const i=e?o:[],u=t?c:[],d=[...i,...u],f=r(d),m=r(i),_=r(d.filter((([t,e])=>n.includes(e))));return[a(f),a(m),a(_),s()]}(w,h,L),I=t=>{const e=t.target;return e instanceof HTMLFormElement?e:e&&e.form},D=t=>v().includes(t);t.addEventListener('submit',(t=>{const e=I(t);if(!e)return;const n=D(e)&&!e.dataset.hcaptchaBound&&!e.dataset.recaptchaBound,o=_(e),c=g().includes(e)&&(!o||!o.value);(n||c)&&t.preventDefault(),c&&!n&&(function(t){try{if(!f())return;!function(t){const e=f();if(!e)return;const n=_(t);if(!n)return;const o=n.value;o&&e.removeItem(o)}(t);const e=Array.from(Array(32),(()=>Math.random().toString(36)[2])).join('');!function(t,e){_(t)||t.append(Object.assign(document.createElement('input'),{type:'hidden',name:u})),t.elements[u].value=e}(t,e),function(t,e){const n=f();if(!n)return;const o=[...t.querySelectorAll(`input[type='${i}']`)].map((({name:t})=>t)),c=[...d,...o],r={};for(const[a,s]of new FormData(t).entries())c.includes(a)||(r[a]=s);n.setItem(e,JSON.stringify({[m]:1,action:t.action,data:r}))}(t,e)}catch(e){console.error('failed to persist form',e)}}(e),e.submit())}));const S=(t,e)=>{t&&!t.dataset[E]&&(n(t,e.some((e=>e===t))),T(t))};for(const o of['focusin','change'])t.addEventListener(o,(t=>{const e=I(t);D(e)&&S(e,y())}));const B=e.get('form_key'),M=e.get(l),P=B&&M;t.addEventListener('DOMContentLoaded',(()=>{const t=y();if(P)for(const e of t)e.elements[l].value===M&&p(e,B);[...new Set([...A(),...v().filter((t=>'true'===t.dataset.shopifyCaptcha))])].forEach((e=>S(e,t)))}))}(h,new URLSearchParams(w.location.search),n,t,e,['guest_login'])})(!0,!0)}();</script>
vendor: 1 bytes, line 76
76
77<script integrity="sha256-JjoPp5ZfB1sSAs5SQaol1x1GgvveM+BgmRzyDexInEQ=" data-source-attribution="shopify.loadfeatures" defer="defer" src="//balkonkraftwerk-express.de/cdn/shopifycloud/storefront/assets/storefront/load_feature-1bd60354.js" crossorigin="anonymous"></script>
vendor: 1 bytes, line 77
77
78<script>(function () {var userAgent = navigator.userAgent;var platform = navigator.platform;var maxTouchPoints = navigator.maxTouchPoints || 0;var isIOS = /iPad|iPhone|iPod/.test(platform) || (platform === 'MacIntel' && maxTouchPoints > 1);var isMacSafari = platform.indexOf('Mac') === 0 && /Safari/.test(userAgent) && !/Chrome|Chromium|CriOS|FxiOS|Edg|OPR|Android/.test(userAgent);var isAppleSafari = isIOS || isMacSafari;if (isAppleSafari) {fetch('/sf_private_access_tokens' + location.search).catch(function () {});}function browserMajorVersion(pattern) {var match = userAgent.match(pattern);return match ? parseInt(match[1], 10) : null;}function shouldLoadAutosizesPolyfill() {if (!window.PerformanceObserver?.supportedEntryTypes?.includes('paint')) {return false;}var chromeVersion = browserMajorVersion(/Chrome\/(\d+)/);if (chromeVersion !== null) {return chromeVersion < 126;}var firefoxVersion = browserMajorVersion(/Firefox\/(\d+)/);if (firefoxVersion !== null) {return firefoxVersion < 150;}var safariVersion = isAppleSafari ? browserMajorVersion(/Version\/(\d+).*Safari\//) : null;if (safariVersion !== null) {return safariVersion < 27;}return true;}if (shouldLoadAutosizesPolyfill()) {var autosizesScript = document.createElement('script');autosizesScript.async = true;autosizesScript.crossOrigin = 'anonymous';autosizesScript.src = "//balkonkraftwerk-express.de/cdn/shopifycloud/storefront/assets/storefront/autosizes-84416378.js";(document.head || document.documentElement).appendChild(autosizesScript);}window.ShopifyAnalytics = window.ShopifyAnalytics || {};window.ShopifyAnalytics.performance = window.ShopifyAnalytics.performance || {};(function () {var LONG_FRAME_THRESHOLD = 50;var longAnimationFrames = [];var activeRafId = null;function collectLongFrames() {var previousTime = null;function rafMonitor(now) {if (activeRafId === null) {return;}var delta = now - previousTime;if (delta > LONG_FRAME_THRESHOLD) {longAnimationFrames.push({startTime: previousTime,endTime: now,});}previousTime = now;activeRafId = requestAnimationFrame(rafMonitor);}previousTime = performance.now();activeRafId = requestAnimationFrame(rafMonitor);}if (!window.PerformanceObserver?.supportedEntryTypes?.includes('long-animation-frame')) {collectLongFrames();var timeoutId = setTimeout(function () {cancelAnimationFrame(activeRafId);}, 10000);window.ShopifyAnalytics.performance.getLongAnimationFrames = function (stopCollection) {if (stopCollection === undefined) {stopCollection = false;}if (stopCollection) {clearTimeout(timeoutId);cancelAnimationFrame(activeRafId);}return longAnimationFrames;};}})();})();</script>
78<script crossorigin="anonymous" defer="defer" src="//balkonkraftwerk-express.de/cdn/shopifycloud/storefront/assets/shopify_pay/storefront-f0e26bdf.js?v=20250812"></script>
vendor: 1 bytes, line 78
78
79<script id="shopify-origin-trials" async="async" integrity="sha256-qJbe/jEZBHtr5W/BlA2cKAz3vkXu6vJQyLjgvgCC86g=" src="//cdn.shopify.com/shopifycloud/storefront/assets/storefront/origin_trials-318ab40a.js" crossorigin="anonymous" onload="window.__shopifyOriginTrialsDone = true" onerror="window.__shopifyOriginTrialsDone = true"></script>
vendor: 1 bytes, line 79
79
80<script data-source-attribution="shopify.dynamic_checkout.buyer_consent"> 81 function portableWalletsHideBuyerConsent(e){var t=document.getElementById("shopify-buyer-consent"),n=document.getElementById("shopify-subscription-policy-button");t&&n&&(t.classList.add("hidden"),t.setAttribute("aria-hidden","true"),n.removeEventListener("click",e))}function portableWalletsShowBuyerConsent(e){var t=document.getElementById("shopify-buyer-consent"),n=document.getElementById("shopify-subscription-policy-button");t&&n&&(t.classList.remove("hidden"),t.removeAttribute("aria-hidden"),n.addEventListener("click",e))}window.Shopify?.PaymentButton&&(window.Shopify.PaymentButton.hideBuyerConsent=portableWalletsHideBuyerConsent,window.Shopify.PaymentButton.showBuyerConsent=portableWalletsShowBuyerConsent); 82</script>
vendor: 1 bytes, line 82
82
83<script data-source-attribution="shopify.dynamic_checkout.cart.bootstrap">document.addEventListener("DOMContentLoaded",(function(){function t(){return document.querySelector("shopify-accelerated-checkout-cart, shopify-accelerated-checkout")}if(t())Shopify.PaymentButton.init();else{new MutationObserver((function(e,n){t()&&(Shopify.PaymentButton.init(),n.disconnect())})).observe(document.body,{childList:!0,subtree:!0})}})); 84</script>
84 85<link id="shopify-accelerated-checkout-styles" rel="stylesheet" media="screen" href="https://balkonkraftwerk-express.de/cdn/shopifycloud/portable-wallets/latest/accelerated-checkout-backwards-compat.css" crossorigin="anonymous"> 86<style id="shopify-accelerated-checkout-cart"> 87 #shopify-buyer-consent { 88 margin-top: 1em; 89 display: inline-block; 90 width: 100%; 91} 92 93#shopify-buyer-consent.hidden { 94 display: none; 95} 96 97#shopify-subscription-policy-button { 98 background: none; 99 border: none; 100 padding: 0; 101 text-decoration: underline; 102 font-size: inherit; 103 cursor: pointer; 104} 105 106#shopify-subscription-policy-button::before { 107 box-shadow: none; 108} 109 110 </style> 111
112<script id="shopify-cfh-end">window.performance && window.performance.mark && window.performance.mark('shopify.content_for_header.end');</script>
112 113 114 115 <style data-shopify> 116 @font-face { 117 font-family: Assistant; 118 font-weight: 400; 119 font-style: normal; 120 font-display: swap; 121 src: url("//balkonkraftwerk-express.de/cdn/fonts/assistant/assistant_n4.9120912a469cad1cc292572851508ca49d12e768.woff2") format("woff2"), 122 url("//balkonkraftwerk-express.de/cdn/fonts/assistant/assistant_n4.6e9875ce64e0fefcd3f4446b7ec9036b3ddd2985.woff") format("woff"); 123} 124 125 @font-face { 126 font-family: Assistant; 127 font-weight: 700; 128 font-style: normal; 129 font-display: swap; 130 src: url("//balkonkraftwerk-express.de/cdn/fonts/assistant/assistant_n7.bf44452348ec8b8efa3aa3068825305886b1c83c.woff2") format("woff2"), 131 url("//balkonkraftwerk-express.de/cdn/fonts/assistant/assistant_n7.0c887fee83f6b3bda822f1150b912c72da0f7b64.woff") format("woff"); 132} 133 134 135 136 @font-face { 137 font-family: Assistant; 138 font-weight: 400; 139 font-style: normal; 140 font-display: swap; 141 src: url("//balkonkraftwerk-express.de/cdn/fonts/assistant/assistant_n4.9120912a469cad1cc292572851508ca49d12e768.woff2") format("woff2"), 142 url("//balkonkraftwerk-express.de/cdn/fonts/assistant/assistant_n4.6e9875ce64e0fefcd3f4446b7ec9036b3ddd2985.woff") format("woff"); 143} 144 145 146 147 :root, 148 .color-background-1 { 149 --color-background: 255,255,255; 150 151 --gradient-background: #ffffff; 152 153 --color-foreground: 18,18,18; 154 --color-shadow: 18,18,18; 155 --color-button: 94,193,31; 156 --color-button-text: 255,255,255; 157 --color-secondary-button: 255,255,255; 158 --color-secondary-button-text: 18,18,18; 159 --color-link: 18,18,18; 160 --color-badge-foreground: 18,18,18; 161 --color-badge-background: 255,255,255; 162 --color-badge-border: 18,18,18; 163 --payment-terms-background-color: rgb(255 255 255); 164 } 165 166 167 .color-background-2 { 168 --color-background: 217,217,217; 169 170 --gradient-background: #d9d9d9; 171 172 --color-foreground: 18,18,18; 173 --color-shadow: 18,18,18; 174 --color-button: 18,18,18; 175 --color-button-text: 243,243,243; 176 --color-secondary-button: 217,217,217; 177 --color-secondary-button-text: 18,18,18; 178 --color-link: 18,18,18; 179 --color-badge-foreground: 18,18,18; 180 --color-badge-background: 217,217,217; 181 --color-badge-border: 18,18,18; 182 --payment-terms-background-color: rgb(217 217 217); 183 } 184 185 186 .color-inverse { 187 --color-background: 0,0,0; 188 189 --gradient-background: #000000; 190 191 --color-foreground: 255,255,255; 192 --color-shadow: 18,18,18; 193 --color-button: 255,255,255; 194 --color-button-text: 18,18,18; 195 --color-secondary-button: 0,0,0; 196 --color-secondary-button-text: 255,255,255; 197 --color-link: 255,255,255; 198 --color-badge-foreground: 255,255,255; 199 --color-badge-background: 0,0,0; 200 --color-badge-border: 255,255,255; 201 --payment-terms-background-color: rgb(0 0 0); 202 } 203 204 205 .color-accent-1 { 206 --color-background: 94,193,31; 207 208 --gradient-background: #5ec11f; 209 210 --color-foreground: 255,255,255; 211 --color-shadow: 18,18,18; 212 --color-button: 255,255,255; 213 --color-button-text: 94,193,31; 214 --color-secondary-button: 94,193,31; 215 --color-secondary-button-text: 255,255,255; 216 --color-link: 255,255,255; 217 --color-badge-foreground: 255,255,255; 218 --color-badge-background: 94,193,31; 219 --color-badge-border: 255,255,255; 220 --payment-terms-background-color: rgb(94 193 31); 221 } 222 223 224 .color-accent-2 { 225 --color-background: 94,193,31; 226 227 --gradient-background: #5ec11f; 228 229 --color-foreground: 255,255,255; 230 --color-shadow: 18,18,18; 231 --color-button: 255,255,255; 232 --color-button-text: 18,18,18; 233 --color-secondary-button: 94,193,31; 234 --color-secondary-button-text: 255,255,255; 235 --color-link: 255,255,255; 236 --color-badge-foreground: 255,255,255; 237 --color-badge-background: 94,193,31; 238 --color-badge-border: 255,255,255; 239 --payment-terms-background-color: rgb(94 193 31); 240 } 241 242 243 .color-scheme-2bbee085-e3ce-49f4-8eba-bd31d571ab49 { 244 --color-background: 18,18,18; 245 246 --gradient-background: #121212; 247 248 --color-foreground: 255,255,255; 249 --color-shadow: 18,18,18; 250 --color-button: 255,255,255; 251 --color-button-text: 255,255,255; 252 --color-secondary-button: 18,18,18; 253 --color-secondary-button-text: 18,18,18; 254 --color-link: 18,18,18; 255 --color-badge-foreground: 255,255,255; 256 --color-badge-background: 18,18,18; 257 --color-badge-border: 255,255,255; 258 --payment-terms-background-color: rgb(18 18 18); 259 } 260 261 262 .color-scheme-720e2e72-967a-4ac1-aa51-633b7d9ff9e3 { 263 --color-background: 185,234,153; 264 265 --gradient-background: #b9ea99; 266 267 --color-foreground: 18,18,18; 268 --color-shadow: 18,18,18; 269 --color-button: 185,234,153; 270 --color-button-text: 185,234,153; 271 --color-secondary-button: 185,234,153; 272 --color-secondary-button-text: 18,18,18; 273 --color-link: 18,18,18; 274 --color-badge-foreground: 18,18,18; 275 --color-badge-background: 185,234,153; 276 --color-badge-border: 18,18,18; 277 --payment-terms-background-color: rgb(185 234 153); 278 } 279 280 281 .color-scheme-a7ae43f3-4401-4c19-abd9-68610e102a83 { 282 --color-background: 255,145,77; 283 284 --gradient-background: #ff914d; 285 286 --color-foreground: 18,18,18; 287 --color-shadow: 255,255,255; 288 --color-button: 255,255,255; 289 --color-button-text: 255,255,255; 290 --color-secondary-button: 255,145,77; 291 --color-secondary-button-text: 255,255,255; 292 --color-link: 255,255,255; 293 --color-badge-foreground: 18,18,18; 294 --color-badge-background: 255,145,77; 295 --color-badge-border: 18,18,18; 296 --payment-terms-background-color: rgb(255 145 77); 297 } 298 299 300 .color-scheme-b3348d90-cedc-48d5-857e-3d0bcd038c27 { 301 --color-background: 8,8,8; 302 303 --gradient-background: #080808; 304 305 --color-foreground: 255,255,255; 306 --color-shadow: 18,18,18; 307 --color-button: 94,193,31; 308 --color-button-text: 255,255,255; 309 --color-secondary-button: 8,8,8; 310 --color-secondary-button-text: 18,18,18; 311 --color-link: 18,18,18; 312 --color-badge-foreground: 255,255,255; 313 --color-badge-background: 8,8,8; 314 --color-badge-border: 255,255,255; 315 --payment-terms-background-color: rgb(8 8 8); 316 } 317 318 319 .color-scheme-e14f7e9d-d6df-4a61-9f92-96cddbace30c { 320 --color-background: 140,82,255; 321 322 --gradient-background: #8c52ff; 323 324 --color-foreground: 255,255,255; 325 --color-shadow: 18,18,18; 326 --color-button: 255,255,255; 327 --color-button-text: 18,18,18; 328 --color-secondary-button: 140,82,255; 329 --color-secondary-button-text: 18,18,18; 330 --color-link: 18,18,18; 331 --color-badge-foreground: 255,255,255; 332 --color-badge-background: 140,82,255; 333 --color-badge-border: 255,255,255; 334 --payment-terms-background-color: rgb(140 82 255); 335 } 336 337 338 body, .color-background-1, .color-background-2, .color-inverse, .color-accent-1, .color-accent-2, .color-scheme-2bbee085-e3ce-49f4-8eba-bd31d571ab49, .color-scheme-720e2e72-967a-4ac1-aa51-633b7d9ff9e3, .color-scheme-a7ae43f3-4401-4c19-abd9-68610e102a83, .color-scheme-b3348d90-cedc-48d5-857e-3d0bcd038c27, .color-scheme-e14f7e9d-d6df-4a61-9f92-96cddbace30c { 339 color: rgba(var(--color-foreground), 0.75); 340 background-color: rgb(var(--color-background)); 341 } 342 343 :root { 344 --font-body-family: Assistant, sans-serif; 345 --font-body-style: normal; 346 --font-body-weight: 400; 347 348 --font-heading-family: Assistant, sans-serif; 349 --font-heading-style: normal; 350 --font-heading-weight: 400; 351 352 --font-body-scale: 1.0; 353 --font-heading-scale: 1.0; 354 --media-padding: px; 355 --media-border-opacity: 0.05; 356 --media-border-width: 1px; 357 --media-radius: 0px;
358 --media-shadow-opacity: 0.0; 359 --media-shadow-horizontal-offset: 0px; 360 --media-shadow-vertical-offset: 4px; 361 --media-shadow-blur-radius: 5px; 362 --media-shadow-visible: 0; 363 364 --page-width: 120rem; 365 --page-width-margin: 0rem; 366 367 --product-card-image-padding: 0.0rem; 368 --product-card-corner-radius: 0.0rem; 369 --product-card-text-alignment: left; 370 --product-card-border-width: 0.0rem; 371 --product-card-border-opacity: 0.1; 372 --product-card-shadow-opacity: 0.0; 373 --product-card-shadow-visible: 0; 374 --product-card-shadow-horizontal-offset: 0.0rem; 375 --product-card-shadow-vertical-offset: 0.4rem; 376 --product-card-shadow-blur-radius: 0.5rem; 377 378 --collection-card-image-padding: 0.0rem; 379 --collection-card-corner-radius: 0.0rem; 380 --collection-card-text-alignment: left; 381 --collection-card-border-width: 0.0rem; 382 --collection-card-border-opacity: 0.1; 383 --collection-card-shadow-opacity: 0.0; 384 --collection-card-shadow-visible: 0; 385 --collection-card-shadow-horizontal-offset: 0.0rem; 386 --collection-card-shadow-vertical-offset: 0.4rem; 387 --collection-card-shadow-blur-radius: 0.5rem; 388 389 --blog-card-image-padding: 0.0rem; 390 --blog-card-corner-radius: 0.0rem; 391 --blog-card-text-alignment: left; 392 --blog-card-border-width: 0.0rem; 393 --blog-card-border-opacity: 0.1; 394 --blog-card-shadow-opacity: 0.0; 395 --blog-card-shadow-visible: 0; 396 --blog-card-shadow-horizontal-offset: 0.0rem; 397 --blog-card-shadow-vertical-offset: 0.4rem; 398 --blog-card-shadow-blur-radius: 0.5rem; 399 400 --badge-corner-radius: 4.0rem; 401 402 --spacing-sections-desktop: 0px; 403 --spacing-sections-mobile: 0px; 404 405 --grid-desktop-vertical-spacing: 8px; 406 --grid-desktop-horizontal-spacing: 8px; 407 --grid-mobile-vertical-spacing: 4px; 408 --grid-mobile-horizontal-spacing: 4px; 409 410 --text-boxes-border-opacity: 0.1; 411 --text-boxes-border-width: 0px; 412 --text-boxes-radius: 0px; 413 --text-boxes-shadow-opacity: 0.0; 414 --text-boxes-shadow-visible: 0; 415 --text-boxes-shadow-horizontal-offset: 0px; 416 --text-boxes-shadow-vertical-offset: 4px; 417 --text-boxes-shadow-blur-radius: 5px; 418 419 --buttons-radius: 0px; 420 --buttons-radius-outset: 0px; 421 --buttons-border-width: 0px; 422 --buttons-border-opacity: 1.0; 423 --buttons-shadow-opacity: 0.0; 424 --buttons-shadow-visible: 0; 425 --buttons-shadow-horizontal-offset: 0px; 426 --buttons-shadow-vertical-offset: 4px; 427 --buttons-shadow-blur-radius: 5px; 428 --buttons-border-offset: 0px; 429 430 --inputs-radius: 0px; 431 --inputs-border-width: 1px; 432 --inputs-border-opacity: 0.55; 433 --inputs-shadow-opacity: 0.0; 434 --inputs-shadow-horizontal-offset: 0px; 435 --inputs-margin-offset: 0px; 436 --inputs-shadow-vertical-offset: 4px; 437 --inputs-shadow-blur-radius: 5px; 438 --inputs-radius-outset: 0px; 439 440 --variant-pills-radius: 40px; 441 --variant-pills-border-width: 1px; 442 --variant-pills-border-opacity: 0.55; 443 --variant-pills-shadow-opacity: 0.0; 444 --variant-pills-shadow-horizontal-offset: 0px; 445 --variant-pills-shadow-vertical-offset: 4px; 446 --variant-pills-shadow-blur-radius: 5px; 447 } 448 </style> 449 450 <link rel="preload" as="font" href="//balkonkraftwerk-express.de/cdn/fonts/assistant/assistant_n4.9120912a469cad1cc292572851508ca49d12e768.woff2" type="font/woff2" crossorigin> 451 452 453 <link rel="preload" as="font" href="//balkonkraftwerk-express.de/cdn/fonts/assistant/assistant_n4.9120912a469cad1cc292572851508ca49d12e768.woff2" type="font/woff2" crossorigin> 454 455<link href="//balkonkraftwerk-express.de/cdn/shop/t/40/assets/section-password.css?v=33879197286244898171778255914" rel="stylesheet" type="text/css" media="all" /> 456 <link href="//balkonkraftwerk-express.de/cdn/shop/t/40/assets/base.css?v=16534392784228263581778255914" rel="stylesheet" type="text/css" media="all" /> 457 <link href="//balkonkraftwerk-express.de/cdn/shop/t/40/assets/component-list-social.css?v=35792976012981934991778255914" rel="stylesheet" type="text/css" media="all" /> 458 459
459<script src="//balkonkraftwerk-express.de/cdn/shop/t/40/assets/global.js?v=136628361274817707361778255914" defer="defer"></script>
459 460
460<script src="//balkonkraftwerk-express.de/cdn/shop/t/40/assets/details-modal.js?v=25581673532751508451778255914" defer="defer"></script>
460 461
461<script src="//balkonkraftwerk-express.de/cdn/shop/t/40/assets/password-modal.js?v=79398572782218654001778255914" defer="defer"></script>
461 462 <!-- BEGIN app block: shopify://apps/klaviyo-email-marketing-sms/blocks/klaviyo-onsite-embed/2632fe16-c075-4321-a88b-50b567f42507 --> 463 464 465 466 467 468 469 470 471 472 473 474 475
475<script async src="https://static.klaviyo.com/onsite/js/UnxcSC/klaviyo.js?company_id=UnxcSC"></script>
475 476
476<script>!function(){if(!window.klaviyo){window._klOnsite=window._klOnsite||[];try{window.klaviyo=new Proxy({},{get:function(n,i){return"push"===i?function(){var n;(n=window._klOnsite).push.apply(n,arguments)}:function(){for(var n=arguments.length,o=new Array(n),w=0;w<n;w++)o[w]=arguments[w];var t="function"==typeof o[o.length-1]?o.pop():void 0,e=new Promise((function(n){window._klOnsite.push([i].concat(o,[function(i){t&&t(i),n(i)}]))}));return e}}})}catch(n){window.klaviyo=window.klaviyo||[],window.klaviyo.push=function(){var n;(n=window._klOnsite).push.apply(n,arguments)}}}}();</script>
476 477 478 479 480 481 482 483
483<script> 484 window.klaviyoReviewsProductDesignMode = false 485 </script>
485 486 487 488 489 490 491 492 493 494 495<!-- END app block --><link href="https://monorail-edge.shopifysvc.com" rel="dns-prefetch">
496<script>(function(){if ("sendBeacon" in navigator && "performance" in window) {try {var session_token_from_headers = performance.getEntriesByType('navigation')[0].serverTiming.find(x => x.name == '_s').description;} catch {var session_token_from_headers = undefined;}var session_cookie_matches = document.cookie.match(/_shopify_s=([^;]*)/);var session_token_from_cookie = session_cookie_matches && session_cookie_matches.length === 2 ? session_cookie_matches[1] : "";var session_token = session_token_from_headers || session_token_from_cookie || "";function handle_abandonment_event(e) {var entries = performance.getEntries().filter(function(entry) {return /monorail-edge.shopifysvc.com/.test(entry.name);});if (!window.abandonment_tracked && entries.length === 0) {window.abandonment_tracked = true;var currentMs = Date.now();var navigation_start = performance.timing.navigationStart;var payload = {shop_id: 55579017562,url: window.location.href,navigation_start,duration: currentMs - navigation_start,session_token,page_type: "password"};window.navigator.sendBeacon("https://monorail-edge.shopifysvc.com/v1/produce", JSON.stringify({schema_id: "online_store_buyer_site_abandonment/1.1",payload: payload,metadata: {event_created_at_ms: currentMs,event_sent_at_ms: currentMs}}));}}window.addEventListener('pagehide', handle_abandonment_event);}}());</script>
vendor: 1 bytes, line 496
496
497<script> 498 window.__TREKKIE_SHIM_QUEUE = window.__TREKKIE_SHIM_QUEUE || []; 499</script>
vendor: 1 bytes, line 499
499
500<script>(function(){var wpmLoader=function(){"use strict";var e=/Googlebot|Storebot-Google|bingbot|Baiduspider|YandexBot|DuckDuckBot|Slurp|facebookexternalhit|Twitterbot|LinkedInBot|Applebot|AdsBot-Google|Mediapartners-Google|APIs-Google|PetalBot|SemrushBot|AhrefsBot|MJ12bot|DotBot|Acunetix|PerplexityBot|Perplexity-User/i,t=/bytedance/i;function i(){try{var e=document.cookie;if(!e||"string"!=typeof e)return;for(var t=0,i=e.split(";");t<i.length;t++){var n=i[t],r=n.indexOf("=");if(-1!==r){var o=n.slice(0,r).trim();if(o){var a=void 0;try{a=decodeURIComponent(o)}catch(e){a=o}if("_shopify_s"===a){var d=n.slice(r+1).trim();try{return decodeURIComponent(d)}catch(e){return d}}}}}return}catch(e){return}}function n(e){try{"undefined"!=typeof console&&"function"==typeof console.warn&&console.warn(e)}catch(e){}}function r(e,t){return"string"==typeof e&&e.length>0&&e.length<=t?e:void 0}function o(e){var t;switch(null==(t=null==e?void 0:e.navigation)?void 0:t.type){case 0:return"navigate";case 1:return"reload";case 2:return"back_forward";default:return}}function a(){var e,t,i;try{var n=null==(i=null==(t=null==(e=self.ShopifyAnalytics)?void 0:e.lib)?void 0:t.trekkie)?void 0:i.state;return"awaiting-consent"===n||"initialized"===n?n:void 0}catch(e){return}}return function(d,s,u,l){var c=arguments.length>4&&void 0!==arguments[4]?arguments[4]:{};try{var p=c.trekkieShim;!0!==p&&"true"!==p||null!=window.__TREKKIE_SHIM_QUEUE||(window.__TREKKIE_SHIM_QUEUE=[])}catch(e){}var f,v,h,y,g=(v=(f={modern:/Edge?\/(1{2}[4-9]|1[2-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Firefox\/(1{2}[4-9]|1[2-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Chrom(ium|e)\/(9{2}|\d{3,})\.\d+(\.\d+|)|(Maci|X1{2}).+ Version\/(15\.\d+|(1[6-9]|[2-9]\d|\d{3,})\.\d+)([,.]\d+|)( \(\w+\)|)( Mobile\/\w+|) Safari\/|Chrome.+OPR\/(9{2}|\d{3,})\.\d+\.\d+|(CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone|CPU IPhone OS|CPU iPad OS)[ +]+(15[._]\d+|(1[6-9]|[2-9]\d|\d{3,})[._]\d+)([._]\d+|)|Android:?[ /-](14[89]|1[5-9]\d|[2-9]\d{2}|\d{4,})(\.\d+|)(\.\d+|)|Android.+Firefox\/(15\d|1[6-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Android.+Chrom(ium|e)\/(14[89]|1[5-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|SamsungBrowser\/([2-9]\d|\d{3,})\.\d+/,legacy:/Edge?\/(1[6-9]|[2-9]\d|\d{3,})\.\d+(\.\d+|)|Firefox\/(5[4-9]|[6-9]\d|\d{3,})\.\d+(\.\d+|)|Chrom(ium|e)\/(5[1-9]|[6-9]\d|\d{3,})\.\d+(\.\d+|)([\d.]+$|.*Safari\/(?![\d.]+ Edge\/[\d.]+$))|(Maci|X1{2}).+ Version\/(10\.\d+|(1[1-9]|[2-9]\d|\d{3,})\.\d+)([,.]\d+|)( \(\w+\)|)( Mobile\/\w+|) Safari\/|Chrome.+OPR\/(3[89]|[4-9]\d|\d{3,})\.\d+\.\d+|(CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone|CPU IPhone OS|CPU iPad OS)[ +]+(10[._]\d+|(1[1-9]|[2-9]\d|\d{3,})[._]\d+)([._]\d+|)|Android:?[ /-](14[89]|1[5-9]\d|[2-9]\d{2}|\d{4,})(\.\d+|)(\.\d+|)|Mobile Safari.+OPR\/([89]\d|\d{3,})\.\d+\.\d+|Android.+Firefox\/(15\d|1[6-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Android.+Chrom(ium|e)\/(14[89]|1[5-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Android.+(UC? ?Browser|UCWEB|U3)[ /]?(15\.([5-9]|\d{2,})|(1[6-9]|[2-9]\d|\d{3,})\.\d+)\.\d+|SamsungBrowser\/(5\.\d+|([6-9]|\d{2,})\.\d+)|Android.+MQ{2}Browser\/(14(\.(9|\d{2,})|)|(1[5-9]|[2-9]\d|\d{3,})(\.\d+|))(\.\d+|)|K[Aa][Ii]OS\/(3\.\d+|([4-9]|\d{2,})\.\d+)(\.\d+|)/}).modern,h=f.legacy,(y=navigator.userAgent).match(e)?"bot":y.match(v)?"modern":y.match(h)?"legacy":y.match(t)?"bot":"unknown"),m=function(e){var t,i,n=r(e,128);try{var a=window.performance,d=null==(t=null==a?void 0:a.getEntriesByType)?void 0:t.call(a,"navigation")[0];if(!d)return{requestId:n,navigationType:o(a)};var s="type"in d?r(d.type,64):void 0,u="serverTiming"in d&&Array.isArray(d.serverTiming)?d.serverTiming:[];return{requestId:null!=n?n:r(null==(i=u.find(function(e){return"requestID"===e.name}))?void 0:i.description,128),navigationType:s}}catch(e){return{requestId:n}}}(c.requestId),w=function(e){var t=e.version,r=e.browserTarget,o=e.surface,a=e.shopId,d=e.monorailEndpoint,s=window.location.href;return{emit:function(e){var u=e.status,l=e.errorMsg,c=e.failureReason,p=e.useKeepaliveFallback,f=void 0!==p&&p,v=e.navigationType,h=e.pagehidePersisted,y=e.requestId,g=e.trekkieState;if(d){var m,w;try{var b=(new Date).getTime();m=JSON.stringify({metadata:{event_sent_at_ms:b},events:[{schema_id:"web_pixels_manager_load/3.3",payload:{version:t,bundle_target:r,page_url:s,status:u,surface:o,error_msg:l,failure_reason:c,navigation_type:v,pagehide_persisted:h,request_id:y,trekkie_state:g,shop_id:a,visit_token:i()},metadata:{event_created_at_ms:b}}]})}catch(e){return}try{if("function"==typeof window.navigator.sendBeacon&&-1===(w=window.navigator.userAgent).indexOf("iPhone; CPU iPhone OS 12_")&&-1===w.indexOf("iPad; CPU OS 12_")&&-1===w.indexOf("iPod touch; CPU iPhone OS 12_")&&window.navigator.sendBeacon.bind(window.navigator)(d,m))return}catch(e){}if(f)try{"function"==typeof window.fetch&&window.fetch(d,{method:"POST",headers:{"Content-Type":"text/plain"},body:m,keepalive:!0}
500).catch(function(){})}catch(e){}else try{var _=new XMLHttpRequest;_.open("POST",d,!0),_.setRequestHeader("Content-Type","text/plain"),_.send(m)}catch(e){n("[Web Pixels Manager] Got an unhandled error while logging to Monorail.")}}else n("[Web Pixels Manager] No Monorail endpoint provided, skipping logging.")}}}({version:u,browserTarget:g,surface:d.surface,shopId:d.shopId,monorailEndpoint:d.monorailEndpoint});if(Boolean(null==(_=null==(b=window.Shopify)?void 0:b.analytics)?void 0:_.replayQueue))w.emit({status:"setup-skipped",errorMsg:"replay queue already initialized."});else{var b,_;w.emit({status:"setup-started"}),window.Shopify=window.Shopify||{};var S=window.Shopify;S.analytics=S.analytics||{};var P=S.analytics;P.replayQueue=[],P.publish=function(e,t,i){return P.replayQueue.push([e,t,i]),!0};try{self.performance.mark("wpm:start")}catch(e){}var k,C="modern"===g?"modern":"legacy",E=(null!=l?l:{modern:"",legacy:""})[C],I=[(k={baseUrl:s,hashVersion:u,buildTarget:C}).baseUrl,"/wpm","/b",k.hashVersion,"modern"===k.buildTarget?"m":"l",".js"].join(""),O=!1,T=!1;try{c.browserTarget=g,function(){O=!0;try{window.addEventListener("pagehide",M)}catch(e){O=!1}}(),R(function(){try{R(x)}catch(e){D(e)}}),w.emit({status:"loading",requestId:m.requestId,navigationType:m.navigationType,trekkieState:a()})}catch(e){D(e)}}function U(){var e;if(q(),!function(){var e,t;return Boolean(null==(t=null==(e=window.Shopify)?void 0:e.analytics)?void 0:t.initialized)}()){var t,i,n;try{if("function"!=typeof(null==(e=window.webPixelsManager)?void 0:e.init))return void A();if(!(null!==(n=i=t=window.webPixelsManager.init(d))&&"function"!=typeof n&&Object(n)===n&&"publish"in i&&"function"==typeof i.publish&&"publishCustomEvent"in i&&"function"==typeof i.publishCustomEvent&&"visitor"in i&&"function"==typeof i.visitor))return void A()}catch(e){return void A(e)}var r=window.Shopify.analytics;r.replayQueue.forEach(function(e){var i=e[0],n=e[1],r=e[2];t.publishCustomEvent(i,n,r)}),r.replayQueue=[],r.publish=t.publishCustomEvent,r.visitor=t.visitor,r.initialized=!0}}function A(e){B("manager_api_unavailable",void 0===e?void 0:Q(e))}function x(){B("script_load_failed","".concat(I," has failed to load"))}function B(e,t){T||(T=!0,q(),w.emit({status:"failed",errorMsg:t,failureReason:e,requestId:m.requestId,navigationType:m.navigationType,trekkieState:a()}))}function M(e){O&&(q(),w.emit({status:"pagehide-while-loading",useKeepaliveFallback:!0,pagehidePersisted:e.persisted,requestId:m.requestId,navigationType:m.navigationType,trekkieState:a()}))}function q(){O=!1;try{window.removeEventListener("pagehide",M)}catch(e){}}function R(e){var t;!function(e){var t=e.src,i=e.async,n=void 0===i||i,r=e.onload,o=e.onerror,a=e.sri,d=e.scriptDataAttributes,s=void 0===d?{}:d,u=document.createElement("script"),l=document.querySelector("head"),c=document.querySelector("body");if(u.async=n,u.src=t,a&&(u.integrity=a,u.crossOrigin="anonymous"),s)for(var p in s)if(Object.prototype.hasOwnProperty.call(s,p))try{u.dataset[p]=String(s[p])}catch(e){}if(r&&u.addEventListener("load",r),o&&u.addEventListener("error",o),l)l.appendChild(u);else{if(!c)throw new Error("Did not find a head or body element to append the script");c.appendChild(u)}}({src:I,async:!0,onload:U,onerror:e,sri:(t=E,"string"==typeof t&&/^sha384-[A-Za-z0-9+/=]+$/.test(t)?E:""),scriptDataAttributes:c})}function D(e){B("script_append_failed",Q(e))}function Q(e){return e instanceof Error?e.message:"Unknown error"}}}();wpmLoader({shopId: 55579017562,storefrontBaseUrl: "https://balkonkraftwerk-express.de",extensionsBaseUrl: "https://extensions.shopifycdn.com/cdn/shopifycloud/web-pixels-manager",monorailEndpoint: "https://balkonkraftwerk-express.de/.well-known/shopify/monorail/unstable/produce_batch",surface: "storefront-renderer",enabledBetaFlags: ["4c64608a","16072cab"],webPixelsConfigList: [{"id":"957907290","configuration":"{\"config\":\"{\\\"google_tag_ids\\\":[\\\"G-1685M1KE7B\\\",\\\"GT-MRQK5B5K\\\"],\\\"gtag_events\\\":[{\\\"type\\\":\\\"begin_checkout\\\",\\\"action_label\\\":[\\\"G-1685M1KE7B\\\",\\\"AW-11136337290\\\/1un6CNGpk-0aEIqLnL4p\\\"]},{\\\"type\\\":\\\"search\\\",\\\"action_label\\\":[\\\"G-1685M1KE
5007B\\\",\\\"AW-11136337290\\\/bCeXCMupk-0aEIqLnL4p\\\"]},{\\\"type\\\":\\\"remove_from_cart\\\",\\\"action_label\\\":\\\"G-1685M1KE7B\\\"},{\\\"type\\\":\\\"view_item\\\",\\\"action_label\\\":[\\\"G-1685M1KE7B\\\",\\\"AW-11136337290\\\/vA6uCMipk-0aEIqLnL4p\\\",\\\"MC-3MGF44JG35\\\"]},{\\\"type\\\":\\\"add_shipping_info\\\",\\\"action_label\\\":\\\"G-1685M1KE7B\\\"},{\\\"type\\\":\\\"purchase\\\",\\\"action_label\\\":[\\\"G-1685M1KE7B\\\",\\\"AW-11136337290\\\/bi5VCMqok-0aEIqLnL4p\\\",\\\"MC-3MGF44JG35\\\",\\\"AW-11136337290\\\/cJx4CLzr0a4ZEIqLnL4p\\\"]},{\\\"type\\\":\\\"page_view\\\",\\\"action_label\\\":[\\\"G-1685M1KE7B\\\",\\\"AW-11136337290\\\/KY-_CM2ok-0aEIqLnL4p\\\",\\\"MC-3MGF44JG35\\\"]},{\\\"type\\\":\\\"view_item_list\\\",\\\"action_label\\\":\\\"G-1685M1KE7B\\\"},{\\\"type\\\":\\\"add_payment_info\\\",\\\"action_label\\\":[\\\"G-1685M1KE7B\\\",\\\"AW-11136337290\\\/tVS0CPKpk-0aEIqLnL4p\\\"]},{\\\"type\\\":\\\"add_to_cart\\\",\\\"action_label\\\":[\\\"G-1685M1KE7B\\\",\\\"AW-11136337290\\\/q43dCM6pk-0aEIqLnL4p\\\"]},{\\\"type\\\":\\\"view_cart\\\",\\\"action_label\\\":\\\"G-1685M1KE7B\\\"}],\\\"enable_monitoring_mode\\\":false}\"}","eventPayloadVersion":"v1","runtimeContext":"OPEN","scriptVersion":"a3321ca85cf5aaf6b57585fcd8d67b3a","type":"APP","apiClientId":1780363,"privacyPurposes":[],"dataSharingAdjustments":{"protectedCustomerApprovalScopes":["read_customer_address","read_customer_email","read_customer_name","read_customer_personal_data","read_customer_phone"],"dataSharingControls":["share_all_events"]},"dataSharingState":"unrestricted","enabledFlags":["9a3ed68a"]},{"id":"345506138","configuration":"{\"pixel_id\":\"1440502810142375\",\"pixel_type\":\"facebook_pixel\",\"metaapp_system_user_token\":\"-\"}","eventPayloadVersion":"v1","runtimeContext":"OPEN","scriptVersion":"bd2f978c17a94bbb8a74d43de2165dc5","type":"APP","apiClientId":2329312,"privacyPurposes":["ANALYTICS","MARKETING","SALE_OF_DATA"],"dataSharingAdjustments":{"protectedCustomerApprovalScopes":["read_customer_address","read_customer_email","read_customer_name","read_customer_personal_data","read_customer_phone"],"dataSharingControls":["share_all_events"]},"dataSharingState":"unrestricted","enabledFlags":["9a3ed68a"]},{"id":"284492122","eventPayloadVersion":"1","runtimeContext":"LAX","scriptVersion":"1","type":"CUSTOM","privacyPurposes":[],"name":"Klar!"},{"id":"shopify-app-pixel","configuration":"{}","eventPayloadVersion":"v1","runtimeContext":"STRICT","scriptVersion":"0530","apiClientId":"shopify-pixel","type":"APP","privacyPurposes":["ANALYTICS","MARKETING"]},{"id":"shopify-custom-pixel","eventPayloadVersion":"v1","runtimeContext":"LAX","scriptVersion":"0530","apiClientId":"shopify-pixel","type":"CUSTOM","privacyPurposes":["ANALYTICS","MARKETING"]}],isMerchantRequest: false,initData: {"shop":{"name":"Balkonkraftwerk-Express","paymentSettings":{"currencyCode":"EUR"},"myshopifyDomain":"e94e42.myshopify.com","countryCode":"DE","storefrontUrl":"https:\/\/balkonkraftwerk-express.de"},"customer":null,"cart":null,"checkout":null,"productVariants":[],"products":[],"purchasingCompany":null},},"https://balkonkraftwerk-express.de/cdn","7ec887c8w14f9f074p854094e1m1370c602",{"modern":"","legacy":""},{"trekkieShim":true,"agentContext":true,"apiClientId":"580111","facebookCapiEnabled":"true","themeId":"193346863450","themeStoreId":"887","themePublished":"true","eventMetadataId":"9291e214-3418-4053-b183-32f60aef3746","pageType":"password","shopId":"55579017562","storefrontBaseUrl":"https:\/\/balkonkraftwerk-express.de","extensionBaseUrl":"https:\/\/extensions.shopifycdn.com\/cdn\/shopifycloud\/web-pixels-manager","surface":"storefront-renderer","enabledBetaFlags":"[\"4c64608a\", \"16072cab\"]","isMerchantRequest":"false","hashVersion":"7ec887c8w14f9f074p854094e1m1370c602","publish":"custom","events":"[[\"page_viewed\",{}]]"});})();</script>
500<script> 501 window.ShopifyAnalytics = window.ShopifyAnalytics || {}; 502 window.ShopifyAnalytics.meta = window.ShopifyAnalytics.meta || {}; 503 window.ShopifyAnalytics.meta.currency = 'EUR'; 504 var meta = {"page":{"pageType":"password","requestId":"5823bc87-814a-4b96-beff-79d5c90e35af-1791586216"}}; 505 for (var attr in meta) { 506 window.ShopifyAnalytics.meta[attr] = meta[attr]; 507 } 508</script>
vendor: 1 bytes, line 508
508
509<script class="analytics"> 510 (function () { 511 var customDocumentWrite = function(content) { 512 var jquery = null; 513 514 if (window.jQuery) { 515 jquery = window.jQuery; 516 } else if (window.Checkout && window.Checkout.$) { 517 jquery = window.Checkout.$; 518 } 519 520 if (jquery) { 521 jquery('body').append(content); 522 } 523 }; 524 525 var hasLoggedConversion = function(token) { 526 if (token) { 527 return document.cookie.indexOf('loggedConversion=' + token) !== -1; 528 } 529 return false; 530 } 531 532 var setCookieIfConversion = function(token) { 533 if (token) { 534 var twoMonthsFromNow = new Date(Date.now()); 535 twoMonthsFromNow.setMonth(twoMonthsFromNow.getMonth() + 2); 536 537 document.cookie = 'loggedConversion=' + token + '; expires=' + twoMonthsFromNow; 538 } 539 } 540 541 var trekkie = window.ShopifyAnalytics.lib = window.trekkie = window.trekkie || []; 542 window.ShopifyAnalytics.lib.trekkie = window.trekkie; 543 if (trekkie.integrations) { 544 return; 545 } 546 trekkie.methods = [ 547 'identify', 548 'page', 549 'ready', 550 'track', 551 'trackForm', 552 'trackLink' 553 ]; 554 trekkie.factory = function(method) { 555 return function() { 556 var args = Array.prototype.slice.call(arguments); 557 args.unshift(method); 558 trekkie.push(args); 559 if (method == 'track' || method == 'page') { 560 var pageUrl; 561 try { 562 pageUrl = window.location.href; 563 } catch (e) {} 564 var pageReferrer; 565 try { 566 pageReferrer = document.referrer; 567 } catch (e) {} 568 try { 569 if (window.__TREKKIE_SHIM_QUEUE == null) { 570 window.__TREKKIE_SHIM_QUEUE = []; 571 } 572 window.__TREKKIE_SHIM_QUEUE.push({ 573 from: 'trekkie-stub', 574 method: method, 575 args: args.slice(1), 576 pageContext: { 577 url: pageUrl, 578 referrer: pageReferrer 579 } 580 }); 581 } catch (e) { 582 // no-op 583 } 584 } 585 return trekkie; 586 }; 587 }; 588 for (var i = 0; i < trekkie.methods.length; i++) { 589 var key = trekkie.methods[i]; 590 trekkie[key] = trekkie.factory(key); 591 } 592 trekkie.load = function(config) { 593 trekkie.config = config || {}; 594 trekkie.config.initialDocumentCookie = document.cookie; 595 var first = document.getElementsByTagName('script')[0]; 596var script = document.createElement('script'); 597script.type = 'text/javascript'; 598script.onerror = function(e) { 599 var scriptFallback = document.createElement('script'); 600 scriptFallback.type = 'text/javascript'; 601 scriptFallback.onerror = function(error) { 602 var Monorail = { 603 produce: function produce(monorailDomain, schemaId, payload) { 604 var currentMs = new Date().getTime(); 605 var event = { 606 schema_id: schemaId, 607 payload: payload, 608 metadata: { 609 event_created_at_ms: currentMs, 610 event_sent_at_ms: currentMs 611 } 612 }; 613 return Monorail.sendRequest("https://" + monorailDomain + "/v1/produce", JSON.stringify(event)); 614 }, 615 sendRequest: function sendRequest(endpointUrl, payload) { 616 // Try the sendBeacon API 617 if (window && window.navigator && typeof window.navigator.sendBeacon === 'function' && typeof window.Blob === 'function' && !Monorail.isIos12()) { 618 var blobData = new window.Blob([payload], { 619 type: 'text/plain' 620 }); 621 622 if (window.navigator.sendBeacon(endpointUrl, blobData)) { 623 return true; 624 } // sendBeacon was not successful 625 626 } // XHR beacon 627 628 var xhr = new XMLHttpRequest(); 629 630 try { 631 xhr.open('POST', endpointUrl); 632 xhr.setRequestHeader('Content-Type', 'text/plain'); 633 xhr.send(payload); 634 } catch (e) { 635 console.log(e); 636 } 637 638 return false; 639 }, 640 isIos12: function isIos12() { 641 return window.navigator.userAgent.lastIndexOf('iPhone; CPU iPhone OS 12_') !== -1 || window.navigator.userAgent.lastIndexOf('iPad; CPU OS 12_') !== -1; 642 } 643 }; 644 Monorail.produce('monorail-edge.shopifysvc.com', 645 'trekkie_storefront_load_errors/1.1', 646 {shop_id: 55579017562, 647 theme_id: 193346863450, 648 app_name: "storefront", 649 context_url: window.location.href, 650 source_url: "//balkonkraftwerk-express.de/cdn/s/trekkie.storefront.51e69540e15035ae012ec8cc42e8f9cd792233a0.min.js"}); 651 652 }; 653 scriptFallback.async = true; 654 scriptFallback.src = '//balkonkraftwerk-express.de/cdn/s/trekkie.storefront.51e69540e15035ae012ec8cc42e8f9cd792233a0.min.js'; 655 first.parentNode.insertBefore(scriptFallback, first); 656}; 657script.async = true; 658script.src = '//balkonkraftwerk-express.de/cdn/s/trekkie.storefront.51e69540e15035ae012ec8cc42e8f9cd792233a0.min.js'; 659first.parentNode.insertBefore(script, first); 660 661 }; 662 trekkie.load( 663 {"Trekkie":{"appName":"storefront","development":false,"defaultAttributes":{"shopId":55579017562,"isMerchantRequest":null,"themeId":193346863450,"themeCityHash":"2785675698912787895","contentLanguage":"de","currency":"EUR","eventMetadataId":"9291e214-3418-4053-b183-32f60aef3746"},"isServerSideCookieWritingEnabled":true,"monorailRegion":"shop_domain","enabledBetaFlags":["764d78cb","4c64608a"]},"Session Attribution":{},"S2S":{"facebookCapiEnabled":true,"source":"trekkie-storefront-renderer","apiClientId":580111}} 664 ); 665 666 var loaded = false;
667 trekkie.ready(function() { 668 if (loaded) return; 669 loaded = true; 670 671 window.ShopifyAnalytics.lib = window.trekkie; 672 673 var originalDocumentWrite = document.write; 674 document.write = customDocumentWrite; 675 try { window.ShopifyAnalytics.merchantGoogleAnalytics.call(this); } catch(error) {}; 676 document.write = originalDocumentWrite; 677 678 var match = window.location.pathname.match(/checkouts\/(.+)\/(thank_you|post_purchase)/) 679 var token = match? match[1]: undefined; 680 if (!hasLoggedConversion(token)) { 681 setCookieIfConversion(token); 682 683 } 684 }); 685 686 window.ShopifyAnalytics.lib.page(null,{"pageType":"password","requestId":"5823bc87-814a-4b96-beff-79d5c90e35af-1791586216","shopifyEmitted":true}); 687 688 var eventsListenerScript = document.createElement('script'); 689 eventsListenerScript.async = true; 690 eventsListenerScript.src = "//balkonkraftwerk-express.de/cdn/shopifycloud/storefront/assets/shop_events_listener-4e26a9ce.js"; 691 document.getElementsByTagName('head')[0].appendChild(eventsListenerScript); 692})();</script>
vendor: 1 bytes, line 692
692
693<script 694 defer 695 src="https://balkonkraftwerk-express.de/cdn/shopifycloud/perf-kit/shopify-perf-kit-3.10.1.min.js" 696 data-application="storefront-renderer" 697 data-shop-id="55579017562" 698 data-render-region="gcp-europe-west4" 699 data-page-type="password" 700 data-theme-instance-id="193346863450" 701 data-theme-name="Dawn" 702 data-theme-version="14.0.0" 703 data-monorail-region="shop_domain" 704 data-resource-timing-sampling-rate="10" 705 data-shs="true" 706 data-shs-beacon="true" 707 data-shs-export-with-fetch="true" 708 data-shs-logs-sample-rate="1" 709 data-shs-beacon-endpoint="https://balkonkraftwerk-express.de/api/collect" 710></script>
710 711<meta name="shopify-y" content="296bd508-3bf7-438b-8c59-f7be3c4d8688" data-expiration="1823143817000"><meta name="shopify-s" content="83d7f2b8-2b2e-415c-ac51-354eb3b00862" data-expiration="1791588017000"> 712</head> 713 714 <body class="password gradient"> 715 <a class="skip-to-content-link button visually-hidden" href="#MainContent"> 716 Direkt zum Inhalt 717 </a> 718 719 <div id="shopify-section-main-password-header" class="shopify-section"><style type="text/css"> 720 .password-logo { 721 max-width: 160px; 722 } 723</style> 724 725<div class="color-background-1 gradient"> 726 <div class="password-header"><img src="//balkonkraftwerk-express.de/cdn/shop/files/BKE-logo-high.jpg?v=1691081304&width=500" alt="Balkonkraftwerk-Express" srcset="//balkonkraftwerk-express.de/cdn/shop/files/BKE-logo-high.jpg?v=1691081304&width=50 50w, //balkonkraftwerk-express.de/cdn/shop/files/BKE-logo-high.jpg?v=1691081304&width=100 100w, //balkonkraftwerk-express.de/cdn/shop/files/BKE-logo-high.jpg?v=1691081304&width=150 150w, //balkonkraftwerk-express.de/cdn/shop/files/BKE-logo-high.jpg?v=1691081304&width=200 200w, //balkonkraftwerk-express.de/cdn/shop/files/BKE-logo-high.jpg?v=1691081304&width=250 250w, //balkonkraftwerk-express.de/cdn/shop/files/BKE-logo-high.jpg?v=1691081304&width=300 300w, //balkonkraftwerk-express.de/cdn/shop/files/BKE-logo-high.jpg?v=1691081304&width=400 400w, //balkonkraftwerk-express.de/cdn/shop/files/BKE-logo-high.jpg?v=1691081304&width=500 500w" width="160" height="75.66632337796086" class="password-logo"> 727<password-modal> 728 <details class="password-modal modal"> 729 <summary class="modal__toggle" aria-haspopup="dialog"> 730 <div class="modal__toggle-open password-link link underlined-link"> 731 <svg aria-hidden="true" focusable="false" class="icon icon-padlock" viewBox="0 0 16 21" fill="none"> 732 <path fill-rule="evenodd" d="M5.03 1.79A3.73 3.73 0 018 .5c1.28 0 2.28.48 2.97 1.29.67.8 1 1.87 1.03 3V7.5h3c.28 0 .5.22.5.5v12a.5.5 0 01-.5.5H1a.5.5 0 01-.5-.5V8c0-.28.22-.5.5-.5h3V4.8c0-1.1.36-2.15 1.03-3.01zM11 6.4v1.09H5V4.82c.03-.99.31-1.82.8-2.4A2.75 2.75 0 018 1.49c1.01 0 1.73.37 2.2.93.49.58.77 1.41.8 2.4V6.4zM1.5 8.49v11h13v-11h-13zm6.51 2.5a1.5 1.5 0 00-.7 2.82v2.5a.68.68 0 001.36 0v-2.47A1.5 1.5 0 008 11l.01-.01z" clip-rule="evenodd" fill="currentColor" /> 733</svg> 734 735 Mit Passwort betreten 736 </div> 737 <div class="modal__toggle-close" aria-hidden="true"> 738 <svg 739 xmlns="http://www.w3.org/2000/svg" 740 aria-hidden="true" 741 focusable="false" 742 class="icon icon-close" 743 fill="none" 744 viewBox="0 0 18 17" 745> 746 <path d="M.865 15.978a.5.5 0 00.707.707l7.433-7.431 7.579 7.282a.501.501 0 00.846-.37.5.5 0 00-.153-.351L9.712 8.546l7.417-7.416a.5.5 0 10-.707-.708L8.991 7.853 1.413.573a.5.5 0 10-.693.72l7.563 7.268-7.418 7.417z" fill="currentColor"> 747</svg> 748 749 </div> 750 </summary> 751 <div 752 class="modal__content" 753 role="dialog" 754 aria-labelledby="DialogHeading" 755 aria-modal="true" 756 > 757 <div class="password-modal__content" tabindex="-1"> 758 <button type="button" class="modal__close-button link" aria-label="SchlieÃen"> 759 <svg 760 xmlns="http://www.w3.org/2000/svg" 761 aria-hidden="true" 762 focusable="false" 763 class="icon icon-close" 764 fill="none" 765 viewBox="0 0 18 17" 766> 767 <path d="M.865 15.978a.5.5 0 00.707.707l7.433-7.431 7.579 7.282a.501.501 0 00.846-.37.5.5 0 00-.153-.351L9.712 8.546l7.417-7.416a.5.5 0 10-.707-.708L8.991 7.853 1.413.573a.5.5 0 10-.693.72l7.563 7.268-7.418 7.417z" fill="currentColor"> 768</svg> 769 770 </button> 771 <h2 class="password-modal__content-heading" id="DialogHeading"> 772 Shop mit Passwort betreten: 773 </h2><form method="post" action="/password" id="login_form" accept-charset="UTF-8" class="password-form"><input type="hidden" name="form_type" value="storefront_password" /><input type="hidden" name="utf8" value="â" /><div class="password-field field"> 774 <input 775 type="password" 776 name="password" 777 id="Password" 778 class="field__input" 779 autocomplete="current-password" 780 placeholder="Dein Passwort" 781 > 782 <label class="field__label" for="Passwort">Dein Passwort</label></div> 783 <button name="commit" class="password-button button button--outline"> 784 Eingeben 785 </button></form><small class="password__footer-text">Bist du der Shop-Inhaber? <a href="/admin" class="link underlined-link">Hier einloggen</a></small> 786 </div> 787 </div> 788 </details> 789 </password-modal> 790 </div> 791</div> 792 793<hr> 794</div> 795
796 <main id="MainContent" class="password-main"> 797 <section id="shopify-section-template--28298773856602__main" class="shopify-section section"><link href="//balkonkraftwerk-express.de/cdn/shop/t/40/assets/section-image-banner.css?v=124819179385751388401778255914" rel="stylesheet" type="text/css" media="all" /> 798<link href="//balkonkraftwerk-express.de/cdn/shop/t/40/assets/component-newsletter.css?v=4727253280200485261778255914" rel="stylesheet" type="text/css" media="all" /> 799<link href="//balkonkraftwerk-express.de/cdn/shop/t/40/assets/newsletter-section.css?v=62410470717655853621778255914" rel="stylesheet" type="text/css" media="all" /> 800<link href="//balkonkraftwerk-express.de/cdn/shop/t/40/assets/section-email-signup-banner.css?v=77014967447923078771778255914" rel="stylesheet" type="text/css" media="all" /> 801<style data-shopify>#Banner-template--28298773856602__main::after { 802 opacity: 0.0; 803 }</style><div 804 id="Banner-template--28298773856602__main" 805 class="email-signup-banner banner banner--content-align-center banner--content-align-mobile-center banner--medium banner--mobile-bottom" 806><div class="banner__media"><svg 807 preserveAspectRatio="xMidYMid slice" 808 aria-hidden="true" 809 focusable="false" 810 class="email-signup-banner-background" 811 version="1.1" 812 id="Layer_1" 813 xmlns="http://www.w3.org/2000/svg" 814 x="0" 815 y="0" 816 viewBox="0 0 2169.3 648" 817 style="enable-background:new 0 0 2169.3 648" 818 xml:space="preserve" 819> 820 <style>.st0,.st4{fill:rgb(var(--color-button))}.st4{opacity:.55;enable-background:new}</style><path class="st0" d="M1791.7 370.7zM1791.7 650.9V370.8h-.1s.1 271.6.1 280.1zM1791.7 370.8s-.1 0 0 0z"/><path d="M1308.6 371.2V216.6L1084.9-.1 861.4 215.8V651h447.2V371.2zm-223.3-248.4c-15 .1-27-12-27.2-26.8-.2-14.8 12-27 26.8-27.2 15-.1 27 12 27.2 26.8v.1c0 14.9-11.9 27-26.8 27.1z" style="opacity:.75;fill:rgb(var(--color-button));enable-background:new"/><path class="st0" d="M501.6 436.9h131c-45.2-4-86.7-22.2-120-49.9-30.9-25.8-54.7-59.9-68.1-98.7-7.8-22.5-12-46.4-12-71.3V0h.5-205.7c2.5.1 5-.2 7.5 0 110.9 10.6 197.7 104.5 197.8 217.8.2 120.4-100.3 218.8-220.9 218.8h-76v213.9h152.1v-.2c.1-117.8 95.9-213.4 213.8-213.4zM861.4 650.4V277.7c-4.8 17.5-11.8 34.2-20.8 50l-1.7 2.8c-6.3 10.8-13.5 20.8-21.5 30.3-.1.2-.4.4-.5.6l-.1.1c-13.5 15.8-29.2 29.6-46.7 40.9-2.9 1.8-5.6 3.6-8.6 5.3-12.8 7.5-26.3 13.6-40.4 18.3-9.5 3.1-19.1 5.6-29 7.4-10.9 1.9-21.9 3.1-33 3.3-1.8 0-3.5.1-5.3.1h-.1v.7c.1 39 7.7 74.9 25.8 106.5 11.4 19.1 25.4 36.5 41.5 51.6 12.7 11.8 26.6 22.4 41.5 31.2.1.1.4.2.5.4 22.7 13.2 48 20.9 73.9 23.1 1.2.1 2.3.2 3.5.2 3.8.2 20.8.4 20.8.4v-.5h.2z"/><path class="st0" d="M651.6 330.4h2.6c25.2-.6 48.5-9.7 66.8-24.5 25.1-20.2 41.2-51.3 41.2-86 0-34.8-16-65.8-41.2-86-17.9-14.5-40.6-23.4-65.1-24.4-1.4 0-2.9-.1-4.3-.1-61.1 0-110.5 49.5-110.5 110.5s49.4 110.5 110.5 110.5z"/><g style="opacity:.55"><path class="st0" d="M1810 371.2c-3.2 0-10.6 0-18.2-.5-3.7-.2-7.5-.6-11-1.1-1.1-.1-2.2-.4-3.2-.5-125.3-15.6-224.1-119.6-229-248.3-.1-1.1-.2-2.3-.4-3.3-11.8 130.3-108.6 234.6-231.7 252-2.6.4-5.3.7-7.9 1v.6c126.6 14 226.7 119.7 239 252.3 0 .2.6.1.7 0 12.4-133.6 114.3-239.9 242.2-252.6h1.2l.2 101.2-.3 179 384.5.1V371l-366.1.2z"/><path class="st0" d="M1792.4 239c5.9.8 11.7 1.2 17.8 1.2 72.4 0 131-58.6 131-131 0-45.6-23.3-85.8-58.6-109.2H1737.7c-.2.1-.4-.1-.6 0-34.9 23.6-57.9 64-57.9 109.2 0 66.3 49.3 121.1 113.2 129.8z"/></g><g style="opacity:.1"><path class="st0" d="M432.6 217.8C432.5 104.5 345.7 10.6 234.8-.1l-99 .1v436.7h76c120.6-.1 221-98.5 220.8-218.9zM1085 68.9c-15 .1-26.9 12.2-26.8 27.2.1 15 12.2 26.9 27.2 26.8 14.8-.1 26.8-12.1 26.8-26.9v-.1c-.2-15.1-12.3-27.2-27.2-27zM763.2 627.2c-.1-.1-.4-.2-.5-.4-15.1-8.9-29-19.4-41.5-31.2-16.2-15.1-30-32.4-41.5-51.6-18.2-31.5-25.7-67.5-25.8-106.5v-.7h-.3v-.1h-152c-118 0-213.7 95.6-213.8 213.6V650.6h552.9c-1.2-.1-2.3-.2-3.5-.2-26.1-2.2-51.3-10-74-23.2zM1882.5-.1s.1-.1 0 0c35.4 23.4 58.7 63.7 58.7 109.2 0 72.4-58.6 131-131 131-6 0-12-.4-17.8-1.2-63.9-8.7-113.2-63.5-113.2-129.8 0-45.2 23-85.7 57.9-109.2.2-.1.4.1.6 0h.2-165.1c-15.8 33.5-24.6 71.7-24.6 111.2v4.9c0 1.7.1 3.5.1 5.1 4.9 128.6 103.7 232.7 229 248.3 1.1.1 2.2.4 3.2.5 3.5.5 7.3.8 11 1.1 7.7.5 276.9.1 276.9.1V-.1h-185.9z"/></g>
820<path class="st0" d="M1308.6 216.6v153.9c2.6-.2 5.1-.6 7.8-1 123.1-17.3 219.9-121.7 231.7-251.8.1 1.1.2 2.3.4 3.3-.1-1.7-.1-3.3-.1-5.1V111c0-39.5 8.9-77.5 24.6-111h-488.1l223.7 216.6zM1791.6 370.8c-127.8 12.8-231 119-243.5 252.6 0 .2-.1.1-.2 0-12.3-132.7-112.7-238.3-239.3-252.3l-.1 279.3v.7h483.1v-.2c.1-8.5 0-280.1 0-280.1z"/><path class="st0" d="M1548.1 623.4c12.4-133.5 115.6-239.8 243.5-252.6h-1.3c-127.9 12.7-229.7 119-242.2 252.6zM2068.7-.1V371h107.2V-.1z"/><path class="st4" d="M135.9-.1C59.6-.1-2.3 61.8-2.3 138.1V649.9l138.2-.4V-.1z"/><g><path class="st0" d="M721.2 134.3c-17.9-14.5-40.6-23.4-65.1-24.4-1.4 0-2.9-.1-4.3-.1-61.1 0-110.5 49.5-110.5 110.5s49.5 110.5 110.5 110.5h2.6c25.2-.6 48.5-9.7 66.8-24.5 25.1-20.2 41.2-51.3 41.2-86s-16.1-65.8-41.2-86z"/><path class="st4" d="M870.4-.1H432.7v217.5c0 24.9 4.2 48.9 12 71.3 13.3 38.8 37.2 72.9 68.1 98.7 33.3 27.8 74.5 45.5 119.9 49.4h21.1s3.7.6 5.5.5c11.1-.2 22.1-1.4 33-3.3 9.8-1.8 19.5-4.3 29-7.4 14-4.7 27.6-10.8 40.4-18.3 2.9-1.7 5.7-3.5 8.6-5.3 17.5-11.4 33.1-25.1 46.7-40.9l.1-.1c.1-.2.4-.4.5-.6 8-9.5 15.2-19.5 21.5-30.3l1.7-2.8c9-15.8 15.9-33.1 20.7-50.6v-62l69-66.6c-37.4-38.5-60.1-91.4-60.1-149.2zM721.2 306.3c-18.3 14.8-41.5 23.9-66.8 24.5h-2.6c-61.1 0-110.5-49.5-110.5-110.5s49.5-110.5 110.5-110.5c1.4 0 2.9 0 4.3.1 24.6 1 47.1 9.9 65.1 24.4 25.1 20.2 41.2 51.3 41.2 86-.1 34.8-16.1 65.8-41.2 86z"/><path class="st0" d="M870.4-.1s0-.1 0 0c0 57.8 22.7 110.8 60 149.2L1084.9-.1s-214.5-.2-214.5 0z"/></g> 821</svg> 822<svg 823 preserveAspectRatio="xMidYMid slice" 824 aria-hidden="true" 825 focusable="false" 826 class="email-signup-banner-background email-signup-banner-background-mobile" 827 id="Layer_1" 828 data-name="Layer 1" 829 xmlns="http://www.w3.org/2000/svg" 830 viewBox="0 0 374.45 238.04" 831> 832 <defs><style>.cls-1{fill:none;}.cls-2,.cls-4,.cls-5{fill:rgb(var(--color-button));;}.cls-3{opacity:0.55;}.cls-4{opacity:0.75;}.cls-5{opacity:0.1;}</style></defs><path class="cls-1" d="M89.5,238c-7.27-1.67-14.13-5.11-20.52-8.82a87.1,87.1,0,0,1-30.36-30.35c-6.72-11.64-9.49-24.91-9.49-39.33H29v0c0,14.42,2.77,27.71,9.49,39.36a87.1,87.1,0,0,0,30.36,30.35c6.39,3.71,13.25,7.15,20.52,8.82h.46Z"/><path class="cls-2" d="M106.58,95.46v-.6a80.39,80.39,0,0,1-9.11,24.62l-.6,1a80,80,0,0,1-25.22,26.36c-1,.67-2.07,1.33-3.14,1.95a78,78,0,0,1-25.42,9.44,80.26,80.26,0,0,1-14,1.26c0,14.42,2.77,27.69,9.49,39.33A87.1,87.1,0,0,0,69,229.17c6.39,3.71,13.25,7.15,20.52,8.82l.31,0H93.7a15.28,15.28,0,0,1,3.38,0,74.24,74.24,0,0,0,10.2,0h-.64Z"/><g class="cls-3"><path class="cls-2" d="M357,118.48A78.88,78.88,0,0,0,346.42,158v0h0v0A78.9,78.9,0,0,0,357,197.68a79.76,79.76,0,0,0,17.46,20.88v-121A79.52,79.52,0,0,0,357,118.48Z"/><path class="cls-2" d="M346.42,0a78.9,78.9,0,0,1-10.56,39.25,79.66,79.66,0,0,1-28.95,29,78.58,78.58,0,0,1-39.17,10.6,78.6,78.6,0,0,1,39.15,10.59A79.23,79.23,0,0,1,346.42,158V78.86a79,79,0,0,1,28-60.49V0Z"/><path class="cls-2" d="M114.22,27.3a79.2,79.2,0,0,1-5-27.3h-79V0H0V53.79a39.51,39.51,0,0,1,29.37-13l.86,0A39.58,39.58,0,0,1,69,78.88c0,.5,0,1,0,1.51A39.62,39.62,0,0,1,0,107v47a78,78,0,0,0,29,5.56v0h.15a80.26,80.26,0,0,0,14-1.26,78,78,0,0,0,25.42-9.44c1.07-.62,2.11-1.28,3.14-1.95a80,80,0,0,0,25.22-26.36l.6-1a80.39,80.39,0,0,0,9.11-24.62V78.57l24.69-24.1h0a79,79,0,0,1-11.41-15.19A79.32,79.32,0,0,1,114.22,27.3Z"/></g><path class="cls-2" d="M357,39.26a79.64,79.64,0,0,1,17.46-20.89h0A79.64,79.64,0,0,0,357,39.26Z"/><path class="cls-2" d="M357,118.46A78.93,78.93,0,0,0,346.42,158v0a79.09,79.09,0,0,1,28-60.44h0A79.64,79.64,0,0,0,357,118.46Z"/><path class="cls-2" d="M357,197.68a78.9,78.9,0,0,1-10.57-39.6v0h0v0a79.06,79.06,0,0,0-78.68-79.14,78.58,78.58,0,0,0,39.17-10.6,79.66,79.66,0,0,0,28.95-29A78.9,78.9,0,0,0,346.42,0c0-.12,0-.23,0-.35H267.37v0H109.28V0h0a79.2,79.2,0,0,0,5,27.3,79.32,79.32,0,0,0,5.63,12,79,79,0,0,0,11.41,15.19L187,0h0l57,55.91h0l23.36,22.93h0l0,0V238h107V218.56A79.76,79.76,0,0,1,357,197.68Z"/><path class="cls-2" d="M29.37,120A39.62,39.62,0,0,0,69,80.39c0-.51,0-1,0-1.51A39.56,39.56,0,0,0,30.23,40.79l-.86,0A39.51,39.51,0,0,0,0,53.79V107A39.52,39.52,0,0,0,29.37,120Z"/><path class="cls-4" d="M94.12,238h2.82A12.66,12.66,0,0,0,94.12,238Z"/><path class="cls-5" d="M38.47,198.87C31.75,187.22,29,173.93,29,159.51A78,78,0,0,1,0,154V238H89.35c-7.27-1.67-14.13-5.11-20.52-8.82A87.1,87.1,0,0,1,38.47,198.87Z"/><path class="cls-4" d="M267.41,78.88l0,0h0L244,55.91h0L187,0h0L131.26,54.46h0l-24.69,24.1V93.82c0-.21.11-.42.15-.6v25.21l0,79V238h1.41l-.91,0H267.41V78.88ZM187.17,44.49A9.88,9.88,0,1,1,197,34.61,9.88,9.88,0,0,1,187.17,44.49Z"/><path class="cls-4" d="M106.78,238h0V197.42l0-79V93.22c0,.18-.1.39-.15.6v1.64L106.64,238h.64l.91,0Z"/><path class="cls-5" d="M357,39.26a78.9,78.9,0,0,0-10.57,39.6V158a79,79,0,0,1,28-60.43V18.37A79.64,79.64,0,0,0,357,39.26Z"/><circle class="cls-5" cx="187.17" cy="34.61" r="9.88"/> 833</svg> 834</div><div class="banner__content banner__content--middle-center page-width"> 835 <div class="email-signup-banner__box banner__box newsletter newsletter__wrapper isolate content-container color-background-1 gradient content-container--full-width-mobile"><h2 class="email-signup-banner__heading h1" > 836 Opening soon 837 </h2><div class="newsletter__subheading rte body" > 838 <p>
838Be the first to know when we launch.</p> 839 </div><div > 840 <form method="post" action="/contact#contact_form" id="contact_form" accept-charset="UTF-8" class="newsletter-form"><input type="hidden" name="form_type" value="customer" /><input type="hidden" name="utf8" value="â" /> 841 <input type="hidden" name="contact[tags]" value="newsletter"> 842 <div class="newsletter-form__field-wrapper"> 843 <div class="field"> 844 <input 845 id="NewsletterForm--template--28298773856602__main" 846 type="email" 847 name="contact[email]" 848 class="field__input" 849 value="" 850 aria-required="true" 851 autocorrect="off" 852 autocapitalize="off" 853 autocomplete="email" 854 855 placeholder="E-Mail" 856 required 857 > 858 <label class="field__label" for="NewsletterForm--template--28298773856602__main"> 859 E-Mail 860 </label> 861 <button 862 type="submit" 863 class="newsletter-form__button field__button" 864 name="commit" 865 id="Subscribe" 866 aria-label="Abonnieren" 867 > 868 <svg 869 viewBox="0 0 14 10" 870 fill="none" 871 aria-hidden="true" 872 focusable="false" 873 class="icon icon-arrow" 874 xmlns="http://www.w3.org/2000/svg" 875> 876 <path fill-rule="evenodd" clip-rule="evenodd" d="M8.537.808a.5.5 0 01.817-.162l4 4a.5.5 0 010 .708l-4 4a.5.5 0 11-.708-.708L11.793 5.5H1a.5.5 0 010-1h10.793L8.646 1.354a.5.5 0 01-.109-.546z" fill="currentColor"> 877</svg> 878 879 </button> 880 </div></div></form> 881 </div></div> 882 </div> 883</div> 884 885 886</section> 887 </main> 888 <footer> 889 <div id="shopify-section-main-password-footer" class="shopify-section"> 890<hr><div class="password__footer color-background-1 gradient"> 891 <ul class="list-social list-unstyled" role="list"></ul> 892 893 <small class="password__footer-caption password__footer-text">Dieser Shop wird unterstützt von 894 <a 895 class="shopify-link" 896 href="//www.shopify.com" 897 rel="nofollow" 898 target="_blank" 899 aria-describedby="a11y-new-window-message" 900 aria-label="Shopify" 901 > 902 <svg 903 xmlns="http://www.w3.org/2000/svg" 904 class="icon icon-shopify"
905 viewBox="0 0 150 43" 906 version="1.1" 907 aria-hidden="true" 908 > 909 <path fill="currentColor" d="M33.3,8.9 C33.3,8.9 33.3,8.7 33.2,8.6 C33.1,8.5 33,8.5 33,8.5 L29.6,8.3 L27.5,6.2 C27.4,6.1 27.3,6.1 27.2,6.1 L25.4,42.2 L38,39.5 L33.3,8.9 Z M25.8,5.9 L24.9,6.2 C24.3,4.6 23.6,3.4 22.6,2.7 C21.9,2.2 21.1,2 20.3,2.1 C20.1,1.9 19.9,1.7 19.7,1.5 C18.8,0.8 17.6,0.6 16.1,1.2 C11.8,2.7 10,8.3 9.3,11 L5.5,12.1 C5.5,12.1 4.6,12.3 4.4,12.6 C4.2,12.9 4.1,13.6 4.1,13.6 L0.9,37.9 L24.5,42.3 L26.3,6 C26.1,5.8 25.9,5.9 25.8,5.9 Z M20.1,7.6 L16,8.9 C16.5,6.8 17.6,4.6 19.6,3.8 C20,4.8 20.1,6.3 20.1,7.6 Z M16.6,2.4 C17.5,2.1 18.2,2.1 18.7,2.4 C16,3.6 14.8,6.7 14.3,9.3 L11,10.3 C11.7,7.8 13.3,3.6 16.6,2.4 Z M18.9,20.3 C18.7,20.2 18.5,20.1 18.2,20 C17.9,19.9 17.7,19.8 17.4,19.7 C17.1,19.6 16.8,19.6 16.4,19.5 L15.3,19.5 C15,19.5 14.7,19.6 14.4,19.7 C14.1,19.8 13.9,19.9 13.7,20.1 C13.5,20.3 13.4,20.5 13.3,20.7 C13.2,20.9 13.1,21.2 13.1,21.4 C13.1,21.6 13.1,21.8 13.2,22 C13.3,22.2 13.4,22.4 13.5,22.6 C13.7,22.8 13.9,23 14.1,23.2 C14.3,23.4 14.6,23.6 14.9,23.8 C15.4,24.1 15.8,24.4 16.3,24.8 C16.8,25.2 17.2,25.6 17.5,26.1 C17.9,26.6 18.2,27.1 18.4,27.8 C18.6,28.4 18.7,29.1 18.7,29.9 C18.6,31.1 18.4,32.2 17.9,33.1 C17.5,34 16.8,34.7 16.1,35.2 C15.4,35.7 14.5,36 13.6,36.1 C12.7,36.2 11.7,36.2 10.8,35.9 C10.3,35.8 9.9,35.6 9.5,35.5 C9.1,35.3 8.7,35.1 8.3,34.9 C8,34.7 7.6,34.5 7.4,34.3 C7.1,34.1 6.9,33.9 6.7,33.6 L7.8,30 C8,30.2 8.2,30.3 8.5,30.5 C8.8,30.7 9.1,30.9 9.4,31 C9.7,31.2 10.1,31.3 10.4,31.5 C10.8,31.6 11.1,31.7 11.5,31.8 L12.3,31.8 C12.5,31.7 12.8,31.6 12.9,31.5 C13.1,31.4 13.2,31.2 13.3,31 C13.4,30.8 13.4,30.6 13.5,30.3 C13.5,30.1 13.5,29.8 13.4,29.6 C13.3,29.4 13.2,29.2 13.1,28.9 C13,28.7 12.8,28.5 12.5,28.2 C12.3,28 12,27.7 11.6,27.5 C11.2,27.2 10.8,26.9 10.4,26.5 C10.1,26.1 9.7,25.8 9.5,25.3 C9.3,24.9 9.1,24.4 8.9,23.9 C8.8,23.4 8.7,22.9 8.7,22.3 C8.7,21.3 8.9,20.5 9.3,19.7 C9.6,18.9 10.1,18.2 10.7,17.5 C11.3,16.9 12,16.3 12.9,15.9 C13.8,15.5 14.7,15.2 15.8,15 C16.3,14.9 16.8,14.9 17.2,14.9 C17.7,14.9 18.1,14.9 18.5,15 C18.9,15.1 19.3,15.1 19.6,15.2 C19.9,15.3 20.2,15.4 20.5,15.5 L18.9,20.3 Z M21.5,7.2 L21.5,6.7 C21.5,5.4 21.3,4.3 21,3.5 C21.3,3.5 21.6,3.6 21.9,3.8 C22.7,4.3 23.2,5.4 23.6,6.6 L21.5,7.2 Z"/> 910 <path fill="currentColor" d="M45.3,29.6 C46.2,30.1 47.8,30.7 49.4,30.7 C50.8,30.7 51.6,29.9 51.6,29 C51.6,28.1 51.1,27.5 49.5,26.6 C47.6,25.5 46.2,24 46.2,22 C46.2,18.5 49.2,16 53.6,16 C55.5,16 57,16.4 57.8,16.8 L56.6,20.3 C55.9,20 54.8,19.6 53.5,19.6 C52.1,19.6 51.2,20.2 51.2,21.3 C51.2,22.1 51.9,22.7 53.1,23.3 C55.1,24.4 56.7,25.9 56.7,28 C56.7,32 53.5,34.2 49,34.1 C46.9,34.1 45,33.5 44.1,32.9 L45.3,29.6 Z M57.7,34.1 L62.6,8.9 L67.6,8.9 L65.7,18.7 L65.8,18.7 C67.1,17.1 68.9,16 71.1,16 C73.7,16 75.2,17.7 75.2,20.5 C75.2,21.4 75.1,22.7 74.8,23.8 L72.8,34.1 L67.8,34.1 L69.7,24.2 C69.8,23.5 69.9,22.7 69.9,22 C69.9,20.9 69.5,20.2 68.3,20.2 C66.7,20.2 65,22.2 64.3,25.5 L62.6,34.2 L57.7,34.2 L57.7,34.1 Z M93.3,23 C93.3,29.1 89.3,34.4 83.4,34.4 C78.9,34.4 76.5,31.3 76.5,27.5 C76.5,21.5 80.5,16.1 86.5,16.1 C91.2,16.1 93.3,19.4 93.3,23 Z M81.6,27.3 C81.6,29.1 82.3,30.5 84,30.5 C86.7,30.5 88.1,25.8 88.1,22.8 C88.1,21.3 87.5,19.8 85.7,19.8 C83.1,19.9 81.6,24.5 81.6,27.3 Z M92.1,41.1 L95.6,23 C96,21 96.4,18.3 96.6,16.4 L101,16.4 L100.7,19.2 L100.8,19.2 C102.1,17.3 104.1,16.2 106.1,16.2 C109.8,16.2 111.3,19.1 111.3,22.5 C111.3,28.5 107.4,34.6 101.6,34.6 C100.4,34.6 99.2,34.1 98.7,34.1 L98.6,34.1 L97.2,41.1 L92.1,41.1 Z M99.3,29.9 C99.8,30.3 100.5,30.6 101.4,30.6 C104.2,30.6 106.1,26 106.1,22.8 C106.1,21.5 105.6,20.1 104.1,20.1 C102.4,20.1 100.7,22.1 100.1,25.2 L99.3,29.9 Z M111.5,34.1 L114.9,16.4 L120,16.4 L116.6,34.1 L111.5,34.1 Z M118,14.5 C116.6,14.5 115.6,13.4 115.6,11.9 C115.6,10.3 116.9,9 118.5,9 C120,9 121,10.1 121,11.6 C121,13.4 119.6,14.5 118,14.5 Z M120.9,34.1 L123.6,20.1 L121.3,20.1 L122,16.4 L124.3,16.4 L124.4,15.6 C124.8,13.5 125.6,11.4 127.3,10 C128.6,8.9 130.4,8.4 132.2,8.4 C133.4,8.4 134.3,8.6 134.9,8.8 L133.9,12.7 C133.5,12.6 133,12.4 132.3,12.4 C130.6,12.4 129.6,13.9 129.3,15.6 L129.1,16.4 L132.6,16.4 L131.9,20.1 L128.4,20.1 L125.7,34.1 L120.9,34.1 L120.9,34.1 Z M138.9,16.4 L139.7,24.3 C139.9,26.1 140.1,27.6 140.1,28.5 L140.2,28.5 C140.6,27.6 141,26.2 141.7,24.3 L144.8,16.4 L150,16.4 L143.9,29.5 C141.7,34 139.6,37.2 137.3,39.4 C135.5,41.1 133.4,41.9 132.4,42.1 L131,37.9
910C131.8,37.6 132.9,37.2 133.8,36.5 C135,35.7 135.9,34.6 136.5,33.5 C136.6,33.2 136.7,33 136.6,31.6 L133.6,16.4 L138.9,16.4 Z"/> 911 </svg> 912 </a> 913 </small> 914 <small class="password__footer-login password__footer-text">Bist du der Shop-Inhaber? <a href="/admin" class="link underlined-link">Hier einloggen</a></small> 915</div> 916 917 918</div> 919 </footer> 920 <ul hidden> 921 <li id="a11y-new-window-message">Wird in einem neuen Fenster geöffnet.</li> 922 </ul> 923 <style> .green {background: #00ba0b;} div.center-pulse {display: flex; align-items: center;} .circle {display: inline; width: 8px; height: 8px; border-radius: 50%; box-shadow: 0px 0px 1px 1px #0000001a; margin: 0 8px 0 0;} .pulse {animation: pulse-animation 1.5s infinite;} @keyframes pulse-animation {0% {box-shadow: 0 0 0 0px lightgreen; } 100% {box-shadow: 0 0 0 8px rgba(0, 0, 0, 0); }} .mnlc-item-avalilable {font-size: 12px;} /* Vetrauenselemente - Start */.sales-points {list-style: none; font-size: 12px; font-weight: 250; color: green;} .sales-points svg {min-width: 25px; text-align: left;} ul.sales-points {padding-inline-start: 0px;} .sales-points span,.sales-points svg {display: flex; align-items: center;} .sales-points li {padding: 16px; border-top: 1px solid #dcdcdc; border-right: 1px solid #dcdcdc; border-left: 1px solid #dcdcdc;} .sales-points li:last-child {border-bottom: 1px solid #dcdcdc;} .sales-points .icon {width: 25px; height: 25px; margin-right: 24px;} .product-form__buttons {max-width: 100% !important;} /* Vetrauenselemente - Ende */button.product-form__submit.button--primary.button--full-width,button[name="checkout"].button--primary,button[name="checkout"].cart__checkout-button,button.product-form__submit.button--primary.button--full-width:hover,button[name="checkout"].button--primary:hover,button[name="checkout"].cart__checkout-button:hover {background-color: #f09500; color: #fff; border: none; outline: none; box-shadow: none;} </style> 924<div id="shopify-block-AV1c1MGppS3Bld2V0Z__1967102994182284862" class="shopify-block shopify-app-block"><!-- BEGIN app snippet: connected_sdk_script -->
924<script src='https://www.paypalobjects.com/pa/js/min/pa.js'></script>
924 925
926<script 927 src='https://www.paypal.com/sdk/js?client-id=AbtXlp5R3xc83T9xW_ZXKTK8IVZ-14EMelDioU4VC285IqSuxUI4yotKxjV0sz_X6YsONYi10vSBPNKM&merchant-id=DMA4TKM2QXZ5U&components=messages' 928 data-namespace='PayPalSDK' 929 data-partner-attribution-id='JADEDPIXEL_SHOPPINGCART_EC_CA' 930></script>
930 931<!-- END app snippet --><!-- BEGIN app snippet: utils -->
931<script> 932 // ============================================ 933 // Core Utilities 934 // ============================================ 935 936 // Setup FPTI Analytics 937 if (PAYPAL && PAYPAL.analytics) { 938 PAYPAL.analytics.setup({ 939 data: { 940 pgrp: 'shopify:messaging:app-embed', 941 comp: 'shopify-app-embed', 942 }, 943 }); 944 } 945 946 const recordEvent = (event_name, additionalData) => { 947 PAYPAL.analytics.instance.recordEvent(event_name, { 948 theme: window?.Shopify?.theme?.name || 'unknown', 949 shop: window?.Shopify?.shop || 'unknown', 950 path: window.location.pathname, 951 flow: 'Shopify - Pay Later', 952 space_key: 'SKD8GM', 953 ...additionalData, 954 }); 955 }; 956 957 // In Theme Editor when using Advanced Settings 958 // User chooses copy as JS Path from their browser 959 // The pasted value will be a string that contains "document.querySelector("...") 960 // Remove document.querySelector(" and ") 961 // Using JS Path we can ensure the path to the element is accurate 962 const parseSelector = (selector) => { 963 try { 964 // Handle both single and double quotes 965 return selector 966 .replace(/document\.querySelector\(["']/g, '') 967 .replace(/["']\)/g, ''); 968 } catch (e) { 969 recordEvent('appEmbed:parseSelector:error', { error: e.message }); 970 } 971 }; 972 973 // Waits for Element to be rendered 974 // Uses mutation observer to watch for changes in the DOM 975 // https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver 976 // On changes, check for the selector again 977 const waitForElement = (selector) => { 978 try { 979 return new Promise((resolve) => { 980 const existing = document.querySelector(selector); 981 if (existing) return resolve(existing); 982 983 const observer = new MutationObserver(() => { 984 const el = document.querySelector(selector); 985 if (el) { 986 observer.disconnect(); 987 resolve(el); 988 } 989 }); 990 991 observer.observe(document.body, { 992 childList: true, 993 subtree: true, 994 }); 995 }); 996 } catch (e) { 997 recordEvent('appEmbed:waitForElement:error', { error: e.message }); 998 } 999 }; 1000 1001 // Formats correct cart/drawer subtotal and adds or updates messaging 1002 const addOrUpdateMessagingPriceAttribute = async (price, locator, messaging) => { 1003 try { 1004 // Format price to 2 decimal places ex: 150000 => 1500.00 1005 // Messaging requires price to be in string format 1006 const formattedPrice = Number((price / 100).toFixed(2)).toString(); 1007 1008 const messagingId = messaging.id; 1009 const existingMessaging = document.getElementById(messagingId); 1010 1011 if (existingMessaging) { 1012 // Update the data-pp-amount attribute on the existing element 1013 existingMessaging.setAttribute('data-pp-amount', formattedPrice); 1014 } else { 1015 const element = await waitForElement(locator); 1016 1017 const updatedPriceMessaging = messaging.cloneNode(); 1018 updatedPriceMessaging.setAttribute('data-pp-amount', formattedPrice); 1019 1020 element.after(updatedPriceMessaging); 1021 } 1022 } catch (e) { 1023 recordEvent('appEmbed:addOrUpdateMessagingPriceAttribute:error', { error: e.message }); 1024 } 1025 }; 1026 1027 // Fetches cart details from Shopify cart endpoint 1028 const fetchCartDetails = async () => { 1029 try { 1030 const response = await fetch('/cart.js'); 1031 const cart = await response.json(); 1032 1033 return cart.total_price; 1034 } catch (e) { 1035 recordEvent('appEmbed:fetchCartDetails:error', { error: e.message }); 1036 } 1037 }; 1038 1039 // Fetches variant details Shopify product endpoint 1040 // https://shopify.dev/docs/api/ajax/reference/product 1041 const fetchVariant = async () => { 1042 try { 1043 // Ex: https://test-store.myshopify.com/products/the-multi-location-snowboard?variant=45268551368883 1044 const urlParams = new URLSearchParams(window.location.search); 1045 const urlVariantId = parseInt(urlParams.get('variant'), 10); 1046 const productHandle = window.location.pathname.split('/products/')[1]?.split('?')[0]; 1047 1048 const response = await fetch(`/products/${productHandle}.js`); 1049 const productData = await response.json(); 1050 const variants = productData.variants; 1051 const selectedVariant = variants.find(v => v.id === urlVariantId); 1052 1053 if (selectedVariant) { 1054 return selectedVariant; 1055 } 1056 1057 return variants.find(v => v.available) || variants[0]; 1058 } catch (e) { 1059 recordEvent('appEmbed:fetchVariant:error', { error: e.message }); 1060 } 1061 }; 1062 1063 // Get the selected variant ID from URL, form input, or select element
1064 const getSelectedVariantId = () => { 1065 try { 1066 const urlParams = new URLSearchParams(window.location.search); 1067 const urlVariant = urlParams.get('variant'); 1068 if (urlVariant) return parseInt(urlVariant, 10); 1069 1070 const formInput = document.querySelector('form[action*="/cart/add"] input[name="id"]'); 1071 if (formInput?.value) return parseInt(formInput.value, 10); 1072 1073 const selectInput = document.querySelector('form[action*="/cart/add"] select[name="id"]'); 1074 if (selectInput?.value) return parseInt(selectInput.value, 10); 1075 1076 return null; 1077 } catch (e) { 1078 recordEvent('appEmbed:getSelectedVariantId:error', { error: e.message }); 1079 return null; 1080 } 1081 }; 1082 1083 const getShopifyData = () => { 1084 try { 1085 const selectedVariantId = getSelectedVariantId(); 1086 1087 const findVariant = (variants) => { 1088 if (!variants?.length) return null; 1089 if (selectedVariantId) { 1090 const selected = variants.find(v => v.id === selectedVariantId); 1091 if (selected) return selected; 1092 } 1093 return variants.find(v => v.available) || variants[0]; 1094 }; 1095 1096 if (window.meta?.product) { 1097 const product = window.meta.product; 1098 const variant = findVariant(product.variants); 1099 return { 1100 product: { title: product.title, vendor: product.vendor, type: product.type, id: product.id }, 1101 price: variant ? { 1102 priceInCents: variant.price, 1103 compareAtPrice: variant.compare_at_price, 1104 currency: window.Shopify?.currency?.active || 'USD', 1105 variantId: variant.id, 1106 variantTitle: variant.title 1107 } : null 1108 }; 1109 } 1110 1111 if (window.ShopifyAnalytics?.meta?.product) { 1112 const product = window.ShopifyAnalytics.meta.product; 1113 const variant = findVariant(product.variants); 1114 return { 1115 product: { title: product.title, vendor: product.vendor, id: product.id }, 1116 price: variant ? { 1117 priceInCents: variant.price, 1118 currency: window.Shopify?.currency?.active || 'USD', 1119 variantId: variant.id 1120 } : null 1121 }; 1122 } 1123 1124 return null; 1125 } catch (e) { 1126 recordEvent('appEmbed:getShopifyData:error', { error: e.message }); 1127 return null; 1128 } 1129 }; 1130 1131 // Generic function to watch for changes in the DOM 1132 // and listen for specific API calls made by the Shopify store 1133 // Callback provides specific logic for handling the response 1134 const watchForChanges = (locator, messaging, callback) => { 1135 try { 1136 // Overrides the native `fetch` function to listen for specific API calls. 1137 // Wrapped in an Immediately Invoked Function Expression (IIFE) 1138 // To avoid polluting the global namespace. 1139 (function (window, windowFetch) { 1140 // Check if `fetch` is a function. If not, exit early. 1141 if (typeof fetch !== 'function') return; 1142 1143 window.fetch = function () { 1144 // Call the original `fetch` function with the provided arguments. 1145 const response = windowFetch.apply(this, arguments); 1146 1147 response.then(async (res) => { 1148 if (typeof callback === 'function') { 1149 callback(res, locator, messaging); 1150 } 1151 }); 1152 1153 // Return the original `fetch` response. 1154 return response; 1155 }; 1156 })(window, window.fetch); 1157 } catch (e) { 1158 recordEvent('appEmbed:watchForChanges:error', { error: e.message }); 1159 } 1160 }; 1161 1162 // Provides ability to watch for cart changes 1163 // such as increment, decrement, or remove items 1164 // Also handles adding new items which opens Cart Drawer 1165 // and updates the messaging with the new cart total 1166 // This function is used for both Cart Drawer and Cart Page 1167 const watchForCartChanges = (locator, messaging) => { 1168 const cartChangeCallback = async (res, locator, messaging) => { 1169 // Determine the id of messaging div either #paypal-upstream-messaging-cart-{page | drawer} 1170 const messagingId = messaging.id; 1171 1172 // Changes (e.g., increment, decrement, or remove items). 1173 if (res.url.includes('/cart/change')) { 1174 // Clone the response to read its JSON data without consuming the original response. 1175 res 1176 .clone() 1177 .json() 1178 .then((data) => { 1179 addOrUpdateMessagingPriceAttribute(data.total_price, locator, messaging); 1180 }); 1181 1182 // Called when adding a new item which opens Cart Drawer 1183 } else if (res.url.includes('/cart/add')) { 1184 // Adding items does not return the updated cart details, 1185 // Fetch the updated cart details (e.g., total_price). 1186 const updatedPrice = await fetchCartDetails(); 1187 1188 // Introduce a delay to handle potential race conditions where the cart drawer has not rendered yet. 1189 setTimeout(async () => { 1190 await addOrUpdateMessagingPriceAttribute(updatedPrice, locator, messaging); 1191 }, 500); 1192 } 1193 }; 1194 1195 watchForChanges(locator, messaging, cartChangeCallback); 1196 }; 1197
1198 // Provides ability to watch for product specific changes 1199 // such as variant selection on product pages 1200 const watchForProductChanges = (locator, messaging) => { 1201 let lastVariantId = new URLSearchParams(window.location.search).get('variant'); 1202 1203 const handleVariantChange = async () => { 1204 const currentVariantId = new URLSearchParams(window.location.search).get('variant'); 1205 if (currentVariantId && currentVariantId !== lastVariantId) { 1206 lastVariantId = currentVariantId; 1207 1208 const newVariantData = await fetchVariant(); 1209 const newPriceInCents = newVariantData?.price; 1210 1211 if (newPriceInCents) { 1212 await addOrUpdateMessagingPriceAttribute(newPriceInCents, locator, messaging); 1213 } else { 1214 recordEvent('appEmbed:watchForProductChanges:error:noNewVariantPrice'); 1215 } 1216 } 1217 }; 1218 1219 // Shopify themes can either handle variant changes on the client-side or server-side (via variant API calls) 1220 // We will "intercept" and handle variant changes by: 1221 // 1. Watching for URL changes 1222 const _pushState = history.pushState.bind(history); 1223 const _replaceState = history.replaceState.bind(history); 1224 1225 history.pushState = function (...args) { 1226 _pushState(...args); 1227 handleVariantChange(); 1228 }; 1229 1230 history.replaceState = function (...args) { 1231 _replaceState(...args); 1232 handleVariantChange(); 1233 }; 1234 1235 // 2. Watching for back/forward browser clicks 1236 window.addEventListener('popstate', handleVariantChange); 1237 1238 // 3. Fallback: Watching for variant API calls 1239 // Future optimization: This fallback could be removed if all Shopify themes can be handled with 1. and 2. 1240 const productChangeCallback = async (res, locator, messaging) => { 1241 if (res.url.includes('/variants')) { 1242 await handleVariantChange(); 1243 return; 1244 } 1245 }; 1246 1247 watchForChanges(locator, messaging, productChangeCallback); 1248 }; 1249</script>
1249 1250<!-- END app snippet --><!-- BEGIN app snippet: price-detection -->
1250<script> 1251 // ============================================ 1252 // Price Detection & Element Scoring 1253 // Depends on: utils.liquid (recordEvent, getShopifyData) 1254 // ============================================ 1255 1256 const extractPriceFromText = (text) => { 1257 const match = text.match(/[\d,]+\.?\d*/); 1258 if (!match) return null; 1259 return parseFloat(match[0].replace(/,/g, '')); 1260 }; 1261 1262 const textMatchesPrice = (text, knownPriceInCents) => { 1263 const extracted = extractPriceFromText(text); 1264 if (extracted === null) return false; 1265 const knownPrice = knownPriceInCents / 100; 1266 return Math.abs(extracted - knownPrice) < 0.01; 1267 }; 1268 1269 const hasStrikethrough = (element) => { 1270 let el = element; 1271 while (el && el !== document.body) { 1272 const style = getComputedStyle(el); 1273 if (style.textDecoration.includes('line-through') || 1274 style.textDecorationLine?.includes('line-through')) { 1275 return true; 1276 } 1277 if (['S', 'DEL', 'STRIKE'].includes(el.tagName)) { 1278 return true; 1279 } 1280 el = el.parentElement; 1281 } 1282 return false; 1283 }; 1284 1285 // Find the add-to-cart button. Result is cached since the button doesn't change per page load. 1286 let _cachedAddToCartButton; 1287 const findAddToCartButton = () => { 1288 if (_cachedAddToCartButton !== undefined) return _cachedAddToCartButton; 1289 try { 1290 const formButton = document.querySelector('form[action*="/cart/add"] button[type="submit"]'); 1291 if (formButton) return (_cachedAddToCartButton = formButton); 1292 1293 const buttons = document.querySelectorAll('button'); 1294 for (const btn of buttons) { 1295 const text = btn.textContent.toLowerCase(); 1296 if (text.includes('add to cart') || text.includes('add to bag') || 1297 text.includes('buy now') || text.includes('add to basket')) { 1298 return (_cachedAddToCartButton = btn); 1299 } 1300 } 1301 1302 return (_cachedAddToCartButton = null); 1303 } catch (e) { 1304 recordEvent('appEmbed:findAddToCartButton:error', { error: e.message }); 1305 return (_cachedAddToCartButton = null); 1306 } 1307 }; 1308 1309 // Find element containing specific text (used for product title proximity). 1310 // Result is cached per text string â title is static per page load and scoreElement 1311 // calls this once per candidate, so without caching it's O(candidates) full DOM walks. 1312 let _titleElementCache = {}; 1313 const findElementContainingText = (text) => { 1314 if (text in _titleElementCache) return _titleElementCache[text]; 1315 try { 1316 const normalizedText = text.toLowerCase().trim(); 1317 const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT); 1318 1319 let node; 1320 while (node = walker.nextNode()) { 1321 const nodeText = node.textContent.trim().toLowerCase(); 1322 if (nodeText.length > 5 && normalizedText.includes(nodeText.substring(0, 20))) { 1323 const el = node.parentElement; 1324 const rect = el.getBoundingClientRect(); 1325 if (rect.width > 0 && rect.height > 0) { 1326 const fontSize = parseFloat(getComputedStyle(el).fontSize); 1327 if (fontSize >= 16) return (_titleElementCache[text] = el); 1328 } 1329 } 1330 } 1331 return (_titleElementCache[text] = null); 1332 } catch (e) { 1333 return null; 1334 } 1335 }; 1336 1337 const getViewportThresholds = () => { 1338 const vh = window.innerHeight; 1339 const isMobile = window.innerWidth < 768; 1340 1341 return { 1342 position: [[vh * 0.6, 50], [vh * 1.2, 30], [vh * 2.0, 10]], 1343 fontSize: [ 1344 [isMobile ? 18 : 24, 40], 1345 [isMobile ? 14 : 18, 25], 1346 [isMobile ? 12 : 14, 10], 1347 ], 1348 proximity: { 1349 close: isMobile ? 60 : 100, 1350 medium: isMobile ? 120 : 200, 1351 far: isMobile ? 240 : 400, 1352 }, 1353 }; 1354 }; 1355 1356 const getTieredScore = (value, tiers, defaultScore = 0) => { 1357 for (const [threshold, points] of tiers) { 1358 if (value < threshold) return points; 1359 } 1360 return defaultScore; 1361 }; 1362 1363 // Scores a DOM element on how likely it is to be the product's main price. 1364 // Higher score = better candidate. Factors: 1365 // - Position on page (elements higher up score better) 1366 // - Font size (larger text = more likely a displayed price) 1367 // - Proximity to product title and add-to-cart button 1368 // - Structural exclusions (footer/header/nav/hidden = heavy penalty) 1369 // - Strikethrough detection (compare-at prices = heavy penalty) 1370 const scoreElement = (element, productMeta = null, debug = false) => { 1371 let score = 0; 1372 const breakdown = debug ? {} : null; 1373 const { position, fontSize, proximity } = getViewportThresholds(); 1374 const { close, medium, far } = proximity; 1375 const rect = element.getBoundingClientRect(); 1376 1377 // POSITION ON PAGE 1378 const absoluteTop = rect.top + window.scrollY; 1379 const posScore = getTieredScore(absoluteTop, position, -30); 1380 score += posScore; 1381 if (debug) breakdown.position = `${posScore >= 0 ? '+' : ''}${posScore} (top ${Math.round(absoluteTop)}px)`; 1382 1383 // FONT SIZE 1384 const fontPx = parseFloat(getComputedStyle(element).fontSize); 1385 const fontScore = fontSize.find(([t]) => fontPx >= t)?.[1] ?? -10; 1386 score += fontScore; 1387 if (debug) breakdown.fontSize = `${fontScore >= 0 ? '+' : ''}${fontScore} (${fontPx}px)`; 1388 1389 // PROXIMITY TO PRODUCT TITLE 1390 if (productMeta?.title) { 1391 const titleElement = findElementContainingText(productMeta.title); 1392 if (titleElement) { 1393 const distance = Math.abs(titleElement.getBoundingClientRect().bottom - rect.top); 1394 const titleScore = getTieredScore(distance, [[close, 40], [medium, 25], [far, 10]], 0); 1395 score += titleScore; 1396 if (debug) breakdown.titleProximity = `+${titleScore} (${Math.round(distance)}px)`; 1397 } else if (debug) { 1398 breakdown.titleProximity = 'title element not found'; 1399 } 1400 } else if (debug) { 1401 breakdown.titleProximity = 'no product title'; 1402 } 1403 1404 // PROXIMITY TO ADD-TO-CART BUTTON 1405 const addButton = findAddToCartButton(); 1406 if (addButton) { 1407 const distance = Math.abs(addButton.getBoundingClientRect().top - rect.top); 1408 const cartScore = getTieredScore(distance, [[close, 30], [medium, 20], [far, 10]], 0); 1409 score += cartScore; 1410 if (debug) breakdown.cartProximity = `+${cartScore} (${Math.round(distance)}px)`; 1411 } else if (debug) { 1412 breakdown.cartProximity = 'add-to-cart not found'; 1413 } 1414 1415 // STRUCTURAL EXCLUSIONS 1416 const exclusions = [ 1417 ['footer, header, nav', 'in footer/header/nav'], 1418 ['button, a[href*="/cart"], [type="submit"]', 'in button/link'], 1419 ['[aria-hidden="true"], [hidden], .hidden, .visually-hidden', 'hidden'], 1420 ]; 1421 for (const [selector, reason] of exclusions) { 1422 if (element.closest(selector)) { 1423 score -= 100; 1424 if (debug) breakdown.structural = `-100 (${reason})`; 1425 break; 1426 } 1427 } 1428 if (debug && !breakdown.structural) breakdown.structural = '+0'; 1429 1430 // STRIKETHROUGH 1431 if (hasStrikethrough(element)) { 1432 score -= 100; 1433 if (debug) breakdown.strikethrough = '-100 (compare-at price)'; 1434 } else if (debug) { 1435 breakdown.strikethrough = '+0'; 1436 } 1437 1438 if (debug) { 1439 breakdown.totalScore = score; 1440 breakdown.elementText = element.textContent.trim().substring(0, 30); 1441 breakdown.elementTag = element.tagName; 1442 } 1443 1444 return debug ? { score, breakdown } : score; 1445 }; 1446 1447 // Find all elements containing a specific price value 1448 const findElementsWithPrice = (knownPriceInCents, productMeta = null, debug = false, log = () => {}) => { 1449 const candidates = []; 1450 const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT); 1451 1452 let node; 1453 while (node = walker.nextNode()) { 1454 const text = node.textContent.trim(); 1455 if (text.length > 30 || !textMatchesPrice(text, knownPriceInCents)) continue; 1456 1457 const element = node.parentElement; 1458 const rect = element.getBoundingClientRect(); 1459 1460 if (rect.width === 0 || rect.height === 0 || hasStrikethrough(element)) continue; 1461 1462 const score = scoreElement(element, productMeta, false); 1463 candidates.push({ element, text, score }); 1464 } 1465 1466 if (debug && candidates.length > 0) { 1467 candidates.sort((a, b) => b.score - a.score); 1468 log(` Candidates: ${candidates.length}, top: score=${candidates[0].score}, text="${candidates[0].text}"`); 1469 } 1470 1471 return candidates; 1472 }; 1473 1474 // Find the /cart/add form by traversing from the price element 1475 // This is more reliable than name-based container detection since all Shopify themes have this form 1476 const findCartFormFromPrice = (priceElement, debug = false, log = () => {}) => { 1477 let el = priceElement; 1478 1479 while (el && el !== document.body) { 1480 if (el.tagName === 'FORM' && el.action?.includes('/cart/add')) { 1481 if (debug) log(` Found /cart/add form (element itself)`); 1482 return el; 1483 } 1484 1485 let sibling = el.nextElementSibling; 1486 while (sibling) { 1487 if (sibling.tagName === 'FORM' && sibling.action?.includes('/cart/add')) { 1488 if (debug) log(` Found /cart/add form as sibling`); 1489 return sibling; 1490 } 1491 // Check if sibling contains the form 1492 const formInSibling = sibling.querySelector('form[action*="/cart/add"]'); 1493 if (formInSibling) { 1494 if (debug) log(` Found /cart/add form inside sibling`); 1495 return formInSibling; 1496 } 1497 sibling = sibling.nextElementSibling; 1498 } 1499 1500 el = el.parentElement; 1501 } 1502 1503 if (debug) log(` No /cart/add form found`); 1504 return null; 1505 }; 1506 1507 // Fallback: walk up from the price text element to find a suitable wrapper. 1508 // Used when no /cart/add form exists near the price element. 1509 const findPriceContainer = (priceElement) => { 1510 let el = priceElement; 1511 let bestContainer = priceElement; 1512 1513 while (el && el !== document.body) { 1514 // Look for elements with price-related IDs (common Shopify pattern) 1515 if (el.id && /price/i.test(el.id)) { 1516 bestContainer = el; 1517 } 1518 // Look for elements with price-related classes that seem like containers 1519 if (el.className && typeof el.className === 'string') { 1520 const classes = el.className.toLowerCase(); 1521 if (/\bprice\b/.test(classes) && !/price-item|price__regular|price__sale/.test(classes)) { 1522 bestContainer = el; 1523 } 1524 } 1525 // Stop at major structural elements 1526 if (el.tagName === 'SECTION' || el.tagName === 'ARTICLE' || el.tagName === 'MAIN') { 1527 break; 1528 } 1529 el = el.parentElement; 1530 } 1531 1532 return bestContainer; 1533 }; 1534 1535 // Try common Shopify theme price selectors (Dawn and derivatives) 1536 const findPriceByTemplateSelector = (debug = false, log = () => {}) => { 1537 const selectors = [ 1538 '[id*="price-template"]', // Dawn theme pattern 1539 '.price-item--regular:not(s):not(del)',
1540 '[data-product-price]', 1541 ]; 1542 1543 for (const selector of selectors) { 1544 const el = document.querySelector(selector); 1545 if (el && !hasStrikethrough(el)) { 1546 if (debug) log(`Template selector found: ${selector}`); 1547 return el; 1548 } 1549 } 1550 return null; 1551 }; 1552 1553 // Check if we're in an isolated/sandboxed iframe (theme editor) 1554 const isIsolatedContext = () => { 1555 if (window === window.top) return false; // Not in iframe 1556 try { 1557 // Try to access parent - if cross-origin, we're isolated 1558 const test = window.parent.document; 1559 return false; // Can access parent, not isolated 1560 } catch (e) { 1561 return true; // Cross-origin, we're isolated 1562 } 1563 }; 1564 1565 // Try to find price element in sibling iframes (theme editor workaround) 1566 const findPriceInSiblingIframes = (knownPriceInCents) => { 1567 const iframes = document.querySelectorAll('iframe'); 1568 1569 for (const iframe of iframes) { 1570 try { 1571 const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document; 1572 if (!iframeDoc) continue; 1573 1574 const priceElement = iframeDoc.querySelector('.price-item, .price, [data-product-price]'); 1575 if (priceElement) { 1576 return { element: priceElement, document: iframeDoc }; 1577 } 1578 } catch (e) { 1579 // Cross-origin iframe, skip 1580 } 1581 } 1582 1583 return null; 1584 }; 1585 1586 // Helper: Insert messaging near a price element (before cart form or after price container). 1587 // Returns the price container element used as the logical anchor. 1588 const insertMessagingNearPrice = (priceElement, messaging, debug = false, log = () => {}) => { 1589 const cartForm = findCartFormFromPrice(priceElement, debug, log); 1590 const container = findPriceContainer(priceElement); 1591 if (cartForm) { 1592 cartForm.before(messaging.cloneNode()); 1593 if (debug) log('Inserted messaging before /cart/add form'); 1594 } else { 1595 container.after(messaging.cloneNode()); 1596 if (debug) log('Inserted after price container (no form found)'); 1597 } 1598 return container; 1599 }; 1600 1601 // Insert messaging after price element 1602 const insertMessagingWithRetry = (shopifyData, messaging, debug = false, log = () => {}) => { 1603 // Skip visual preview iframe 1604 if (window.location.search.includes('source=visualPreviewInitialLoad')) return; 1605 if (document.querySelector('#paypal-upstream-messaging-product')) return; 1606 1607 const liquidPriceInCents = shopifyData?.price?.priceInCents; 1608 const isIsolated = isIsolatedContext(); 1609 1610 if (debug) { 1611 log('=== Price Detection ==='); 1612 log(`Context: ${isIsolated ? 'Theme Editor' : 'Live Storefront'}`); 1613 log(`Price: ${liquidPriceInCents} cents ($${(liquidPriceInCents / 100).toFixed(2)})`); 1614 } 1615 1616 // Priority 1: Template selectors (common theme patterns like Dawn) 1617 const templateElement = findPriceByTemplateSelector(debug, log); 1618 if (templateElement) { 1619 return insertMessagingNearPrice(templateElement, messaging, debug, log); 1620 } 1621 1622 // Priority 2: Scoring-based price detection 1623 const priceElement = findPriceElement(shopifyData, debug, log); 1624 if (priceElement) { 1625 return insertMessagingNearPrice(priceElement, messaging, debug, log); 1626 } 1627 1628 // Priority 3: Sibling iframes (theme editor only) 1629 if (isIsolated) { 1630 const siblingResult = findPriceInSiblingIframes(liquidPriceInCents); 1631 if (siblingResult) { 1632 return insertMessagingNearPrice(siblingResult.element, messaging, debug, log); 1633 } 1634 } 1635 1636 if (!isIsolated) { 1637 log('Could not find price element. Consider using Advanced Settings with a custom selector.'); 1638 } 1639 return null; 1640 }; 1641 1642 const findPriceElement = (shopifyData = null, debug = false, log = () => {}) => { 1643 try { 1644 const knownPriceInCents = shopifyData?.price?.priceInCents; 1645 const productMeta = shopifyData?.product; 1646 1647 if (debug) log(`findPriceElement: ${knownPriceInCents} cents`); 1648 1649 if (knownPriceInCents) { 1650 const candidates = findElementsWithPrice(knownPriceInCents, productMeta, debug, log); 1651 if (candidates.length > 0) { 1652 candidates.sort((a, b) => b.score - a.score); 1653 if (debug) log(` â FOUND (score=${candidates[0].score})`); 1654 return candidates[0].element; 1655 } 1656 } 1657 1658 if (debug) log(' â null (no candidates)'); 1659 return null; 1660 } catch (e) { 1661 if (debug) log(` â Error: ${e.message}`); 1662 recordEvent('appEmbed:findPriceElement:error', { error: e.message }); 1663 return null; 1664 } 1665 }; 1666</script>
1666 1667<!-- END app snippet --><!-- BEGIN app snippet: navigator-panel --><template id="pp-nav-template"> 1668 <style> 1669 /* --- Floating placement panel shell --- */ 1670 #pp-nav-panel { 1671 position: fixed; 1672 top: 50%; 1673 left: 20px; 1674 transform: translateY(-50%); 1675 background: #fff; 1676 border-radius: 12px; 1677 font-family: system-ui, -apple-system, sans-serif; 1678 font-size: 13px; 1679 z-index: 999999; 1680 width: 340px; 1681 box-shadow: 0 8px 32px rgba(0,0,0,0.18); 1682 overflow: hidden; 1683 transition: transform 0.3s ease, height 0.3s ease; 1684 } 1685 #pp-nav-panel.minimized { 1686 display: none; 1687 } 1688 1689 /* --- Minimized pill button --- */ 1690 #pp-nav-pill { 1691 position: fixed; 1692 bottom: 20px; 1693 left: 20px; 1694 background: #003087; 1695 color: #fff; 1696 border: none; 1697 border-radius: 24px; 1698 padding: 10px 18px; 1699 font-family: system-ui, -apple-system, sans-serif; 1700 font-size: 13px; 1701 font-weight: 600; 1702 cursor: pointer; 1703 z-index: 999999; 1704 box-shadow: 0 4px 16px rgba(0,48,135,0.3); 1705 display: none; 1706 align-items: center; 1707 gap: 8px; 1708 transition: box-shadow 0.2s; 1709 } 1710 #pp-nav-pill:hover { 1711 box-shadow: 0 6px 20px rgba(0,48,135,0.45); 1712 } 1713 1714 /* --- Header --- */ 1715 .pp-panel-header { 1716 background: #003087; 1717 color: #fff; 1718 padding: 14px 16px; 1719 display: flex; 1720 align-items: center; 1721 justify-content: space-between; 1722 } 1723 .pp-panel-title { 1724 font-size: 14px; 1725 font-weight: 700; 1726 margin: 0; 1727 letter-spacing: 0.01em; 1728 } 1729 .pp-minimize-btn { 1730 background: none; 1731 border: none; 1732 color: rgba(255,255,255,0.8); 1733 cursor: pointer; 1734 font-size: 18px; 1735 line-height: 1; 1736 padding: 2px; 1737 border-radius: 4px; 1738 transition: color 0.15s; 1739 } 1740 .pp-minimize-btn:hover { 1741 color: #fff; 1742 } 1743 1744 /* --- Panel body --- */ 1745 .pp-panel-content { 1746 padding: 16px; 1747 } 1748 1749 /* --- Step info boxes --- */ 1750 .pp-step-box { 1751 border-radius: 8px; 1752 padding: 10px 12px; 1753 font-size: 12px; 1754 line-height: 1.5; 1755 margin-bottom: 12px; 1756 display: flex; 1757 align-items: flex-start; 1758 gap: 8px; 1759 } 1760 .pp-step-box.blue { 1761 background: #e8f4fd; 1762 border: 1px solid #b8dff5; 1763 color: #0a5a96; 1764 } 1765 .pp-step-box.green { 1766 background: #e6f9ee; 1767 border: 1px solid #a3e4b8; 1768 color: #1a7a3a; 1769 } 1770 .pp-step-text { 1771 flex: 1; 1772 } 1773 .pp-step-text p { 1774 margin: 0 0 8px 0; 1775 } 1776 .pp-step-text p:last-child { 1777 margin-bottom: 0; 1778 } 1779 .pp-step-text strong { 1780 display: block; 1781 font-size: 12px; 1782 margin-bottom: 2px; 1783 } 1784 .pp-step-dismiss { 1785 background: none; 1786 border: none; 1787 color: inherit; 1788 opacity: 0.5; 1789 cursor: pointer; 1790 font-size: 16px; 1791 line-height: 1; 1792 padding: 0; 1793 flex-shrink: 0; 1794 } 1795 .pp-step-dismiss:hover { 1796 opacity: 1; 1797 } 1798 1799 /* --- Warning banners --- */ 1800 .pp-warning { 1801 background: #fff3cd; 1802 border: 1px solid #ffc107; 1803 border-radius: 8px; 1804 padding: 10px 12px; 1805 margin-bottom: 12px; 1806 font-size: 12px; 1807 color: #856404; 1808 line-height: 1.4; 1809 } 1810 .pp-warning-icon { 1811 margin-right: 4px; 1812 } 1813 1814 /* --- Pick placement + copy actions --- */ 1815 .pp-pick-btn { 1816 width: 100%; 1817 padding: 12px; 1818 border: 2px solid #0070ba; 1819 border-radius: 8px; 1820 background: #0070ba; 1821 color: #fff; 1822 cursor: pointer; 1823 font-size: 13px; 1824 font-weight: 600; 1825 transition: all 0.15s; 1826 display: flex; 1827 align-items: center; 1828 justify-content: center; 1829 gap: 8px; 1830 } 1831 .pp-pick-btn:hover { 1832 opacity: 0.9; 1833 } 1834 .pp-pick-btn.active { 1835 background: #f0f7ff; 1836 color: #0070ba; 1837 border-color: #b8dff5; 1838 } 1839 .pp-pick-btn.active:hover { 1840 background: #e0efff; 1841 } 1842 1843 /* --- Copy button --- */ 1844 #pp-apply-btn { 1845 width: 100%; 1846 background: #28a745; 1847 color: #fff; 1848 border: none; 1849 padding: 12px; 1850 border-radius: 8px; 1851 font-weight: 600; 1852 cursor: pointer; 1853 font-size: 13px; 1854 transition: all 0.2s; 1855 display: none; 1856 align-items: center; 1857 justify-content: center; 1858 gap: 8px; 1859 margin-top: 12px; 1860 } 1861 #pp-apply-btn:hover { 1862 opacity: 0.9; 1863 } 1864 #pp-apply-btn.applied { 1865 background: #28a745; 1866 } 1867 1868 /* --- Step 2 confirmation --- */ 1869 #pp-step2 { 1870 display: none; 1871 margin-top: 18px; 1872 } 1873 </style> 1874 <div class='pp-panel-header'> 1875 <div class='pp-panel-title'>PayPal Pay Later Messaging</div> 1876 <button class='pp-minimize-btn' id='pp-collapse-btn' title='Minimize panel'>▾</button> 1877 </div> 1878 <div class='pp-panel-content'> 1879 <div id='pp-warning' class='pp-warning' style='display: none;'></div> 1880 <div id='pp-inspector-warning' class='pp-warning' style='display: none;'></div> 1881 <div class='pp-step-box blue' id='pp-step1'>
1882 <span class='pp-step-text' 1883 ><strong>Step 1: Choose message placement</strong> 1884 Click the button below and then hover over your page to select a placement. 1885 </span> 1886 </div> 1887 <button id='pp-pick-btn' class='pp-pick-btn'>◎ Start Picking Placement</button 1888 ><button id='pp-apply-btn'>Copy Current Placement</button> 1889 <div id='pp-step2' style='display:none;'> 1890 <div class='pp-step-box green'> 1891 <span class='pp-step-text' 1892 ><strong>Step 2: Paste and save</strong> 1893 <p style='margin: 0 0 8px 0;'> 1894 You have copied the placement for the <b id='pp-context-name'>Product Page</b>. 1895 </p> 1896 <p style='margin: 0;'> 1897 Go back to the App Embeds section, and in the Paypal Advanced Settings, paste the copied value into the 1898 <b id='pp-selector-name'>Product Page</b> field and click the 'Save' button in the top right corner. 1899 </p></span 1900 ><button class='pp-step-dismiss' id='pp-step2-dismiss' title='Dismiss'>×</button> 1901 </div> 1902 </div> 1903 </div> 1904</template> 1905<!-- END app snippet --><!-- BEGIN app snippet: navigator -->
1905<script> 1906 // ============================================ 1907 // DOM Traversal Controls for Theme Editor 1908 // Depends on: price-detection.liquid (isIsolatedContext) 1909 // ============================================ 1910 1911 const OVERLAY_STYLE = 1912 'position:fixed;pointer-events:none;background:rgba(255,152,0,0.12);border:2px solid rgba(255,152,0,0.6);z-index:999998;display:none;'; 1913 const OVERLAY_LABEL_STYLE = 1914 'position:absolute;bottom:100%;left:-2px;background:#f57c00;color:#fff;font:10px system-ui;padding:2px 6px;border-radius:3px 3px 0 0;white-space:nowrap;'; 1915 const COPY_BUTTON_LABEL = 'Copy Current Placement'; 1916 const COPIED_BUTTON_LABEL = 'Placement Copied!'; 1917 1918 const isInOverlay = (el) => { 1919 while (el && el !== document.body) { 1920 if (getComputedStyle(el).position === 'fixed') return true; 1921 el = el.parentElement; 1922 } 1923 return false; 1924 }; 1925 1926 // Position a fixed overlay to cover a target element 1927 const positionOverlayOnElement = (overlay, el) => { 1928 const rect = el.getBoundingClientRect(); 1929 Object.assign(overlay.style, { 1930 top: rect.top + 'px', 1931 left: rect.left + 'px', 1932 width: rect.width + 'px', 1933 height: rect.height + 'px', 1934 display: 'block', 1935 }); 1936 }; 1937 1938 // Module-level refs for global listeners added by createNavigationPanel. 1939 // Stored here so they can be removed if the panel is re-initialized. 1940 let _panelInspectorActivate = null; 1941 let _panelInspectorDeactivate = null; 1942 let _highlightedElement = null; 1943 let _panelPickModeCleanup = null; 1944 1945 // Detect UID pattern in ID (Shopify's --suffix or long numeric/hex suffixes) 1946 const hasUid = (id) => { 1947 if (!id) return false; 1948 if (/--/.test(id)) return true; 1949 return /[-_](\d{5,}|[a-f0-9]{8,})$/i.test(id); 1950 }; 1951 const getIdPrefix = (id) => { 1952 if (id.includes('--')) return id.split('--')[0]; 1953 const match = id.match(/^(.+?)[-_](\d{5,}|[a-f0-9]{8,})$/i); 1954 return match ? match[1] : null; 1955 }; 1956 const getMeaningfulClasses = (el) => { 1957 if (!el.className || typeof el.className !== 'string') return []; 1958 return el.className.trim().split(/\s+/).filter(c => 1959 c && !c.startsWith('_') && !c.startsWith('js-') && 1960 c.length < 30 && !/^[a-f0-9]{6,}$/i.test(c) && !/^[A-Z]/.test(c) 1961 ); 1962 }; 1963 const isUnique = (selector) => document.querySelectorAll(selector).length === 1; 1964 1965 // Returns null if no unique selector can be generated 1966 const generateSelector = (element) => { 1967 if (!element || element === document.body || element === document.documentElement) { 1968 return null; 1969 } 1970 const classes = getMeaningfulClasses(element); 1971 1972 // Priority 1: Clean ID 1973 if (element.id && !hasUid(element.id)) return `#${element.id}`; 1974 1975 // Priority 2: ID with UID â attribute wildcard selector 1976 if (element.id && hasUid(element.id)) { 1977 const prefix = getIdPrefix(element.id); 1978 if (prefix) { 1979 const selector = `[id^="${prefix}-"]`; 1980 if (isUnique(selector)) return selector; 1981 } 1982 } 1983 1984 // Priority 3: Unique class 1985 for (const cls of classes) { 1986 if (isUnique('.' + cls)) return '.' + cls; 1987 } 1988 1989 // Priority 4: Ancestor + target class combinations (walk up to 5 levels) 1990 if (classes.length > 0) { 1991 let ancestor = element.parentElement; 1992 let depth = 0; 1993 while (ancestor && ancestor !== document.body && ancestor !== document.documentElement && depth < 5) { 1994 const ancClasses = getMeaningfulClasses(ancestor); 1995 if (ancClasses.length > 0) { 1996 const s1 = `.${ancClasses[0]} .${classes[0]}`; 1997 if (isUnique(s1)) return s1; 1998 const s2 = `.${ancClasses[0]} > .${classes[0]}`; 1999 if (isUnique(s2)) return s2; 2000 } 2001 if (ancestor.id && !hasUid(ancestor.id)) { 2002 const s3 = `#${ancestor.id} .${classes[0]}`; 2003 if (isUnique(s3)) return s3; 2004 } 2005 ancestor = ancestor.parentElement; 2006 depth++; 2007 } 2008 } 2009 2010 // NO FALLBACK - return null if no unique selector exists 2011 return null; 2012 }; 2013 const hasSpecificSelector = (el) => generateSelector(el) !== null; 2014 const shouldSkipElement = (el) => { 2015 if (!el || el.hidden) return true; 2016 if (['SCRIPT', 'STYLE', 'TEMPLATE'].includes(el.tagName)) return true; 2017 if ( 2018 el.closest('#pp-nav-panel') || 2019 el.closest('#pp-nav-pill') || 2020 el.closest('#pp-pick-overlay') 2021 ) 2022 return true; 2023 if (el.hasAttribute('data-pp-message')) return true; 2024 if (!hasSpecificSelector(el)) return true; 2025 return false;
2026 }; 2027 const resolveContextName = (providedContextName, messagingTemplate) => { 2028 if (providedContextName && providedContextName !== 'this area') return providedContextName; 2029 const id = messagingTemplate?.id || ''; 2030 if (id.includes('cart-drawer')) return 'Cart Drawer'; 2031 if (id.includes('cart')) return 'Cart Page'; 2032 if (id.includes('product')) return 'Product Page'; 2033 return 'this area'; 2034 }; 2035 2036 // Called when the cart drawer closes (via IntersectionObserver in cartDrawer.liquid) 2037 // and at the start of createNavigationPanel to tear down any existing instance. 2038 // Cleans up pick mode listeners, DOM elements, highlights, and preview clones. 2039 const destroyNavigationPanel = () => { 2040 if (_panelPickModeCleanup) { 2041 _panelPickModeCleanup(); 2042 _panelPickModeCleanup = null; 2043 } 2044 document.getElementById('pp-nav-panel')?.remove(); 2045 document.getElementById('pp-nav-pill')?.remove(); 2046 const preview = document.querySelector('[id$="-preview"][data-pp-message]'); 2047 if (preview) preview.remove(); 2048 if (_highlightedElement) { 2049 _highlightedElement.style.outline = ''; 2050 _highlightedElement.style.outlineOffset = ''; 2051 _highlightedElement = null; 2052 } 2053 if (_panelInspectorActivate) { 2054 document.removeEventListener('shopify:inspector:activate', _panelInspectorActivate); 2055 document.removeEventListener('shopify:inspector:deactivate', _panelInspectorDeactivate); 2056 _panelInspectorActivate = _panelInspectorDeactivate = null; 2057 } 2058 document.documentElement.style.cursor = ''; 2059 }; 2060 2061 const createNavigationPanel = ( 2062 initialElement, 2063 messagingTemplate, 2064 warningMessage = null, 2065 contextName = 'this area', 2066 ) => {
2067 let currentTarget = initialElement; 2068 let resolvedContextName = resolveContextName(contextName, messagingTemplate); 2069 destroyNavigationPanel(); 2070 2071 const panel = document.createElement('div'); 2072 panel.id = 'pp-nav-panel'; 2073 const tmpl = document.getElementById('pp-nav-template'); 2074 panel.appendChild(tmpl.content.cloneNode(true)); 2075 2076 // Create minimized pill button (separate from panel) 2077 const pill = document.createElement('button'); 2078 pill.id = 'pp-nav-pill'; 2079 pill.innerHTML = 'PayPal Pay Later Messaging'; 2080 const applyBtn = panel.querySelector('#pp-apply-btn'); 2081 const warningEl = panel.querySelector('#pp-warning'); 2082 const step2 = panel.querySelector('#pp-step2'); 2083 const step2Dismiss = panel.querySelector('#pp-step2-dismiss'); 2084 const contextEl = panel.querySelector('#pp-context-name'); 2085 const selectorEl = panel.querySelector('#pp-selector-name'); 2086 const updateContextUI = (newContext) => { 2087 resolvedContextName = newContext; 2088 if (contextEl) contextEl.textContent = resolvedContextName; 2089 if (selectorEl) { 2090 selectorEl.textContent = resolvedContextName; 2091 } 2092 }; 2093 updateContextUI(resolvedContextName); 2094 const detectContextFromElement = (el) => { 2095 if (el.closest('#CartDrawer, .drawer, .cart-drawer, [id*="cart-drawer"], [class*="cart-drawer"]')) 2096 return 'Cart Drawer'; 2097 if (window.location.pathname.endsWith('/cart') || el.closest('.cart, #cart, #main-cart-items, #main-cart-footer')) 2098 return 'Cart Page'; 2099 return 'Product Page'; 2100 }; 2101 2102 // Show warning message if provided 2103 if (warningMessage && warningEl) { 2104 warningEl.innerHTML = `<span class="pp-warning-icon">â ï¸</span>${warningMessage}`; 2105 warningEl.style.display = 'block'; 2106 } 2107 let isInitialRender = true; 2108 const updateUI = () => { 2109 // Highlight element 2110 highlightElement(currentTarget); 2111 // Update button states 2112 applyBtn.textContent = COPY_BUTTON_LABEL; 2113 applyBtn.classList.remove('applied'); 2114 // Reposition messaging â skip on initial render (already placed correctly by auto-detection) 2115 if (!isInitialRender) repositionMessaging(); 2116 isInitialRender = false;
2117 }; 2118 2119 const highlightElement = (el) => { 2120 if (_highlightedElement) { 2121 _highlightedElement.style.outline = ''; 2122 _highlightedElement.style.outlineOffset = ''; 2123 } 2124 _highlightedElement = el; 2125 if (el) { 2126 el.style.outline = '2px solid rgba(255,152,0,0.6)'; 2127 el.style.outlineOffset = '-1px'; 2128 } 2129 }; 2130 2131 const repositionMessaging = () => { 2132 if (!currentTarget) return; 2133 const existing = document.getElementById(messagingTemplate.id); 2134 const targetInOverlay = isInOverlay(currentTarget); 2135 const msgInOverlay = existing && isInOverlay(existing); 2136 if (targetInOverlay !== msgInOverlay) { 2137 // Cross-context: reposition messaging in the target's context without touching the original 2138 const allMessaging = document.querySelectorAll('[data-pp-message]'); 2139 let contextMsg = null; 2140 for (const el of allMessaging) { 2141 if (isInOverlay(el) === targetInOverlay) { contextMsg = el; break; } 2142 } 2143 if (!contextMsg) { 2144 contextMsg = messagingTemplate.cloneNode(true); 2145 contextMsg.id = messagingTemplate.id + '-preview'; 2146 } 2147 contextMsg.remove(); 2148 currentTarget.after(contextMsg); 2149 return; 2150 } 2151 if (existing) existing.remove(); 2152 const newMessaging = messagingTemplate.cloneNode(true); 2153 currentTarget.after(newMessaging); 2154 }; 2155 2156 const collapseBtn = panel.querySelector('#pp-collapse-btn'); 2157 const pickBtn = panel.querySelector('#pp-pick-btn'); 2158 let pickMode = false; 2159 let pickHoverTarget = null; 2160 let pickOverlay = null; 2161 let pickLabel = null; 2162 let applyBtnResetTimer = null; 2163 let _pickRafId = null; 2164 2165 const enterPickMode = () => { 2166 pickMode = true; 2167 document.documentElement.style.cursor = 'crosshair'; 2168 pickBtn.classList.add('active'); 2169 pickBtn.innerHTML = 'â Pick a spot on the page...'; 2170 applyBtn.textContent = COPY_BUTTON_LABEL; 2171 applyBtn.classList.remove('applied'); 2172 applyBtn.style.display = 'flex'; 2173 step2.style.display = 'none'; 2174 if (contextEl) contextEl.textContent = resolvedContextName; 2175 // Create overlay for highlighting (pointer-events:none prevents interference) 2176 pickOverlay = document.createElement('div'); 2177 pickOverlay.id = 'pp-pick-overlay'; 2178 pickOverlay.style.cssText = OVERLAY_STYLE; 2179 pickLabel = document.createElement('div'); 2180 pickLabel.style.cssText = OVERLAY_LABEL_STYLE; 2181 pickOverlay.appendChild(pickLabel); 2182 document.body.appendChild(pickOverlay); 2183 2184 document.addEventListener('mousemove', onPickHover, true); 2185 document.addEventListener('click', onPickClick, true); 2186 document.addEventListener('mousedown', onPickSuppressEvent, true); 2187 document.addEventListener('pointerdown', onPickSuppressEvent, true); 2188 }; 2189 2190 const exitPickMode = () => { 2191 pickMode = false; 2192 document.documentElement.style.cursor = ''; 2193 pickBtn.classList.remove('active'); 2194 pickBtn.innerHTML = 'â Start Picking Placement'; 2195 applyBtn.style.display = 'none'; 2196 // Remove overlay 2197 const overlay = document.getElementById('pp-pick-overlay'); 2198 if (overlay) overlay.remove(); 2199 pickOverlay = null; 2200 pickLabel = null; 2201 pickHoverTarget = null; 2202 2203 if (_pickRafId) { cancelAnimationFrame(_pickRafId); _pickRafId = null; } 2204 document.removeEventListener('mousemove', onPickHover, true); 2205 document.removeEventListener('click', onPickClick, true); 2206 document.removeEventListener('mousedown', onPickSuppressEvent, true); 2207 document.removeEventListener('pointerdown', onPickSuppressEvent, true); 2208 }; 2209 2210 // Find the nearest visible child element to cursor coordinates 2211 const snapToNearestChild = (parent, cx, cy) => { 2212 const children = parent.children; 2213 if (!children.length) return null; 2214 let best = null; 2215 let bestDist = 30; 2216 for (const child of children) { 2217 if (shouldSkipElement(child)) continue; 2218 const r = child.getBoundingClientRect(); 2219 if (r.width === 0 || r.height === 0) continue; 2220 const dx = cx < r.left ? r.left - cx : cx > r.right ? cx - r.right : 0; 2221 const dy = cy < r.top ? r.top - cy : cy > r.bottom ? cy - r.bottom : 0; 2222 const dist = Math.sqrt(dx * dx + dy * dy); 2223 if (dist < bestDist) { 2224 bestDist = dist; 2225 best = child; 2226 } 2227 } 2228 return best; 2229 }; 2230 2231 const onPickHover = (e) => { 2232 if (_pickRafId) return; 2233 _pickRafId = requestAnimationFrame(() => { 2234 _pickRafId = null; 2235 let el = document.elementFromPoint(e.clientX, e.clientY); 2236 if (!el || shouldSkipElement(el)) { 2237 document.documentElement.style.cursor = 'not-allowed'; 2238 if (pickOverlay) pickOverlay.style.display = 'none'; 2239 pickHoverTarget = null; 2240 return; 2241 } 2242 2243 if (el.children.length > 0) { 2244 const snapped = snapToNearestChild(el, e.clientX, e.clientY); 2245 if (snapped) el = snapped; 2246 } 2247 2248 // Skip elements without a unique selector 2249 if (shouldSkipElement(el)) { 2250 document.documentElement.style.cursor = 'not-allowed'; 2251 pickHoverTarget = null; 2252 if (pickOverlay) pickOverlay.style.display = 'none'; 2253 return; 2254 } 2255 document.documentElement.style.cursor = 'crosshair'; 2256 if (el === pickHoverTarget) return; 2257 pickHoverTarget = el; 2258 positionOverlayOnElement(pickOverlay, el);
2259 pickLabel.innerHTML = 'Select this placement'; 2260 }); 2261 }; 2262 2263 const onPickSuppressEvent = (e) => { 2264 if (e.target.closest('#pp-nav-panel')) return; 2265 e.preventDefault(); 2266 e.stopPropagation(); 2267 }; 2268 2269 const onPickClick = (e) => { 2270 if (e.target.closest('#pp-nav-panel')) return; 2271 e.preventDefault(); 2272 e.stopPropagation(); 2273 e.stopImmediatePropagation(); 2274 if (pickHoverTarget && !shouldSkipElement(pickHoverTarget)) { 2275 currentTarget = pickHoverTarget; 2276 updateContextUI(detectContextFromElement(currentTarget)); 2277 updateUI(); 2278 } 2279 }; 2280 2281 // --- Pick button --- 2282 pickBtn.addEventListener('click', () => { 2283 if (pickMode) exitPickMode(); 2284 else enterPickMode(); 2285 }); 2286 2287 _panelPickModeCleanup = () => { if (pickMode) exitPickMode(); }; 2288 2289 // --- Minimize / expand --- 2290 collapseBtn.addEventListener('click', () => { 2291 if (pickMode) exitPickMode(); 2292 panel.classList.add('minimized'); 2293 pill.style.display = 'flex'; 2294 highlightElement(null); 2295 }); 2296 pill.addEventListener('click', () => { 2297 panel.classList.remove('minimized'); 2298 pill.style.display = 'none'; 2299 highlightElement(currentTarget); 2300 }); 2301 2302 // --- Step 2 dismiss --- 2303 step2Dismiss.addEventListener('click', () => { 2304 step2.style.display = 'none'; 2305 }); 2306 2307 // Handle copy button click 2308 panel.addEventListener('click', async (e) => { 2309 const button = e.target.closest('#pp-apply-btn'); 2310 if (!button) return; 2311 // Skip buttons handled separately 2312 if (button.id === 'pp-collapse-btn' || button.id === 'pp-pick-btn' || button.id === 'pp-step2-dismiss') return; 2313 if (button.id === 'pp-apply-btn') { 2314 const selector = generateSelector(currentTarget); 2315 if (!selector) return; 2316 let copied = false; 2317 try { 2318 // Try multiple copy methods (iframe security blocks clipboard API) 2319 if (navigator.clipboard?.writeText) { 2320 await navigator.clipboard.writeText(selector); 2321 copied = true; 2322 } 2323 } catch {} 2324 if (!copied) { 2325 try { 2326 const ta = document.createElement('textarea'); 2327 ta.value = selector; 2328 ta.style.position = 'fixed'; 2329 ta.style.opacity = '0'; 2330 document.body.appendChild(ta); 2331 ta.select(); 2332 copied = document.execCommand('copy'); 2333 ta.remove(); 2334 } catch {} 2335 } 2336 if (copied) { 2337 applyBtn.textContent = COPIED_BUTTON_LABEL; 2338 applyBtn.classList.add('applied'); 2339 if (applyBtnResetTimer) clearTimeout(applyBtnResetTimer); 2340 applyBtnResetTimer = setTimeout(() => { 2341 applyBtn.textContent = COPY_BUTTON_LABEL; 2342 applyBtn.classList.remove('applied'); 2343 applyBtnResetTimer = null; 2344 }, 1200); 2345 // Show Step 2 confirmation 2346 step2.style.display = 'block'; 2347 } 2348 exitPickMode(); 2349 } 2350 }); 2351 2352 // --- Inspector mode detection --- 2353 const inspectorWarning = panel.querySelector('#pp-inspector-warning'); 2354 let inspectorActive = typeof Shopify !== 'undefined' && Shopify.inspectMode; 2355 const isMac = navigator.platform?.startsWith('Mac') || navigator.userAgentData?.platform === 'macOS'; 2356 const shortcut = isMac ? 'â â§ I' : 'Ctrl + Shift + I'; 2357 const enableInspectorMode = () => { 2358 inspectorActive = true; 2359 inspectorWarning.innerHTML = `<span class="pp-warning-icon">â ï¸</span> For a better experience, press <strong>${shortcut}</strong> to turn off inspector mode`; 2360 inspectorWarning.style.display = 'block'; 2361 }; 2362 const disableInspectorMode = () => { 2363 inspectorActive = false; 2364 inspectorWarning.style.display = 'none'; 2365 }; 2366 _panelInspectorActivate = enableInspectorMode; 2367 _panelInspectorDeactivate = disableInspectorMode; 2368 document.addEventListener('shopify:inspector:activate', enableInspectorMode); 2369 document.addEventListener('shopify:inspector:deactivate', disableInspectorMode); 2370 2371 // Prevent panel/pill clicks from closing cart drawer (themes close drawer on outside mousedown) 2372 const stopBubble = (e) => e.stopPropagation(); 2373 panel.addEventListener('mousedown', stopBubble); 2374 panel.addEventListener('pointerdown', stopBubble); 2375 pill.addEventListener('mousedown', stopBubble); 2376 pill.addEventListener('pointerdown', stopBubble); 2377 2378 document.body.appendChild(panel); 2379 document.body.appendChild(pill); 2380 if (inspectorActive) enableInspectorMode(); 2381 updateUI(); 2382 return panel; 2383 }; 2384</script>
2384 2385<!-- END app snippet --><!-- BEGIN app snippet: cartDrawer -->
2385<script> 2386 // Determine correct locator by theme name 2387 const cartDrawerThemeName = window.Shopify.theme.name; 2388 let cartDrawerLocator, cartDrawerNodeToObserve, cartDrawerMutationNode; 2389 2390 switch (cartDrawerThemeName) { 2391 // to support other themes add cases 2392 // default case supports Shopify Free themes 2393 default: 2394 cartDrawerLocator = '.cart-drawer__footer .totals'; 2395 cartDrawerNodeToObserve = '#CartDrawer'; 2396 cartDrawerMutationNode = 'drawer__inner'; 2397 break; 2398 } 2399 // Begin Upstream Messaging Implementation 2400 try { 2401 const cartDrawerMessaging = document.createElement('div'); 2402 const price = 0 / 100; 2403 2404 const cartDrawerAttributes = { 2405 'id': 'paypal-upstream-messaging-cart-drawer', 2406 'data-pp-message': '', 2407 'data-pp-placement': 'cart', 2408 'data-pp-style-layout': 'text', 2409 'data-pp-style-text-align': 'right', 2410 'data-pp-style-logo-type': "inline", 2411 'data-pp-style-text-color': "black", 2412 'data-pp-style-text-size': "12", 2413 'data-pp-style-logo-position': "", 2414 'data-pp-amount': price, 2415 }; 2416 2417 Object.keys(cartDrawerAttributes).forEach((key) => { 2418 cartDrawerMessaging.setAttribute(key, cartDrawerAttributes[key]); 2419 }); 2420 2421 const advancedSettingsEnabled = 'false' === 'true'; 2422 const customCartDrawerSelector = ''.trim(); 2423 2424 if (advancedSettingsEnabled && customCartDrawerSelector !== '') { 2425 // utils.liquid 2426 cartDrawerLocator = parseSelector(customCartDrawerSelector); 2427 } 2428 2429 if ("enabled" === 'enabled') { 2430 waitForElement(cartDrawerLocator).then(elem => { 2431 elem.after(cartDrawerMessaging.cloneNode()); 2432 2433 if (advancedSettingsEnabled && isIsolatedContext() && !document.getElementById('pp-nav-panel')) { 2434 let drawerPanelActive = false; 2435 new IntersectionObserver((entries) => { 2436 if (entries[0].isIntersecting) { 2437 if (!document.getElementById('pp-nav-panel')) { 2438 setTimeout(() => { 2439 if (!document.getElementById('pp-nav-panel')) { 2440 createNavigationPanel(elem, cartDrawerMessaging, null, 'Cart Drawer'); 2441 drawerPanelActive = true; 2442 } 2443 }, 300); 2444 } 2445 } else if (drawerPanelActive) { 2446 destroyNavigationPanel(); 2447 drawerPanelActive = false; 2448 } 2449 }, { threshold: 0.1 }).observe(elem); 2450 } 2451 }); 2452 2453 // Custom selector not found â show navigator with warning when drawer opens 2454 if (advancedSettingsEnabled && customCartDrawerSelector && !document.querySelector(cartDrawerLocator) && isIsolatedContext() && !document.getElementById('pp-nav-panel')) { 2455 const drawerContainer = document.querySelector(cartDrawerNodeToObserve); 2456 if (drawerContainer) { 2457 let warningPanelActive = false; 2458 const warning = 'Selected placement not found on this page. Please choose an element that is present on all page templates.'; 2459 recordEvent('appEmbed:advanced-settings-selector-not-found', { locator: cartDrawerLocator }); 2460 new IntersectionObserver((entries) => { 2461 if (entries[0].isIntersecting) { 2462 if (!document.getElementById('pp-nav-panel')) { 2463 setTimeout(() => { 2464 if (!document.getElementById('pp-nav-panel')) { 2465 createNavigationPanel(drawerContainer, cartDrawerMessaging, warning, 'Cart Drawer'); 2466 warningPanelActive = true; 2467 } 2468 }, 300); 2469 } 2470 } else if (warningPanelActive) { 2471 destroyNavigationPanel(); 2472 warningPanelActive = false; 2473 } 2474 }, { threshold: 0.1 }).observe(drawerContainer); 2475 } 2476 } 2477 2478 // utils.liquid 2479 watchForCartChanges(cartDrawerLocator, cartDrawerMessaging); 2480 } 2481 } catch (e) { 2482 // utils.liquid 2483 recordEvent('appEmbed:query-selector-not-found-cart-drawer', { locator: cartDrawerLocator, error: e.message }); 2484 } 2485 </script>
2485 2486<!-- END app snippet --> 2487 2488 2489</div><!-- Failed to render app block "4215564508307704909": app block path "shopify://apps/sitemap-noindex-pro-seo/blocks/app-embed/4d815e2c-5af0-46ba-8301-d0f9cf660031" does not exist --><div id="shopify-block-AbmZNMkJoZTRRcUIvb__10830403595879607806" class="shopify-block shopify-app-block"><style> 2490 .rio-withoutBranding .FloatingWidget .R-Logo, 2491 .rio-withoutBranding .CarouselWidget .R-ReviewsioLogo, 2492 .rio-withoutBranding .ElementsWidget .footer__reviewsLogo-container, 2493 .rio-withoutBranding .QnaWidget .footer__reviewsLogo-container, 2494 .rio-withoutBranding .MediaGalleryWidget-prefix .R-ReviewsioLogo, 2495 .rio-withoutBranding .RatingBarWidget .R-PlatformIcon { 2496 display: none !important; 2497 } 2498</style>
2499<script> 2500 window.__REVIEWS_EDITOR__ = false; 2501 window.__REVIEWS_DEMO_CONFIG__ = { 2502 store: 'ecommerce-demo-store', 2503 sku: 'bunch_of_flowers_sku;blue_flowers_sku;white_flowers_sku;pastel_flowers_sku;summer_selection_sku', 2504 widgetIds: { 2505 nuggets: 'W10cpKyjl72VtHv6', 2506 ugc: '7sG9C5qNeKpvhykZ', 2507 floating: 'GBvqmKV1Zy9H4Mpd', 2508 ratingBar: 'pxNMXi7FTlODgr8Q', 2509 survey: 'uLJcD7bFpGMf1Z0C' 2510 }, 2511 surveyCampaignId: '392' 2512 }; 2513 2514 2515 var reviewsIoStore = 'balkonkraftwerk-express.de'; 2516 2517 2518 var reviewsIoEnableSyndication = false; 2519 2520 2521 var reviewsIoLang = 'de'; 2522 2523</script>
2523 2524
2525<script defer src="https://cdn.shopify.com/extensions/01a0f781-40b0-734a-a579-a6ab0f7a7741/reviews-co-uk-product-and-merchant-review-collection-60/assets/reviewsio-preview.js"></script>
2525 2526 2527 2528 2529</div><!-- Failed to render app block "15154131639906026026": app block path "shopify://apps/tinyseo-seo-speed/blocks/broken-link-embed/0605268f-f7c4-4e95-b560-e43df7d59ae4" does not exist --><div id="shopify-block-AQm9TRHFSWXVWZkxWc__15316669992541710076" class="shopify-block shopify-app-block">
2529<script defer src="https://widget.reviews.io/rating-snippet/dist.js"></script>
2529 2530<link rel="stylesheet" href="https://widget.reviews.io/rating-snippet/dist.css" />
2531<script> 2532 (function () { 2533 function initRatingSnippet() { 2534 var hasStore = typeof reviewsIoStore !== 'undefined' && !!reviewsIoStore; 2535 if (!hasStore) return; 2536 2537 if (typeof ratingSnippet !== 'function') return; 2538 2539 // Ensure the target element exists (rating-output block may render after this app embed). 2540 if (!document.querySelector('.ruk_rating_snippet')) return; 2541 2542 var storeOverride = window.__REVIEWSIO_RATING_SNIPPET_STORE_OVERRIDE__; 2543 var storeForSnippet = storeOverride || reviewsIoStore; 2544 2545 ratingSnippet("ruk_rating_snippet",{ 2546 store: storeForSnippet, 2547 color: '#ffb701', 2548 2549 textClr: '#333333', 2550 2551 linebreak: false, 2552 2553 2554 listenForChanges: true, 2555 2556 2557 2558 formatReviewCount: true, 2559 2560 2561 lang: (typeof reviewsIoLang !== 'undefined' && reviewsIoLang) ? reviewsIoLang : 'en', 2562 mode: 'default', 2563 showEmptyStars: true, 2564 2565 enableSyndication: (typeof reviewsIoEnableSyndication !== 'undefined') ? reviewsIoEnableSyndication : false 2566 }); 2567 2568 const reviewsIoModal = document.getElementById('reviews_fade'); 2569 if (reviewsIoModal) { 2570 reviewsIoModal.classList.add('rio-withoutBranding'); 2571 } 2572 } 2573 2574 // Retry to survive slow network/script timing. 2575 var attempts = 0; 2576 var MAX_RATING_SNIPPET_INIT_ATTEMPTS = 20; 2577 var MAX_TIMEOUT_RETRY = 200; 2578 2579 function tick() { 2580 attempts += 1; 2581 initRatingSnippet(); 2582 if (attempts < MAX_RATING_SNIPPET_INIT_ATTEMPTS && (typeof ratingSnippet !== 'function' || !document.querySelector('.ruk_rating_snippet'))) { 2583 window.setTimeout(tick, MAX_TIMEOUT_RETRY); 2584 } 2585 } 2586 2587 // Use window load to maximize chance that the external script executed. 2588 window.addEventListener('load', tick); 2589 })(); 2590</script>
2590 2591 2592 2593</div>
2593<script src="https://cdn.shopify.com/storefront/standard-actions.js" type="module" data-source-attribution="shopify.standard_actions"></script>
2593 2594</body> 2595</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.