1<!DOCTYPE html> 2<html lang="sv"> 3 4<head><meta property="ix:host" content="cdn.quickbutik.com/images"> 5 <title>Webbutik - Ett ljus i Norr</title> 6 <meta charset="utf-8"> 7 8 <meta name="author" content="Quickbutik"> 9 <meta name="description" content="Webbutik - Ett ljus i Norr"> 10 11 <meta property="og:description" content="Webbutik - Ett ljus i Norr"> 12 <meta property="og:title" content="Webbutik - Ett ljus i Norr"> 13 <meta property="og:url" content="https://ettljusinorr.se/webbutik"> 14 <meta property="og:site_name" content="Ett ljus i Norr"> 15 <meta property="og:type" content="website"> 16 <meta property="og:image" content=""> 17 18 <meta property="twitter:card" content="summary"> 19 <meta property="twitter:title" content="Ett ljus i Norr"> 20 <meta property="twitter:description" content="Webbutik - Ett ljus i Norr"> 21 <meta property="twitter:image" content=""> 22 23 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 24 25 <link rel="canonical" href="https://ettljusinorr.se/webbutik"> 26 27 <!-- Google Fonts --> 28 29 30 31 <link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 32 <link rel="preload" href="https://fonts.googleapis.com/css?family=Asap:400,700%7CAsap:400,600,700&display=swap" as="style"> 33 <link href="https://fonts.googleapis.com/css?family=Asap:400,700%7CAsap:400,600,700&display=swap" rel="stylesheet" type="text/css" media="all"> 34 35 <!-- Preload images --> 36 <link rel="preload" as="image" href="https://cdn.quickbutik.com/images/88049h/templates/lejon/assets/logo.png?s=4304&auto=format&fit=clip&h=160&w=300"> 37 38 <!-- Favicon --> 39 <link rel="icon" type="image/png" href="https://cdn.quickbutik.com/images/88049h/templates/lejon/assets/favicon.png?s=4304&auto=format"> 40 41 <!-- Components --> 42
42<script defer src="https://storage.quickbutik.com/templates/lejon/js/settings.js?4304"></script>
42 43
43<script defer src="https://storage.quickbutik.com/templates/lejon/js/tailwind-styling.js?4304"></script>
43 44 45
45<script src="https://storage.quickbutik.com/templates/lejon/js/appsOverrides.js?4304"></script>
45 46 47 <!-- Quickbutik --> 48
49<script type="text/javascript">var qs_store_url = "https://ettljusinorr.se";</script>
vendor: 1 bytes, line 49
49
50<script type="text/javascript">var qs_store_apps_data = {"location":"\/webbutik","products":"","product_id":null,"category_id":"8","category_name":null,"fetch":"fetch","order":null,"search":null,"recentPurchaseId":null,"pagination":{"total_page_count":0,"current_page":1,"total_product_count":"0","previous_page":0,"next_page":null,"previous_page_url":null,"next_page_url":null,"current_url":"https:\/\/ettljusinorr.se\/webbutik","has_more_pages":false,"pagination_activated":true,"sort":"s-sortcount_1"}}; var qs_store_apps = [];</script>
vendor: 1 bytes, line 50
50
51<script defer type="text/javascript" src="/assets/qb_essentials_alpine.js?v=20260819a"></script>
51 52 53 <!-- Libraries --> 54
54<script defer type="text/javascript" src="/assets/imgix.min.js?v=290323"></script>
54 55
55<script defer src="https://storage.quickbutik.com/templates/lejon/js/swiper-bundle.min.js?4304"></script>
55 56
56<script defer src="https://storage.quickbutik.com/templates/lejon/js/photoswipe-lightbox.umd.min.js?4304"></script>
56 57
57<script defer src="https://storage.quickbutik.com/templates/lejon/js/photoswipe.umd.min.js?4304"></script>
57 58 59 <!-- Default parameters for Imgix --> 60
60<script>document.addEventListener("DOMContentLoaded", () => { imgix.config.defaultParams = { auto: 'format' } })</script>
60 61 62 <!-- Scripts in head --> 63 64 <!-- Stylesheet --> 65 <style>[x-cloak]{display: none !important;}img[ix-sizes="auto"]{display:block;width:100%;}img[src=""],img[src="#"],img:not([src]){visibility: hidden;}</style> 66 <link rel="preload" href="https://storage.quickbutik.com/templates/lejon/css/swiper-bundle.min.css?4304" as="style" onload="this.onload=null;this.rel='stylesheet'"> 67 <noscript><link rel="stylesheet" href="https://storage.quickbutik.com/templates/lejon/css/swiper-bundle.min.css?4304"></noscript> 68 <link rel="preload" href="https://storage.quickbutik.com/templates/lejon/css/photoswipe.css?4304" as="style" onload="this.onload=null;this.rel='stylesheet'"> 69 <noscript><link rel="stylesheet" href="https://storage.quickbutik.com/templates/lejon/css/photoswipe.css?4304"></noscript> 70 <link type="text/css" rel="stylesheet" href="https://storage.quickbutik.com/stores/88049h/templates/lejon/css/theme.css?4304">
71<script type="text/javascript" defer>document.addEventListener('DOMContentLoaded', () => { 72 document.body.setAttribute('data-qb-page', 'list'); 73});</script>
73<script type="text/javascript" defer>(function () { 74 const MAX_GET_QUERY_LENGTH = 2000; 75 76 window.apps = window.apps ?? {}; 77 78 window.registerApp = function (app) { 79 console.debug('registerApp', app); 80 window.apps[app.identifier] = app; 81 }; 82 83 document.addEventListener('DOMContentLoaded', () => { 84 const page = document.body.dataset?.qbPage; 85 const bodyFormData = new FormData(); 86 87 const productIds = [ 88 ...new Set( 89 [...document.querySelectorAll('[data-qb-selector="product-item"]')] 90 .map((product) => product.dataset.productId?.trim()) 91 .filter(Boolean) 92 ), 93 ]; 94 95 bodyFormData.set('location', window.location.pathname + window.location.search); 96 bodyFormData.set('products', productIds.join(',')); 97 bodyFormData.set( 98 'category_id', 99 document.querySelector('[data-qb-category-id]')?.dataset?.qbCategoryId || '' 100 ); 101 bodyFormData.set( 102 'product_id', 103 ['product'].includes(page) && document.querySelector('[data-product-id]')?.dataset?.productId || '' 104 ); 105 106 bodyFormData.set('fetch', 'fetch'); 107 bodyFormData.set('order', ''); 108 bodyFormData.set('search', ''); 109 bodyFormData.set('recentPurchaseId', ''); 110 111 const visitedProductId = bodyFormData.get('product_id'); 112 if (visitedProductId) { 113 const visit = new URLSearchParams({ product_id: visitedProductId }); 114 if (!(navigator.sendBeacon && navigator.sendBeacon(qs_store_url + '/apps/visit', visit))) { 115 fetch(qs_store_url + '/apps/visit', { method: 'post', body: visit, keepalive: true }).catch(() => {}); 116 } 117 } 118 119 const query = [...bodyFormData.entries()] 120 .map(([key, value]) => 121 encodeURIComponent(key) + '=' + 122 (key === 'products' ? productIds.map(encodeURIComponent).join(',') : encodeURIComponent(value)) 123 ) 124 .join('&'); 125 126 const postApps = () => { 127 const postData = new FormData(); 128 bodyFormData.forEach((value, key) => postData.append(key, value)); 129 postData.set('variants', JSON.stringify(typeof qs_options !== 'undefined' ? qs_options : [])); 130 131 return fetch(qs_store_url + '/apps/fetch', { method: 'post', body: postData }).then((res) => res.json()); 132 }; 133 134 const appsResponse = 135 query.length <= MAX_GET_QUERY_LENGTH 136 ? fetch(qs_store_url + '/apps/fetch?' + query, { method: 'get' }) 137 .then((res) => { 138 if (!res.ok || !res.headers.get('X-QB-Apps-Fetch')) { 139 throw new Error('GET /apps/fetch not accepted (' + res.status + ')'); 140 } 141 142 return res.json(); 143 }) 144 .catch(postApps) 145 : postApps(); 146 147 appsResponse 148 .then((data) => { 149 console.debug('Fetch apps success', data); 150 151 if (typeof Alpine === 'undefined') { 152 document.addEventListener('alpine:init', () => { 153 initApps(data); 154 }) 155 } else { 156 initApps(data); 157 } 158 }) 159 .catch((error) => { 160 console.debug('Fetch apps error', error); 161 }); 162 }); 163 164 function initApps(appData) { 165 const page = document.querySelector('[data-qb-page]')?.dataset?.qbPage; 166 167 console.debug('initApps', appData, page, window.apps); 168 169 if (page) { 170 Object.keys(window.apps).forEach((appIdentifier) => { 171 if (window.apps[appIdentifier].pages.includes(page)) { 172 let appOverrides = window?.appsOverrides?.[appIdentifier]; 173 174 if (appOverrides) { 175 window.apps[appIdentifier] = merge( 176 window.apps[appIdentifier], 177 appOverrides 178 ); 179 } 180 181 window.apps[appIdentifier].onMount({ appData, page }); 182 } 183 }); 184 } 185 } 186 187 function isObject(obj) { 188 return obj !== null && typeof obj === 'object'; 189 } 190 191 function merge(target, source) { 192 if (!isObject(target) || !isObject(source)) { 193 throw new Error('Both target and source must be objects.'); 194 } 195 196 for (const key in source) { 197 if (source.hasOwnProperty(key)) { 198 const targetValue = target[key]; 199 const sourceValue = source[key]; 200 201 if (isObject(sourceValue) && isObject(targetValue)) { 202 target[key] = merge(targetValue, sourceValue); 203 } else { 204 target[key] = sourceValue; 205 } 206 } 207 } 208 209 return target; 210 } 211})(); 212</script>
212<script src="/assets/shopassets/cookieconsent/cookieconsent.js?v=230"></script>
212 213<style> 214 :root { 215 --cc-btn-primary-bg: #222222 !important; 216 --cc-btn-primary-text: #fff !important; 217 --cc-btn-primary-hover-bg: #222222 !important; 218 --cc-btn-primary-hover-text: #fff !important; 219 } 220</style> 221<style> 222 html.force--consent, 223 html.force--consent body{ 224 height: auto!important; 225 width: 100vw!important; 226 } 227</style> 228 229
229<script type="text/javascript"> 230 var qb_cookieconsent = initCookieConsent(); 231 var qb_cookieconsent_blocks = [{"title":"Jag anv\u00e4nder cookies","description":"Jag anv\u00e4nder cookies f\u00f6r att anpassa det inneh\u00e5ll som visas f\u00f6r dig och f\u00f6r att du ska f\u00e5 b\u00e4sta t\u00e4nkbara upplevelse n\u00e4r du handlar hos mig."},{"title":"N\u00f6dv\u00e4ndiga cookies *","description":"Dessa cookies \u00e4r n\u00f6dv\u00e4ndiga f\u00f6r att butiken ska fungera korrekt och g\u00e5r inte att st\u00e4nga av utan att beh\u00f6va l\u00e4mna denna webbplats. De anv\u00e4nds f\u00f6r att funktionalitet som t.ex. varukorg, skapa ett konto och annat ska fungera korrekt.","toggle":{"value":"basic","enabled":true,"readonly":true}},{"title":"Statistik och prestanda","description":"Dessa cookies g\u00f6r att vi kan r\u00e4kna bes\u00f6k och trafik och d\u00e4rmed ta reda p\u00e5 saker som vilka sidor som \u00e4r mest popul\u00e4ra och hur bes\u00f6karna r\u00f6r sig p\u00e5 v\u00e5r webbplats. All information som samlas in genom dessa cookies \u00e4r sammanst\u00e4lld, vilket inneb\u00e4r att den \u00e4r anonym. Om du inte till\u00e5ter dessa cookies kan vi inte ge dig en lika skr\u00e4ddarsydd upplevelse.","toggle":{"value":"analytics","enabled":false,"readonly":false}},{"title":"Marknadsf\u00f6ring","description":"Dessa kakor s\u00e4tts via v\u00e5r webbplats av v\u00e5ra annonspartners f\u00f6r att skapa en profil utifr\u00e5n dina intressen och visa relevanta annonser p\u00e5 andra webbplatser. De kan samla information om ditt surfbeteende och anv\u00e4ndas f\u00f6r att identifiera din webbl\u00e4sare och internetenhet. Vissa annonspartners kan \u00e4ven ta emot hashad kontaktinformation (till exempel e-postadress) f\u00f6r att f\u00f6rb\u00e4ttra annonsm\u00e5lgrupperingen. Om du inte till\u00e5ter dessa kakor kommer du att uppleva mindre riktad annonsering.","toggle":{"value":"marketing","enabled":false,"readonly":false}}]; 232</script>
232 233<link rel="stylesheet" href="https://storage.quickbutik.com/stores/88049h/templates/lejon/css/tailwind.css?4304" /></head> 234 235 236 237 238 239<body class="antialiased bg-body text-body font-body"> 240 <header 241 data-qb-area="header" data-qb-element="" 242 id="main-nav" 243 x-data="{navMenuOpen: false}" 244 @keydown.escape="navMenuOpen = false; $store.cart.hideCart(); $store.settings.hideSettings();" 245 class="sticky top-0 z-50 bg-header shadow-default"> 246 247 <section x-data class="w-full" data-qb-element="notifications"> 248 <template x-for="notification in $store.notifications.notifications" :key="notification.id"> 249 <div x-show="notification.visible" x-on:click="$store.notifications.removeNotification(notification.id)" 250 x-transition:enter='transition ease-out duration-default transform' 251 x-transition:enter-start='opacity-0 -translate-y-4' 252 x-transition:enter-end='opacity-100 translate-y-0' 253 x-transition:leave='transition ease-in duration-default transform' 254 x-transition:leave-start='opacity-100 translate-y-0' 255 x-transition:leave-end='opacity-0 -translate-y-4' 256 > 257 <div class="relative w-full px-4 sm:px-8 py-4 bg-black" :style="{'background-color': notification.backgroundColor || '#000'}"> 258 <div class="relative z-10 flex items-center justify-between -m-2"> 259 <div class="flex-1 p-2" :style="{'color': notification.textColor || '#fff'}"> 260 <template x-if="notification.message"> 261 <h3 class="font-heading font-semibold text-lg" x-html="notification.message"></h3> 262 </template> 263 <template x-if="notification.htmlContent"> 264 <div x-html="notification.htmlContent"></div> 265 </template> 266 </div> 267 <div class="p-2"> 268 <button 269 class="w-8 h-8 flex items-center justify-center rounded-full hover:opacity-75 transition-opacity duration-200 cursor-pointer" 270 :style="{'color': notification.textColor || '#fff'}" 271 x-on:click.stop="$store.notifications.removeNotification(notification.id)" 272 aria-label="Close notification" 273 > 274 <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> 275 <path d="M12 4L4 12M4 4L12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> 276 </svg> 277 </button> 278 </div> 279 </div> 280 </div> 281 </div> 282 </template> 283 </section> 284 285 <nav class="text-navbar"> 286 <div class="container py-2 sm:py-4 mx-auto"> 287 <div class="flex items-center justify-between relative min-h-20 md:h-20"> 288 289 <!-- Logo (left-aligned) --> 290 <div class="flex grow items-center justify-start"> 291 <a data-qb-element="use_logo" href="https://ettljusinorr.se" class="content-center transition-all duration-200"> 292 <img src="https://cdn.quickbutik.com/images/88049h/templates/lejon/assets/logo.png?s=4304&auto=format&fit=clip&h=160&w=400&dpr=2" alt="Ett ljus i Norr" class="transition-all duration-200 h-20 md:h-20 object-contain object-left pr-4 w-full md:max-w-[400px]"> 293 </a> 294 </div> 295 296 <!-- Logo (centered, absolute positioning) --> 297 298 <!-- Smaller menu --> 299 300 <div class="flex flex-none items-center justify-end"> 301 302 <!-- Search --> 303 <button data-qb-element="show_search" @click="$store.search.showSearch(); $nextTick(() => document.getElementById('searchInput').focus());" 304 aria-label="Sök" class="px-2 hover:text-primary transition duration-200 focus:outline-none"> 305 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5 sm:w-7 sm:h-7"> 306 <path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" /> 307 </svg> 308 </button> 309 310 <!-- Login --> 311 312 <!-- Wishlist --> 313 314 <!-- Settings --> 315 316 <button @click="$store.settings.showSettings()" aria-label="Inställningar" 317 class="hidden lg:block px-2 hover:text-primary transition duration-200 focus:outline-none" 318 style="display:none"> 319 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5 sm:w-7 sm:h-7"> 320 <path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.
320074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" /> 321 <path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> 322 </svg> 323 </button> 324 325 <!-- Cart --> 326 <button @click="$store.cart.toggleCart()" aria-label="Varukorg" 327 class="relative px-2 hover:text-primary transition duration-200 focus:outline-none"> 328 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5 sm:w-7 sm:h-7"> 329 <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 10.5V6a3.75 3.75 0 10-7.5 0v4.5m11.356-1.993l1.263 12c.07.665-.45 1.243-1.119 1.243H4.25a1.125 1.125 0 01-1.12-1.243l1.264-12A1.125 1.125 0 015.513 7.5h12.974c.576 0 1.059.435 1.119 1.007zM8.625 10.5a.375.375 0 11-.75 0 .375.375 0 01.75 0zm7.5 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z"></path> 330 </svg> 331 332 <!-- Cart bubble --> 333 <template x-if="$store.cart.totalQuantity > 0"> 334 <span x-text="$store.cart.totalQuantity" class="cart-bubble absolute flex items-center justify-center -top-2.5 lg:-top-1.5 -right-0.5 h-6 w-6 border-2 border-header bg-primary text-white font-bold text-xs rounded-full"></span> 335 </template> 336 337 </button> 338 339 <!-- Mobile menu button --> 340 <div class="flex justify-items-start -mr-2 lg:hidden"> 341 <button @click="navMenuOpen = !navMenuOpen" aria-label="Menu" 342 class="navbar-burger py-3 px-2 sm:px-4 hover-opacity transition duration-200 focus:outline-none"> 343 <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 sm:w-7 sm:h-7"> 344 <path fill-rule="evenodd" clip-rule="evenodd" d="M3.5 7C3.22386 7 3 6.77614 3 6.5C3 6.22386 3.22386 6 3.5 6H20.5C20.7761 6 21 6.22386 21 6.5C21 6.77614 20.7761 7 20.5 7H3.5ZM3.5 12C3.22386 12 3 11.7761 3 11.5C3 11.2239 3.22386 11 3.5 11H20.5C20.7761 11 21 11.2239 21 11.5C21 11.7761 20.7761 12 20.5 12H3.5ZM3 16.5C3 16.7761 3.22386 17 3.5 17H20.5C20.7761 17 21 16.7761 21 16.5C21 16.2239 20.7761 16 20.5 16H3.5C3.22386 16 3 16.2239 3 16.5Z" fill="currentColor"></path> 345 </svg> 346 </button> 347 </div> 348 349 </div> 350 351 </div> 352 </div> 353 </nav> 354 355 <!-- Larger menu --> 356 <nav class="hidden lg:block items-center justify-center text-navbar"> 357 <div class="container pb-2 sm:pb-4 mx-auto"> 358 <div class="overflow-y-auto"> 359 360 361 362 <ul 363 x-data="{ navbarLevelsDepth: 3 }" 364 class=" flex-wrap justify-center grid grid-flow-col justify-stretch"> 365 366 367 <li class="mx-2"> 368 <a href="/" class="flex flex-auto justify-center py-2 px-2 transition ease-in-out hover-opacity " 369 title="Hem"> 370 <span>Hem</span> 371 </a> 372 </li> 373 374 375 376 377 <li x-data="dropdown" @mouseover="open = navbarLevelsDepth >= 1 && true" @mouseleave="open = false" class="mx-2 group"> 378 <div class="relative flex flex-auto justify-center items-center"> 379 <a href="/webbutik" id="menu-item-15" 380 class="flex justify-center py-2 px-2 transition ease-in-out hover-opacity active">
381 <span>Webbutik</span> 382 </a> 383 <svg x-show="navbarLevelsDepth >= 1" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" 384 stroke-width="1.5" stroke="currentColor" 385 class="w-4 h-4 cursor-pointer transform rotate-90 " :class="{'rotate-0' : open}"> 386 <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5"></path> 387 </svg> 388 </div> 389 390 <!-- First drop down --> 391 <ul x-cloak 392 x-show="open" 393 " 394 x-transition:enter='transition ease-out transform' 395 x-transition:enter-start='opacity-0 -translate-y-4' 396 x-transition:enter-end='opacity-100 translate-y-0' 397 x-transition:leave='transition ease-in transform' 398 x-transition:leave-start='opacity-100 translate-y-0' 399 x-transition:leave-end='opacity-0 -translate-y-4' 400 class="absolute z-50 w-full max-w-xs px-6 py-2 bg-body text-body shadow-default rounded-none" aria-labelledby="menu-item-15"> 401 402 <li x-data="dropdown" @mouseover="open = navbarLevelsDepth >= 2 && true" @mouseleave="open = false" class="relative"> 403 <div class="flex"> 404 <a href="/webbutik/keramik" class="flex flex-1 py-3 transition ease-in-out hover-opacity" aria-expanded="false"> 405 <span>Keramik</span> 406 </a> 407 <div x-show="navbarLevelsDepth >= 2" class="flex h-full flex-col -mr-3 p-3 cursor-pointer rounded-full hover-opacity"> 408 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" 409 stroke="currentColor" class="w-4 h-4"> 410 <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5"></path> 411 </svg> 412 </div> 413 </div> 414 415 <!-- Second dropdown (to the right) --> 416 <ul x-show="open" 417 x-init="$watch('open', value => isInViewport($el, value))" 418 419 x-transition:enter='transition ease-out transform' 420 x-transition:enter-start='opacity-0 -translate-x-10' 421 x-transition:enter-end='opacity-100 translate-x-0' 422 x-transition:leave='transition ease-in transform' 423 x-transition:leave-start='opacity-100 translate-x-0' 424 x-transition:leave-end='opacity-0 -translate-x-10' 425 aria-labelledby="categoryDropdown17" 426 class="absolute top-0 left-full ml-3 z-50 w-full max-w-xs px-6 py-2 text-body bg-body shadow-default rounded-none"> 427 428 429 <li><a href="/webbutik/keramik/bi-vattnare-tradgardskonst" class="block py-3 transition ease-in-out hover-opacity ">Bi-vattnare / TrädgÃ¥rdskonst</a></li> 430 431 </ul> 432 </li> 433 434 435 436 437 <li><a href="/webbutik/tavlor" class="block py-3 transition ease-in-out hover-opacity ">Tavlor</a></li> 438 439 440 <li x-data="dropdown" @mouseover="open = navbarLevelsDepth >= 2 && true" @mouseleave="open = false" class="relative"> 441 <div class="flex"> 442 <a href="/webbutik/kristaller" class="flex flex-1 py-3 transition ease-in-out hover-opacity" aria-expanded="false"> 443 <span>Kristaller och Energiarbete</span> 444 </a> 445 <div x-show="navbarLevelsDepth >= 2" class="flex h-full flex-col -mr-3 p-3 cursor-pointer rounded-full hover-opacity"> 446 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" 447 stroke="currentColor" class="w-4 h-4"> 448 <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5"></path> 449 </svg> 450 </div> 451 </div> 452 453 <!-- Second dropdown (to the right) --> 454 <ul x-show="open" 455 x-init="$watch('open', value => isInViewport($el, value))" 456 457 x-transition:enter='transition ease-out transform' 458 x-transition:enter-start='opacity-0 -translate-x-10' 459 x-transition:enter-end='opacity-100 translate-x-0' 460 x-transition:leave='transition ease-in transform' 461 x-transition:leave-start='opacity-100 translate-x-0' 462 x-transition:leave-end='opacity-0 -translate-x-10' 463 aria-labelledby="categoryDropdown18" 464 class="absolute top-0 left-full ml-3 z-50 w-full max-w-xs px-6 py-2 text-body bg-body shadow-default rounded-none"> 465 466 467 <li><a href="/webbutik/kristaller/trumlade-kristaller" class="block py-3 transition ease-in-out hover-opacity ">Trumlade kristaller</a></li> 468 469 470 471 <li><a href="/webbutik/kristaller/spetsar-klot-torn-och-raa-kristaller" class="block py-3 transition ease-in-out hover-opacity ">Spetsar, klot, torn och rÃ¥a kristaller</a></li> 472 473 474 475 <li><a href="/webbutik/kristaller/stora-unika-kristaller" class="block py-3 transition ease-in-out hover-opacity ">Kluster och stora unika kristaller</a></li> 476 477 478 479 <li><a href="/webbutik/kristaller/orakel-och-tarotkort" class="block py-3 transition ease-in-out hover-opacity ">Orakel och Tarotkort</a></li> 480 481 482 483 <li><a href="/webbutik/kristaller/rokelser-mm" class="block py-3 transition ease-in-out hover-opacity ">Rökelser mm.</a></li> 484 485 486 487 <li><a href="/webbutik/kristaller/bocker" class="block py-3 transition ease-in-out hover-opacity ">Böcker</a></li> 488 489 490 491 <li><a href="/webbutik/kristaller/till-kristallnatet" class="block py-3 transition ease-in-out hover-opacity ">Till Kristallnätet</a></li> 492 493 494 495 <li><a href="/webbutik/kristaller/smycken" class="block py-3 transition ease-in-out hover-opacity ">Smycken</a></li> 496 497 498 499 <li><a href="/webbutik/kristaller/ovrigt" class="block py-3 transition ease-in-out hover-opacity ">Ãvrigt</a></li> 500 501 </ul> 502 </li> 503 504 505 </ul> 506 </li> 507 508 509 <li class="mx-2"> 510 <a href="/sidor/tjanster" class="flex flex-auto justify-center py-2 px-2 transition ease-in-out hover-opacity " 511 title="Tjänster">
512 <span>Tjänster</span> 513 </a> 514 </li> 515 516 517 518 <li class="mx-2"> 519 <a href="/sidor/omettljusinorr" class="flex flex-auto justify-center py-2 px-2 transition ease-in-out hover-opacity " 520 title="Om Ett ljus i Norr"> 521 <span>Om Ett ljus i Norr</span> 522 </a> 523 </li> 524 525 526 527 <li class="mx-2"> 528 <a href="/contact" class="flex flex-auto justify-center py-2 px-2 transition ease-in-out hover-opacity " 529 title="Kontakt"> 530 <span>Kontakt</span> 531 </a> 532 </li> 533 534 535 536 </ul> 537 </div> 538 </div> 539 </nav> 540 541 <!-- Menu --> 542 <div 543 x-cloak 544 @keydown.escape="navMenuOpen = false" 545 x-show="navMenuOpen" 546 class="navbar-menu fixed top-0 left-0 bottom-0 w-full sm:max-w-sm z-40"> 547 548 <!-- Overlay --> 549 <div 550 x-show='navMenuOpen' 551 @click='navMenuOpen = false' 552 x-transition:enter='transition ease-out duration-default transform' 553 x-transition:enter-start='opacity-0' 554 x-transition:enter-end='opacity-100' 555 x-transition:leave='transition ease-in duration-default transform' 556 x-transition:leave-start='opacity-100' 557 x-transition:leave-end='opacity-0' 558 class='fixed inset-0 transition-opacity bg-overlay bg-opacity-40' 559 aria-hidden='true' 560 ></div> 561 562 <!-- Menu --> 563 <nav 564 x-show='navMenuOpen' 565 @keydown.escape="navMenuOpen = false" 566 class="relative flex flex-col h-full bg-body" 567 x-transition:enter='transition ease-out duration-default transform' 568 x-transition:enter-start='opacity-0 -translate-x-10 sm:-translate-x-10' 569 x-transition:enter-end='opacity-100 translate-x-0' 570 x-transition:leave='transition ease-in duration-default transform' 571 x-transition:leave-start='opacity-100 translate-x-0' 572 x-transition:leave-end='opacity-0 -translate-x-10 sm:-translate-x-10' 573 > 574 <div class="flex items-center justify-between mb-12 p-6"> 575 576 <!-- Logo --> 577 <a class="items-center" href="https://ettljusinorr.se"> 578 579 <img alt="Ett ljus i Norr" class="img-fluid img-logo object-contain object-left w-full h-20 pr-8" 580 ix-src="https://cdn.quickbutik.com/images/88049h/templates/lejon/assets/logo.png?s=4304&auto=format"> 581 </a> 582 583 <!-- Close button --> 584 <button @click="navMenuOpen = false" class="inline-block navbar-close focus:outline-none" type="button" 585 aria-label="Close"> 586 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" 587 stroke="currentColor" class="w-6 h-6"> 588 <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"></path> 589 </svg></button> 590 </div> 591 592 <!-- Menu --> 593 <div class="px-6 overflow-y-auto"> 594 595 <ul> 596 597 <li class=""> 598 <a class="block py-3 font-bold hover:text-gray-900 " href="/" 599 title="Hem"> 600 <span>Hem</span> 601 </a> 602 </li> 603 604 605 606 607 <li x-data="{expanded:false}" class="active"> 608 <div class="flex"> 609 <a href="/webbutik" 610 class="flex flex-1 py-3 font-bold hover:text-gray-900 active"> 611 <span id="navMenuItem15">Webbutik</span> 612 </a> 613 <div class="flex h-full flex-col py-3"> 614 <svg @click="expanded = !expanded" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" 615 stroke="currentColor" class="w-6 h-6 cursor-pointer transform duration-default" :class="{'rotate-90' : expanded}"> 616 <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5"></path> 617 </svg> 618 </div> 619 </div> 620 <ul x-show="expanded" 621 x-transition:enter='transition ease-out duration-default transform' 622 x-transition:enter-start='opacity-0 -translate-y-4' 623 x-transition:enter-end='opacity-100 translate-y-0' 624 x-transition:leave='transition ease-in duration-default transform' 625 x-transition:leave-start='opacity-100 translate-y-0' 626 x-transition:leave-end='opacity-0 -translate-y-4' 627 class="pl-4" aria-labelledby="navMenuItem15"> 628 629 <li x-data="{expanded:false}"> 630 <div class="flex"> 631 <a href="/webbutik/keramik" class="flex flex-1 py-3" aria-expanded="false">
632 <span id="navDropdown17">Keramik</span> 633 </a> 634 <div class="flex h-full flex-col py-3"> 635 <svg @click="expanded = !expanded" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" 636 stroke="currentColor" class="w-4 h-4 cursor-pointer transform duration-default" :class="{'rotate-90' : expanded}"> 637 <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5"></path> 638 </svg> 639 </div> 640 </div> 641 642 <ul x-show="expanded" 643 x-transition:enter='transition ease-out duration-default transform' 644 x-transition:enter-start='opacity-0 -translate-y-4' 645 x-transition:enter-end='opacity-100 translate-y-0' 646 x-transition:leave='transition ease-in duration-default transform' 647 x-transition:leave-start='opacity-100 translate-y-0' 648 x-transition:leave-end='opacity-0 -translate-y-4' 649 aria-labelledby="navDropdown17" class="pl-4"> 650 651 652 <li><a href="/webbutik/keramik/bi-vattnare-tradgardskonst" class="block py-3 ">Bi-vattnare / TrädgÃ¥rdskonst</a></li> 653 654 </ul> 655 </li> 656 657 658 659 660 <li><a href="/webbutik/tavlor" class="block py-3 ">Tavlor</a></li> 661 662 663 <li x-data="{expanded:false}"> 664 <div class="flex"> 665 <a href="/webbutik/kristaller" class="flex flex-1 py-3" aria-expanded="false"> 666 <span id="navDropdown18">Kristaller och Energiarbete</span> 667 </a> 668 <div class="flex h-full flex-col py-3"> 669 <svg @click="expanded = !expanded" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" 670 stroke="currentColor" class="w-4 h-4 cursor-pointer transform duration-default" :class="{'rotate-90' : expanded}"> 671 <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5"></path> 672 </svg> 673 </div> 674 </div> 675 676 <ul x-show="expanded" 677 x-transition:enter='transition ease-out duration-default transform' 678 x-transition:enter-start='opacity-0 -translate-y-4' 679 x-transition:enter-end='opacity-100 translate-y-0' 680 x-transition:leave='transition ease-in duration-default transform' 681 x-transition:leave-start='opacity-100 translate-y-0' 682 x-transition:leave-end='opacity-0 -translate-y-4' 683 aria-labelledby="navDropdown18" class="pl-4"> 684 685 686 <li><a href="/webbutik/kristaller/trumlade-kristaller" class="block py-3 ">Trumlade kristaller</a></li> 687 688 689 690 <li><a href="/webbutik/kristaller/spetsar-klot-torn-och-raa-kristaller" class="block py-3 ">Spetsar, klot, torn och rÃ¥a kristaller</a></li> 691 692 693 694 <li><a href="/webbutik/kristaller/stora-unika-kristaller" class="block py-3 ">Kluster och stora unika kristaller</a></li> 695 696 697 698 <li><a href="/webbutik/kristaller/orakel-och-tarotkort" class="block py-3 ">Orakel och Tarotkort</a></li> 699 700 701 702 <li><a href="/webbutik/kristaller/rokelser-mm" class="block py-3 ">Rökelser mm.</a></li> 703 704 705 706 <li><a href="/webbutik/kristaller/bocker" class="block py-3 ">Böcker</a></li> 707 708 709 710 <li><a href="/webbutik/kristaller/till-kristallnatet" class="block py-3 ">Till Kristallnätet</a></li> 711 712 713 714 <li><a href="/webbutik/kristaller/smycken" class="block py-3 ">Smycken</a></li> 715 716 717 718 <li><a href="/webbutik/kristaller/ovrigt" class="block py-3 ">Ãvrigt</a></li> 719 720 </ul> 721 </li> 722 723 724 725 </ul> 726 </li> 727 728 729 <li class=""> 730 <a class="block py-3 font-bold hover:text-gray-900 " href="/sidor/tjanster" 731 title="Tjänster">
732 <span>Tjänster</span> 733 </a> 734 </li> 735 736 737 738 <li class=""> 739 <a class="block py-3 font-bold hover:text-gray-900 " href="/sidor/omettljusinorr" 740 title="Om Ett ljus i Norr"> 741 <span>Om Ett ljus i Norr</span> 742 </a> 743 </li> 744 745 746 747 <li class=""> 748 <a class="block py-3 font-bold hover:text-gray-900 " href="/contact" 749 title="Kontakt"> 750 <span>Kontakt</span> 751 </a> 752 </li> 753 754 755 </ul> 756 </div> 757 758 <div class="p-6 mt-auto"> 759 760 <!-- Wishlist --> 761 762 <!-- Settings --> 763 764 <span href="" @click="$store.settings.showSettings()" class="block px-6 py-3 text-center button-rounding border border-foreground bg-transparent text-body font-bold cursor-pointer hover-opacity mb-2" 765 style="display:none"> 766 Inställningar 767 </span> 768 769 <!-- Login --> 770 771 </div> 772 </nav> 773 </div> 774 <!-- Settings modal --> 775 <template x-teleport='body'> 776 <div 777 x-data 778 x-cloak 779 x-show='$store.settings.settingsOpen' 780 @keydown.escape='$store.settings.hideSettings()' 781 class='fixed inset-0 z-50 overflow-y-auto' 782 aria-labelledby='modal-title' 783 role='dialog' 784 aria-modal='true' 785 > 786 <div class='flex items-end justify-center min-h-screen text-center md:items-center sm:block'> 787 788 <!-- Overlay --> 789 <div 790 @click='$store.settings.hideSettings()' 791 x-show='$store.settings.settingsOpen' 792 x-transition:enter='transition ease-out duration-default transform' 793 x-transition:enter-start='opacity-0' 794 x-transition:enter-end='opacity-100' 795 x-transition:leave='transition ease-in duration-default transform' 796 x-transition:leave-start='opacity-100' 797 x-transition:leave-end='opacity-0' 798 class='fixed inset-0 transition-opacity bg-overlay bg-opacity-40' 799 aria-hidden='true'></div> 800 801 <!-- Modal --> 802 <div x-show='$store.settings.settingsOpen' 803 x-transition:enter='transition ease-out duration-default transform' 804 x-transition:enter-start='opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95' 805 x-transition:enter-end='opacity-100 translate-y-0 sm:scale-100' 806 x-transition:leave='transition ease-in duration-default transform' 807 x-transition:leave-start='opacity-100 translate-y-0 sm:scale-100' 808 x-transition:leave-end='opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95' 809 class='flex flex-col sm:inline-block w-full max-w-xl fixed top-0 bottom-0 sm:top-auto sm:bottom-auto sm:static sm:my-20 overflow-hidden text-left transition-all transform bg-body rounded-none shadow-xl 2xl:max-w-2xl'> 810 811 <!-- Header --> 812 <header class='flex items-center justify-between p-6 mb-2'> 813 814 <!-- Title --> 815 <span class='text-h2 font-bold font-heading'>Inställningar</span> 816 817 <!-- Close button --> 818 <button @click='$store.settings.hideSettings()' class='text-gray-600 focus:outline-none hover:text-gray-700'> 819 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> 820 <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"></path> 821 </svg> 822 </button> 823 </header> 824 825 <div class="px-6 pb-6 mb-6 h-full overflow-y-auto"> 826 827 <!-- Languages --> 828 829 <!-- Tax --> 830 831 <!-- Currencies--> 832 833 </div> 834 </div> 835 </div> 836 </div> 837 </template> <!-- Search modal --> 838 <template x-teleport='body'> 839 <div 840 x-data='appSearchModal' 841 x-cloak 842 x-show='$store.search.searchOpen' 843 @keydown.escape='$store.search.hideSearch()' 844 class='fixed inset-0 z-50 overflow-y-auto' 845 aria-labelledby='modal-title' 846 role='dialog' 847 aria-modal='true' 848 > 849 <div class='flex items-end justify-center min-h-screen px-4 text-center md:items-center sm:block sm:p-0'> 850 851 <!-- Overlay --> 852 <div 853 @click='$store.search.hideSearch()' 854 x-show='$store.search.searchOpen' 855 x-transition:enter='transition ease-out duration-default transform' 856 x-transition:enter-start='opacity-0' 857 x-transition:enter-end='opacity-100' 858 x-transition:leave='transition ease-in duration-default transform' 859 x-transition:leave-start='opacity-100' 860 x-transition:leave-end='opacity-0' 861 class='fixed inset-0 transition-opacity bg-overlay bg-opacity-40' 862 aria-hidden='true' 863 ></div> 864 865 <!-- Modal --> 866 <div 867 modal="search" 868 x-show='$store.search.searchOpen' 869 x-transition:enter='transition ease-out duration-default transform' 870 x-transition:enter-start='opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95' 871 x-transition:enter-end='opacity-100 translate-y-0 sm:scale-100' 872 x-transition:leave='transition ease-in duration-default transform' 873 x-transition:leave-start='opacity-100 translate-y-0 sm:scale-100' 874 x-transition:leave-end='opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95' 875 class='flex flex-col h-screen sm:inline-block w-full h-max-screen sm:max-w-xl 2xl:max-w-2xl fixed top-0 bottom-0 sm:top-auto sm:bottom-auto sm:static sm:my-20 overflow-hidden text-left transition-all transform' 876 style='@media (min-width: 576px) { max-height: calc(80vh) }' 877 > 878 <div class="pointer-events-auto relative flex w-full flex-col max-h-full overflow-hidden border-none bg-body shadow-xl"> 879 880 <!-- Header --> 881 <header class='flex flex-col flex-shrink-0 p-6'> 882 883 <div class="flex items-center justify-between pb-6"> 884 885 <!-- Title -->
886 <span class='text-h2 font-bold font-heading'>Sök</span> 887 888 <!-- Close button --> 889 <button 890 @click='$store.search.hideSearch()' 891 class='text-body focus:outline-none hover-opacity'> 892 <svg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='currentColor' class='w-6 h-6'> 893 <path stroke-linecap='round' stroke-linejoin='round' d='M6 18L18 6M6 6l12 12'></path> 894 </svg> 895 </button> 896 </div> 897 898 <form 899 action='/shop/search' 900 method='get' class='flex'> 901 <div class='flex w-full relative'> 902 <div class="flex h-full flex-col justify-center absolute top-0 left-0 pl-2"> 903 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 opacity-40"> 904 <path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" /> 905 </svg> 906 </div> 907 <input 908 type='search' 909 name='s' 910 x-model='searchTerm' 911 id='searchInput' 912 @input.debounce.500ms="search" 913 placeholder='Sök produkter' 914 aria-label='Sök' 915 aria-describedby='button-search-desktop' 916 class='flex flex-1 py-3 pr-3 pl-14 input-bg input-text input-border border input-rounding focus:outline-none' 917 > 918 </div> 919 </form> 920 </header> 921 922 <!-- No results found --> 923 <div x-show='searchTerm && noResults' class="px-6 mb-8"> 924 <p class="mt-8 text-center"> 925 Inga sökresultat hittades 926 </p> 927 </div> 928 929 <!-- Search results --> 930 <div x-show='!noResults && searchTerm != ""' class="relative overflow-y-auto pb-6"> 931 <template x-for='result in results'> 932 <a :href='result.product.url'> 933 <div class="flex py-2 px-6 transition duration-default hover-opacity"> 934 <div class="flex flex-0 w-16 h-16 mr-6"> 935 <img :src="result.product.firstimage + '?w=100&h=100'" ix-sizes="auto" :alt="result.product.title" class="w-full object-contain object-center"> 936 </div> 937 <div class="flex flex-1 flex-col justify-center"> 938 <span x-html='highlightTitle(result.product.title)'></span> 939 </div> 940 <div class="flex flex-col justify-center"> 941 <span x-text='result.product.price'></span> 942 </div> 943 </div> 944 <hr class="border-foreground opacity-10"> 945 </a> 946 </template> 947 </div> 948 949 </div> 950 951 </div> 952 </div> 953 </div> 954 </template> 955 956 </header> 957 <!-- Campaign bar --> 958 <!-- Campaign bar --> 959 960 961 962 963 <!-- Standard layout utan sidomeny --> 964 965 <!-- Breadcrumbs --> 966 <section data-qb-area="category" class="py-16" data-qb-category-id="8"> 967 <div class="container mx-auto"> 968 969 <!-- Title --> 970 <h1 data-qb-element="list_show_cattitle" class="mb-8 font-heading text-center text-h1"> 971 Webbutik 972 </h1> 973 974 <!-- Description --> 975 976 <!-- Categories --> 977 978 <section> 979 980 <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-size"> 981 982 983 984
985 <!-- Categories on start page --> 986 987 988 989 990 991 <!-- Categories on list --> 992 993 994 995 996 <div class="flex w-full max-w-xl flex-col"> 997 <div class="relative bg-gray-400 bg-opacity-60 rounded-none aspect-1/1 overflow-hidden shadow-default h-full w-full text-white"> 998 <a href="/webbutik/keramik" aria-label="Webbutik"> 999 <img ix-src="https://cdn.quickbutik.com/images/88049h/pages/z35p5xr8mjybksj.jpg" ix-sizes="auto" class="absolute top-0 left-0 right-0 bottom-0 z-0 w-full h-full object-cover object-center transform transition-all duration-500 hover:scale-105" alt="Webbutik"> 1000 <div class="relative pointer-events-none flex h-full"> 1001 1002 <!-- Show category name according to START:categories component --> 1003 1004 <!-- Category name for subcategories on list --> 1005 <h2 class="z-10 px-4 flex-1 hidden text-left justify-left text-h4 font-heading">Keramik</h2> 1006 1007 <!-- Show title if image is missing --> 1008 1009 1010 1011 </div> 1012 </a> 1013 </div> 1014 1015 <!-- Show category name according to START:categories component --> 1016 1017 <!-- Category name for subcategories on list --> 1018 <div class="flex text-body"> 1019 <a href="/webbutik/keramik" class="flex flex-1"> 1020 1021 <h2 class="z-10 pt-4 flex flex-1 h-16 items-start text-left justify-left text-lg font-heading">Keramik</h2> 1022 1023 </a> 1024 </div> 1025 1026 </div> 1027 1028 1029 1030
1031 <!-- Categories on start page --> 1032 1033 1034 1035 1036 1037 <!-- Categories on list --> 1038 1039 1040 1041 1042 <div class="flex w-full max-w-xl flex-col"> 1043 <div class="relative bg-gray-400 bg-opacity-60 rounded-none aspect-1/1 overflow-hidden shadow-default h-full w-full text-white"> 1044 <a href="/webbutik/tavlor" aria-label="Webbutik"> 1045 <img ix-src="https://cdn.quickbutik.com/images/88049h/pages/vqxnkrtxpxjoh9j.jpg" ix-sizes="auto" class="absolute top-0 left-0 right-0 bottom-0 z-0 w-full h-full object-cover object-center transform transition-all duration-500 hover:scale-105" alt="Webbutik"> 1046 <div class="relative pointer-events-none flex h-full"> 1047 1048 <!-- Show category name according to START:categories component --> 1049 1050 <!-- Category name for subcategories on list --> 1051 <h2 class="z-10 px-4 flex-1 hidden text-left justify-left text-h4 font-heading">Tavlor</h2> 1052 1053 <!-- Show title if image is missing --> 1054 1055 1056 1057 </div> 1058 </a> 1059 </div> 1060 1061 <!-- Show category name according to START:categories component --> 1062 1063 <!-- Category name for subcategories on list --> 1064 <div class="flex text-body"> 1065 <a href="/webbutik/tavlor" class="flex flex-1"> 1066 1067 <h2 class="z-10 pt-4 flex flex-1 h-16 items-start text-left justify-left text-lg font-heading">Tavlor</h2> 1068 1069 </a> 1070 </div> 1071 1072 </div> 1073 1074 1075 1076
1077 <!-- Categories on start page --> 1078 1079 1080 1081 1082 1083 <!-- Categories on list --> 1084 1085 1086 1087 1088 <div class="flex w-full max-w-xl flex-col"> 1089 <div class="relative bg-gray-400 bg-opacity-60 rounded-none aspect-1/1 overflow-hidden shadow-default h-full w-full text-white"> 1090 <a href="/webbutik/kristaller" aria-label="Webbutik"> 1091 <img ix-src="https://cdn.quickbutik.com/images/88049h/pages/wro0gazyrabskyi.jpg" ix-sizes="auto" class="absolute top-0 left-0 right-0 bottom-0 z-0 w-full h-full object-cover object-center transform transition-all duration-500 hover:scale-105" alt="Webbutik"> 1092 <div class="relative pointer-events-none flex h-full"> 1093 1094 <!-- Show category name according to START:categories component --> 1095 1096 <!-- Category name for subcategories on list --> 1097 <h2 class="z-10 px-4 flex-1 hidden text-left justify-left text-h4 font-heading">Kristaller och Energiarbete</h2> 1098 1099 <!-- Show title if image is missing --> 1100 1101 1102 1103 </div> 1104 </a> 1105 </div> 1106 1107 <!-- Show category name according to START:categories component --> 1108 1109 <!-- Category name for subcategories on list --> 1110 <div class="flex text-body"> 1111 <a href="/webbutik/kristaller" class="flex flex-1"> 1112 1113 <h2 class="z-10 pt-4 flex flex-1 h-16 items-start text-left justify-left text-lg font-heading">Kristaller och Energiarbete</h2> 1114 1115 </a> 1116 </div> 1117 1118 </div> 1119 1120 1121 </div> 1122 1123 </section> 1124 1125 1126 <hr class="border-foreground opacity-10 mt-16"> 1127 1128 <!-- Products filter and sort --> 1129 <div class="w-full my-6 flex justify-between"> 1130 <div class="gap-2 flex justify-end" data-qb-selector="filter-container"></div> 1131 <div class="flex items-start"> 1132 <div class="flex justify-end" data-qb-selector="sort-container"></div> 1133 </div> 1134 </div> 1135 1136 <div class="flex flex-wrap mt-6"> 1137 <div class="w-full"> 1138 <div 1139 x-data="{ 1140 outOfStockStatus: 'show', 1141 cardPadding: '4', 1142 cardTextPadding: '4' 1143 }" 1144 id="productList" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-size"> 1145 1146 <!-- Products --> 1147 1148 </div> 1149 </div> 1150 </div> 1151 1152 <!-- Pagination --> 1153 <!-- Pagination --> 1154 1155 <!-- Description (2) --> 1156 1157 </div> 1158 </section> 1159 1160 1161 <section data-qb-area="footer" class="bg-footer text-center lg:text-left"> 1162 <div class="container py-8 md:py-12 mx-auto "> 1163 <div class="flex flex-wrap justify-between"> 1164 1165 <!-- Footer menu --> 1166 <div class="w-full sm:w-1/3 mb-10 lg:mb-0"> 1167 1168 <h5 data-qb-setting="footer_menu_heading" class="text-h5 font-bold mb-5 text-footer"> 1169 Läs mer 1170 </h5> 1171 1172 <ul data-qb-setting="use_footer_menu" class="opacity-70"> 1173 <li class="mb-2"> 1174 <a class="inline-block text-footer hover:opacity-50" href="/sidor/terms-and-conditions">Köpvillkor</a> 1175 </li> 1176 <li class="mb-2"> 1177 <a class="inline-block text-footer hover:opacity-50" href="/sidor/faq">Vanliga frågor</a> 1178 </li> 1179 </ul> 1180 </div> 1181 1182 1183 <!-- Social media link --> 1184 <div class="w-full sm:w-1/3 mb-10 lg:mb-0"> 1185 <div data-qb-setting="show_footer_socialicons" class="text-footer mb-3"> 1186 1187 <h5 class="text-h5 font-bold mb-5 text-footer"> 1188 Sociala medier 1189 </h5> 1190 1191 <div class="opacity-70"> 1192 1193 <a class="inline-block mr-4 hover-opacity" aria-label="Facebook" href="https://www.facebook.com/ettljusinorr.se/" target="_blank"> 1194 <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewB
1194ox="0 0 24 24"> 1195 <path fill="currentColor" d="M9 8h-3v4h3v12h5v-12h3.642l.358-4h-4v-1.667c0-.955.192-1.333 1.115-1.333h2.885v-5h-3.808c-3.596 0-5.192 1.583-5.192 4.615v3.385z"></path> 1196 </svg> 1197 </a> 1198 <a class="inline-block mr-4 hover-opacity" aria-label="Instagram" href="https://www.instagram.com/ettljusinorr.se" target="_blank"> 1199 <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24"> 1200 <path fill="currentColor" d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"></path> 1201 </svg> 1202 </a> 1203 </div> 1204 1205 </div> 1206 </div> 1207 1208 <!-- Newsletter signup --> 1209 1210 </div> 1211 1212 <div> 1213 <!-- Payment methods --> 1214 </div> 1215 </div> 1216 1217 <div> 1218 <hr class="border-foreground opacity-10"> 1219 <div class="container mx-auto"> 1220 <div class="py-8 text-center"> 1221 <p class="text-footer text-sm opacity-50"> 1222 <!-- Copyright --> 1223 © 2026 Ett ljus i Norr 1224 <span data-qb-setting="use_footer_poweredby">– <a style="text-decoration: none;" target="_blank" rel="nofollow sponsored" title="Powered by Quickbutik" href="https://quickbutik.com">Powered by Quickbutik</a></span> 1225 </p> 1226 </div> 1227 </div> 1228 </div> 1229 </section> 1230 <div x-data x-cloak 1231 x-show="$store.cart.cartOpen" 1232 @keydown.escape="$store.cart.hideCart()" 1233 @pageshow.window="$store.cart.init()" 1234 class="navbar-menu fixed top-0 right-0 bottom-0 w-full sm:max-w-md z-50"> 1235 1236 <!-- Overlay --> 1237 <div 1238 x-show='$store.cart.cartOpen' 1239 @click='$store.cart.hideCart()' 1240 x-transition:enter='transition ease-out duration-default transform' 1241 x-transition:enter-start='opacity-0' 1242 x-transition:enter-end='opacity-100' 1243 x-transition:leave='transition ease-in duration-default transform' 1244 x-transition:leave-start='opacity-100' 1245 x-transition:leave-end='opacity-0' 1246 class='fixed inset-0 transition-opacity bg-overlay bg-opacity-40' 1247 aria-hidden='true' 1248 ></div> 1249 1250 <!-- Cart --> 1251 <div 1252 x-show='$store.cart.cartOpen' 1253 @keydown.escape="$store.cart.hideCart()" 1254 class="relative flex flex-col h-full bg-body" 1255 x-transition:enter='transition ease-out duration-default transform' 1256 x-transition:enter-start='opacity-0 translate-x-10 sm:translate-x-10' 1257 x-transition:enter-end='opacity-100 translate-x-0' 1258 x-transition:leave='transition ease-in duration-default transform' 1259 x-transition:leave-start='opacity-100 translate-x-0' 1260 x-transition:leave-end='opacity-0 translate-x-10 sm:translate-x-10' 1261 > 1262 <header class="flex items-center justify-between p-6"> 1263 1264 <!-- Title --> 1265 <span class="items-center font-heading text-h3"> 1266 Varukorg 1267 </span> 1268 1269 <div class="flex"> 1270 1271 <!-- Close button --> 1272 <button 1273 @click="$store.cart.hideCart()" 1274 class="flex cart-close cursor-pointer py-2 px-4 -mr-4 focus:outline-none hover-opacity" 1275 type="button" 1276 aria-label="Stäng" 1277 >
1278 <span>Stäng</span> 1279 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 ml-2"> 1280 <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"></path> 1281 </svg> 1282 </button> 1283 1284 </div> 1285 1286 </header> 1287 1288 <!-- Cart contents --> 1289 <div class="h-full p-6 overflow-y-auto"> 1290 1291 <!-- Cart messages --> 1292 <div class="qs-cart-message"></div> 1293 1294 <!-- Empty cart snippet--> 1295 <!-- In case of empty cart - display this snippet + remove .d-none--> 1296 <div class="hidden qs-cart-empty flex items-center h-full opacity-50 text-center"> 1297 <div> 1298 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" class="inline-block w-16 h-16 mb-6"> 1299 <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 10.5V6a3.75 3.75 0 10-7.5 0v4.5m11.356-1.993l1.263 12c.07.665-.45 1.243-1.119 1.243H4.25a1.125 1.125 0 01-1.12-1.243l1.264-12A1.125 1.125 0 015.513 7.5h12.974c.576 0 1.059.435 1.119 1.007zM8.625 10.5a.375.375 0 11-.75 0 .375.375 0 01.75 0zm7.5 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z"></path> 1300 </svg> 1301 <p> 1302 Varukorgen är tom 1303 </p> 1304 </div> 1305 </div> 1306 1307 <!-- Cart items --> 1308 <div class="grid grid-cols-1 "> 1309 1310 <template 1311 x-for="product in $store.cart.products" 1312 :key="`${product.item.id}-${product.qty}-${product.uid}`" 1313 > 1314 <div class="bg-card shadow-default rounded-none p-4 mb-4"> 1315 1316 <div class="flex w-full"> 1317 1318 <!-- Product image --> 1319 <div class="flex shrink-0 justify-center items-start pr-4 w-36"> 1320 <a :href="product.item.url"> 1321 <img loading="lazy" :src="product.item.firstimage + '?w=150&h=150'" class="object-contain object-center w-full h-full rounded-none" :alt="product.item.title"> 1322 </a> 1323 </div> 1324 <div class="flex flex-1 flex-col overflow-hidden"> 1325 1326 <div class="flex flex-col"> 1327 1328 <!-- Product title --> 1329 <a :href="product.item.url"> 1330 <h4 class="font-body font-bold" x-text="product.item.title"></h4> 1331 </a> 1332 1333 <!-- Product variant --> 1334 <template x-if="product.variant_name"> 1335 <span x-text="product.variant_name" class="text-sm opacity-50"></span> 1336 </template> 1337 1338 <!-- Product propertiers --> 1339 <template x-if="product.properties"> 1340 <template x-for="property in Object.values(product.properties)"> 1341 <small class="truncate text-ellipsis" x-text="property"></small> 1342 </template> 1343 </template> 1344 1345 </div> 1346 1347 <!-- Current price --> 1348 <span class="text-sm"> 1349 <span 1350 :class="product.item.has_discount ? 'text-discounted-price' : ''" 1351 x-text="product.item.price" 1352 ></span> 1353 1354 <!-- Original price (only show if has discount) --> 1355 <template x-if="product.item.has_discount"> 1356 <span 1357 class="opacity-50 line-through ml-1" 1358 x-text="product.item.original_price" 1359 ></span> 1360 </template> 1361 </span> 1362 1363 <div class="flex flex-1 grow-1"> 1364 <div class="flex self-end flex-1 justify-between" 1365 x-data="{ 1366 quantity: product.qty, 1367 }" 1368 x-init="$watch('quantity', Alpine.debounce((value) => { 1369 if (value && value > 0) { 1370 $store.cart.update({ 1371 productID: product.item.id, 1372 quantity: value 1373 }) 1374 } 1375 }, 500))" 1376 > 1377 <div class="flex input-bg input-text border input-border input-rou
1377nding overflow-hidden"> 1378 1379 <!-- Decrease quantity --> 1380 <button 1381 type="button" 1382 class="w-8 font-bold cursor-pointer input-bg input-text focus:outline-none hover:bg-gray-100 py-1 px-2" 1383 @click="quantity = Math.max(1, quantity - 1)" 1384 > 1385 - 1386 </button> 1387 1388 <!-- Quantity input --> 1389 <input 1390 required 1391 min="1" 1392 step="1" 1393 type="number" 1394 class="w-12 qs-cart-qty navbar-cart-product-qty input-bg input-text border-0 input-items text-center focus:outline-none" 1395 @input="quantity = Math.floor(Number($event.target.value))" 1396 x-model="quantity" 1397 > 1398 1399 <!-- Increase quantity --> 1400 <button 1401 type="button" 1402 class="w-8 font-bold cursor-pointer input-bg input-text focus:outline-none hover:bg-gray-100 py-1 px-2" 1403 @click="quantity = Number(quantity) + 1" 1404 > 1405 + 1406 </button> 1407 </div> 1408 1409 <!-- Remove from cart --> 1410 <button 1411 type="button" 1412 class="font-medium text-baseThird qs-cart-delete py-2 px-4 -mr-2 hover:text-red-700" 1413 data-pid="" 1414 @click="$store.cart.delete({ 1415 productID: product.item.id, 1416 })" 1417 > 1418 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4"> 1419 <path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" /> 1420 </svg> 1421 </button> 1422 </div> 1423 </div> 1424 1425 </div> 1426 </div> 1427 1428 </div> 1429 </template> 1430 1431 </div> 1432 </div> 1433 1434 <!-- Footer --> 1435 <footer class="mt-auto p-8 bg-body"> 1436 1437 <!-- Total discount (only show if there's a discount) --> 1438 <div class="flex items-end mb-2" x-show="$store.cart.totalDiscountRaw && $store.cart.totalDiscountRaw > 0"> 1439 <div class="flex-1"> 1440 <span class="text-sm font-medium text-discounted-price">Rabatt</span> 1441 </div> 1442 <div class="flex-1 text-right"> 1443 <span 1444 class="text-sm font-medium text-discounted-price" 1445 x-text="'-' + $store.cart.totalDiscount" 1446 ></span> 1447 </div> 1448 </div> 1449 1450 <div class="flex items-end mb-4"> 1451 <div class="w-8/12"> 1452 <span class="text-sm font-bold">Total summa 1453 1454 <!-- Tax --> 1455 1456 </div> 1457 <div class="w-6/12 text-right"> 1458 1459 <!-- Cart total amount --> 1460 <span 1461 class="text-lg font-bold text-primary" 1462 x-text="$store.cart.totalAmount" 1463 ></span> 1464 </div> 1465 </div> 1466 1467 <!-- Buttons --> 1468 <div> 1469 <a href="/cart/index" class="block button-px button-py mb-2 text-center text-white font-bold bg-primary hover:bg-gray-900 button-rounding focus:outline-none"> 1470 Till kassan 1471 </a> 1472 <button @click='$store.cart.hideCart()' class="block w-full button-px button-py text-center text-primary hover:text-body button-rounding focus:outline-none"> 1473 Fortsätt att handla 1474 </button> 1475 </div> 1476 </footer> 1477 1478 </div> 1479 </div> 1480 <section 1481 x-data 1482 x-cloak 1483 x-show="$store.modal.modalVisible" 1484 x-transition.opacity 1485 class="fixed inset-0 py-6 sm:py-24 bg-black bg-opacity-50 z-50 h-full overflow-y-auto"> 1486 <div class="container mx-auto relative"> 1487 <div x-on:click.outside="$store.modal.closeModal()" :style="{'background-color': $store.modal.backgroundColor || '#fff'}" 1488 class="relative max-w-xl mx-auto text-center py-12 md:py-16 px-6 md:px-14 roun
1488ded-none"> 1489 <a class="absolute top-4 right-4 inline-block ml-auto cursor-pointer hover-opacity" x-on:click="$store.modal.closeModal()" href="#" rel="nofollow"> 1490 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> 1491 <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"></path> 1492 </svg> 1493 </a> 1494 <div x-html="$store.modal.content"></div> 1495 </div> 1496 </div> 1497 </section> 1498 1499 <svg width="0" height="0" style="display:none"> 1500 <symbol xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" id="icon--building"> 1501 <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 21h19.5m-18-18v18m10.5-18v18m6-13.5V21M6.75 6.75h.75m-.75 3h.75m-.75 3h.75m3-6h.75m-.75 3h.75m-.75 3h.75M6.75 21v-3.375c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21M3 3h12m-.75 4.5H21m-3.75 3.75h.008v.008h-.008v-.008zm0 3h.008v.008h-.008v-.008zm0 3h.008v.008h-.008v-.008z" /> 1502 </symbol> 1503 1504 <symbol xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" id="icon--card"> 1505 <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 8.25h19.5M2.25 9h19.5m-16.5 5.25h6m-6 2.25h3m-3.75 3h15a2.25 2.25 0 002.25-2.25V6.75A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25v10.5A2.25 2.25 0 004.5 19.5z" /> 1506 </symbol> 1507 1508 <symbol xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" id="icon--chat"> 1509 <path stroke-linecap="round" stroke-linejoin="round" d="M20.25 8.511c.884.284 1.5 1.128 1.5 2.097v4.286c0 1.136-.847 2.1-1.98 2.193-.34.027-.68.052-1.02.072v3.091l-3-3c-1.354 0-2.694-.055-4.02-.163a2.115 2.115 0 01-.825-.242m9.345-8.334a2.126 2.126 0 00-.476-.095 48.64 48.64 0 00-8.048 0c-1.131.094-1.976 1.057-1.976 2.192v4.286c0 .837.46 1.58 1.155 1.951m9.345-8.334V6.637c0-1.621-1.152-3.026-2.76-3.235A48.455 48.455 0 0011.25 3c-2.115 0-4.198.137-6.24.402-1.608.209-2.76 1.614-2.76 3.235v6.226c0 1.621 1.152 3.026 2.76 3.235.577.075 1.157.14 1.74.194V21l4.155-4.155" /> 1510 </symbol> 1511 1512 <symbol xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" id="icon--people"> 1513 <path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" /> 1514 </symbol> 1515 1516 <symbol xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" id="icon--store"> 1517 <path stroke-linecap="round" stroke-linejoin="round" d="M13.5 21v-7.5a.75.75 0 01.75-.75h3a.75.75 0 01.75.75V21m-4.5 0H2.36m11.14 0H18m0 0h3.64m-1.39 0V9.349m-16.5 11.65V9.35m0 0a3.001 3.001 0 003.75-.615A2.993 2.993 0 009.75 9.75c.896 0 1.7-.393 2.25-1.016a2.993 2.993 0 002.25 1.016c.896 0 1.7-.393 2.25-1.016a3.001 3.001 0 003.75.614m-16.5 0a3.004 3.004 0 01-.621-4.72L4.318 3.44A1.5 1.5 0 015.378 3h13.243a1.5 1.5 0 011.06.44l1.19 1.189a3 3 0 01-.621 4.72m-13.5 8.65h3.75a.75.75 0 00.75-.75V13.5a.75.75 0 00-.75-.75H6.75a.75.75 0 00-.75.75v3.75c0 .415.336.75.75.75z" /> 1518 </symbol> 1519 1520 <symbol xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" id="icon--truck"> 1521 <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 18.75a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m3 0h6m-9 0H3.375a1.125 1.125 0 01-1.125-1.125V14.25m17.25 4.5a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m3 0h1.125c.621 0 1.129-.504 1.09-1.124a17.902 17.902 0 00-3.213-9.193 2.056 2.056 0 00-1.58-.86H14.25M16.5 18.75h-2.25m0-11.177v-.958c0-.568-.422-1.048-.987-1.106a48.554 48.554 0 00-10.026 0 1.106 1.106 0 00-.987 1.106v7.635m12-6.677v6.677m0 4.5v-4.5m0 0h-12" /> 1522 </symbol> 1523 1524 <symbol xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" id="icon--verified"> 1525 <path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z" /> 1526 </symbol> 1527 1528 <symbol xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 61 92" stroke-width="1.5" stroke="currentColor" id="icon--marker"> 1529 <path fill-rule="evenodd" d="M33.04 89.474s27.607-35.842 27.607-54.635c0-18.792-13.431-34.027-30-34.027-16.568 0-30 15.235-30 34.027 0 18.793 27.621 54.652 27.621 54.652 1.314 1.764 3.456 1.765 4.771-.017Zm-2.393-43.737c8.285 0 15-6.704 15-14.975 0-8.27-6.715-14.975-15-14.975-8.284 0-15 6.705-15 14.975s6.716 14.975 15 14.975Z"/> 1530 </symbol> 1531 1532 <symbol xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 1200 1200" stroke-width="1.5" stroke="currentColor" id="icon--marker-2"> 1533 <path d="M617.65 0c233.9 0 412.94 176.3 412.94 410.21v13.316c0 308.25-272.78 691.77-420 776.47h-3.531c-147.22-84.707-437.65-468.23-437.65-776.47v-13.312c0-233.92 196.69-410.22 430.59-410.22h17.648z"/> 1534 <path d="M616.77 28.234c222.21 0 385.58 169.26 385.58 391.47v12.648c0 292.83-252.44 654.71-392.3 739.41h-3.348c-139.85-84.707-409.05-446.59-409.05-739.41v-12.648c0-222.21 180.14-391.47 402.35-391.47h16.773z"/>
1534 1535 </symbol> 1536 1537 <symbol xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 -1 11 29" stroke-width="1.5" stroke="currentColor" id="icon--marker-3"> 1538 <path d="M6 26V10.975A5.499 5.499 0 0 0 5.5 0 5.5 5.5 0 0 0 5 10.975V26h1zM3 5.5c0 .171.018.338.051.5h-1C2.026 5.835 2 5.671 2 5.5c0-1.759 1.309-3.204 3-3.449v1C3.86 3.283 3 4.292 3 5.5z"/> 1539 </symbol> 1540 1541 <symbol xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor" id="icon--chevron-left"> 1542 <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" /> 1543 </symbol> 1544 1545 <symbol xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor" id="icon--chevron-right"> 1546 <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" /> 1547 </symbol> 1548 </svg>
1549<script type="text/javascript"> var recaptchaSiteKey="6Lc-2GcUAAAAAMXG8Lz5S_MpVshTUeESW6iJqK0_"; </script>
1549<script type="text/javascript">window.registerApp({ 1550 name: 'ProductProperties', 1551 identifier: 'QuickbutikProductProperties', 1552 description: 'Show product properties', 1553 version: '1.1.0', 1554 pages: ['product'], 1555 data: {}, 1556 config: { 1557 insertTarget: { 1558 selector: '#product-properties', 1559 position: 'afterbegin', // beforebegin | afterbegin | beforeend | afterend 1560 }, 1561 fieldsAttributes: { 1562 fileInputAccept: 'image/*,application/pdf', 1563 }, 1564 styling: { 1565 formClasses: 'flex gap-4 flex-col pb-4', 1566 inputContainerClasses: 'flex flex-col gap-2', 1567 labelClasses: 'text-sm font-bold text-gray-700', 1568 labelErrorClasses: 'ml-1 text-red-500', 1569 checkboxInputClasses: 'form-checkbox h-5 w-5 text-gray-600', 1570 shortTextInputClasses: 1571 'block w-full p-3 text-xs text-black placeholder-gray-500 transition-all bg-white border border-gray-200 focus:border-gray-700 focus:outline-none', 1572 longTextInputClasses: 1573 'block w-full p-3 text-xs text-black placeholder-gray-500 transition-all bg-white border border-gray-200 focus:border-gray-700 focus:outline-none', 1574 fileContainerClasses: 'flex w-full input-bg input-border input-px input-py input-rounding w-full', 1575 fileInputClasses: 'input-bg input-border input-rounding input-px input-py', 1576 fileInputButtonClasses: 'shrink-0 cursor-pointer bg-primary text-center button-text-primary font-bold button-py button-px button-rounding mr-4 transition duration-100 hover:opacity-70', 1577 fileInputFilenameClasses: 'truncate button-py', 1578 fileUploadProgressContainerClasses: 'block relative w-full mt-2 rounded',
1579 fileUploadProgressBarBackgroundClasses: 'block absolute w-full h-full bg-black opacity-10 text-white rounded', 1580 fileUploadProgressBarClasses: 'block relative bg-primary py-1 px-4 text-xs font-bold text-white rounded', 1581 addonPriceClasses: 'text-sm text-gray-600 ml-0', 1582 }, 1583 }, 1584 onMount: function ({ appData, page }) { 1585 console.debug(`${this.identifier}@onMount`) 1586 1587 const productID = document 1588 .querySelector('[data-product-id]') 1589 .getAttribute('data-product-id') 1590 1591 if (productID) { 1592 fetch(`${qs_store_url}/apps/productproperties/view?product_id=${productID}`) 1593 .then((response) => response.json()) 1594 .then((data) => { 1595 this.data = data 1596 this.renderProductProperties() 1597 }) 1598 .catch((error) => { 1599 console.error('Error:', error); 1600 }) 1601 } 1602 1603 }, 1604 generageInputId: function (id) { 1605 return `ProductProperties-${id}` 1606 }, 1607 generateInputName: function (id) { 1608 return `product_properties_${id}` 1609 }, 1610 renderProductProperties: function () { 1611 const productPropertiesContainer = document.querySelector(this.data.identifier) 1612 1613 const insertPosition = { 1614 before: 'beforebegin', 1615 after: 'afterend', 1616 in: 'afterbegin', 1617 }[this.data.apply] 1618 1619 if (productPropertiesContainer && insertPosition && this.data.properties) { 1620 const form = document.createElement('form') 1621 form.classList.add(...this.config.styling.formClasses.split(' ')) 1622 form.setAttribute('id', 'ProductPropertiesForm') 1623 1624 this.data.properties.forEach((property) => { 1625 const [key] = 1626 Object.entries(property) 1627 .filter(([key]) => 1628 ['isCheckbox', 'isFile', 'isLongtext', 'isShorttext', 'isAddonProduct'].includes( 1629 key 1630 ) 1631 ) 1632 .find(([_, value]) => value) || [] 1633 1634 let input = null 1635 1636 switch (key) { 1637 case 'isCheckbox': 1638 input = this.renderCheckbox(property) 1639 break 1640 case 'isFile': 1641 input = this.renderFile(property) 1642 break 1643 case 'isLongtext': 1644 input = this.renderLongtext(property) 1645 break 1646 case 'isShorttext': 1647 input = this.renderShorttext(property) 1648 break 1649 case 'isAddonProduct': 1650 input = this.renderAddonProduct(property) 1651 break 1652 default: 1653 console.log('No valid type found') 1654 } 1655 1656 if (input) { 1657 const inputContainer = document.createElement('div') 1658 inputContainer.classList.add( 1659 ...this.config.styling.inputContainerClasses.split(' ') 1660 ) 1661 inputContainer.classList.add('product-property-container') // Add a class for easier selection 1662 form.appendChild(inputContainer) 1663 1664 // Only add label for non-addon products and checkboxes 1665 if (!['isAddonProduct', 'isCheckbox'].includes(key)) { 1666 1667 // Label 1668 const label = document.createElement('label') 1669 label.classList.add(...this.config.styling.labelClasses.split(' ')) 1670 1671 // File element 1672 if (key == 'isFile') { 1673 label.setAttribute('for', 'file-' + property.id) 1674 } else { 1675 label.setAttribute('for', this.generageInputId(property.id)) 1676 } 1677 label.innerText = property.title 1678 1679 // Add addon price display next to the property title 1680 if (property.addonPriceRaw && parseFloat(property.addonPriceRaw) > 0) { 1681 const addonPriceSpan = document.createElement('span') 1682 addonPriceSpan.classList.add(...this.config.styling.addonPriceClasses.split(' ')) 1683 addonPriceSpan.classList.add('addon-price') // Add a class for easier selection 1684 addonPriceSpan.innerText = ` (+${property.addonPrice})` 1685 addonPriceSpan.setAttribute('data-addon-price', property.addonPriceRaw) 1686 label.appendChild(addonPriceSpan) 1687 } 1688 1689 if (property.required === '1') { 1690 const requiredStar = document.createElement('span') 1691 requiredStar.classList.add(...this.config.styling.labelErrorClasses.split(' ')) 1692 requiredStar.innerText = '*' 1693 label.appendChild(requiredStar) 1694 } 1695 inputContainer.appendChild(label) 1696 } 1697 1698 // All elements except files and addon products, which need specific structure and attributes 1699 if (key !== 'isFile' && key !== 'isAddonProduct') { 1700 input.setAttribute('id', this.generageInputId(property.id)) 1701 input.setAttribute('name', this.generateInputName(property.id)) 1702 input.setAttribute('data-qb-property', property.id) 1703 1704 if (property.required === '1') { 1705 input.setAttribute('required', true) 1706 } 1707 } 1708 1709 inputContainer.appendChild(input) 1710 1711 form.appendChild(inputContainer) 1712 } 1713 }) 1714 1715 productPropertiesContainer.insertAdjacentElement(insertPosition, form) 1716 1717 // Only render ReCaptcha if we have a File property 1718 if (this.data.hasFileProperties) { 1719 this.renderReCaptcha() 1720 } 1721 } 1722 }, 1723 renderCheckbox: function (property) { 1724 const checkboxWrapper = document.createElement('div') 1725 checkboxWrapper.classList.add('flex', 'items-center') 1726 1727 const checkbox = document.createElement('input') 1728 checkbox.classList.add( 1729 ...this.config.styling.checkboxInputClasses.split(' ') 1730 ) 1731 checkbox.setAttribute('type', 'checkbox') 1732 checkbox.setAttribute('name', this.generateInputName(property.id)) 1733 checkbox.setAttribute('value', '1') 1734 checkbox.setAttribute('data-qb-property', property.id) 1735 1736 // Label 1737 const label = document.createElement('label') 1738 label.classList.add(...this.config.styling.labelClasses.split(' ')) 1739 label.classList.add('ml-4') 1740 label.setAttribute('for', this.generageInputId(property.id)) 1741 label.innerText = property.title 1742 1743 if (property.required === '1') { 1744 const requiredStar = document.createElement('span') 1745 requiredStar.classList.add(...this.config.styling.labelErrorClasses.split(' ')) 1746 requiredStar.innerText = '*' 1747 label.appendChild(requiredStar) 1748 1749 checkbox.setAttribute('required', true) 1750 } 1751 1752 checkboxWrapper.appendChild(checkbox) 1753 checkboxWrapper.appendChild(label) 1754 1755 return checkboxWrapper 1756 }, 1757 renderAddonProduct: function (property) { 1758 const addonContainer = document.createElement('div') 1759 addonContainer.classList.add('addon-product-container', 'flex', 'items-center', 'p-4', 'border', 'rounded-md') 1760 1761 // Checkbox 1762 const checkbox = document.createElement('input') 1763 checkbox.classList.add( 1764 ...this.config.styling.checkboxInputClasses.split(' '), 1765 'mr-4' 1766 ) 1767 checkbox.setAttribute('type', 'checkbox') 1768 checkbox.setAttribute('name', this.generateInputName(property.id)) 1769 checkbox.setAttribute('value', '1') 1770 checkbox.setAttribute('data-qb-property', property.id) 1771 1772 if (property.required === '1') { 1773 checkbox.setAttribute('required', true) 1774 } 1775 1776 // Add checkbox to the container first 1777 addonContainer.appendChild(checkbox) 1778 1779 // Image 1780 if (property.addonProductImage) { 1781 const imageContainer = document.createElement('div') 1782 imageContainer.classList.add('w-16', 'h-16', 'mr-4') 1783 1784 const image = document.createElement('img') 1785 image.src = property.addonProductImage 1786 image.alt = property.addonProductTitle || property.title 1787 image.classList.add('w-full', 'h-full', 'object-cover', 'rounded') 1788 1789 imageContainer.appendChild(image) 1790 addonContainer.appendChild(imageContainer) 1791 } 1792 1793 // Content (Title, Price, Link) 1794 const contentContainer = document.createElement('div') 1795 contentContainer.classList.add('flex', 'flex-col', 'flex-grow') 1796 1797 const titleElement = document.createElement('span') 1798 titleElement.classList.add('font-bold', 'mb-1') 1799 titleElement.textContent = property.addonProductTitle || property.title 1800 1801 const priceElement = document.createElement('span') 1802 priceElement.classList.add('text-sm', 'text-gray-600') 1803 priceElement.textContent = '+' + (property.addonProductPrice || property.addonPrice) 1804 1805 contentContainer.appendChild(titleElement) 1806 contentContainer.appendChild(priceElement) 1807 1808 if (property.addonProductLink) { 1809 const link = document.createElement('a') 1810 link.href = property.addonProductLink 1811 link.target = '_blank' 1812 link.classList.add('text-blue-600', 'hover:underline', 'text-sm', 'mt-1') 1813 link.textContent = property.addonProductLinkText || 'Read More' 1814 contentContainer.appendChild(link) 1815 } 1816 1817 // Add content container last 1818 addonContainer.appendChild(contentContainer) 1819 1820 return addonContainer 1821 }, 1822 renderFile: function (property) { 1823 1824 // Wrapper 1825 const fileContainer = document.createElement('div') 1826 fileContainer.classList.add(...this.config.styling.fileContainerClasses.split(' ')) 1827
1828 // Input 1829 const file = document.createElement('input') 1830 file.classList.add(...this.config.styling.fileInputClasses.split(' ')) 1831 file.classList.add('qs-cart-product-file') 1832 file.setAttribute('id', 'file-' + property.id) 1833 file.setAttribute('type', 'file') 1834 file.setAttribute('name', 'product_fields_files[' + property.id + ']') 1835 file.setAttribute('accept', this.config.fieldsAttributes.fileInputAccept) 1836 if (property.required === '1') { 1837 file.setAttribute('required', true) 1838 file.setAttribute('data-is-required', true) 1839 } 1840 file.style.display = 'none' 1841 1842 file.setAttribute('data-qb-property', property.id) 1843 fileContainer.append(file) 1844 1845 const button = document.createElement('label') 1846 button.setAttribute('for', 'file-' + property.id) 1847 button.classList.add(...this.config.styling.fileInputButtonClasses.split(' ')) 1848 button.innerText = this.data.texts.chooseFile 1849 file.addEventListener('change', function () { 1850 filename.innerText = file.files[0].name 1851 }) 1852 1853 fileContainer.append(button) 1854 1855 const filename = document.createElement('span') 1856 filename.innerText = this.data.texts.noFileChosen 1857 filename.classList.add(...this.config.styling.fileInputFilenameClasses.split(' ')) 1858 fileContainer.append(filename) 1859 1860 return fileContainer 1861 }, 1862 renderLongtext: function (property) { 1863 const longtext = document.createElement('textarea') 1864 longtext.classList.add( 1865 ...this.config.styling.longTextInputClasses.split(' ') 1866 ) 1867 longtext.setAttribute('name', property.name) 1868 longtext.setAttribute('maxlength', 250) 1869 1870 return longtext 1871 }, 1872 renderShorttext: function (property) { 1873 const shorttext = document.createElement('input') 1874 shorttext.classList.add( 1875 ...this.config.styling.shortTextInputClasses.split(' ') 1876 ) 1877 shorttext.setAttribute('type', 'text') 1878 shorttext.setAttribute('name', property.name) 1879 shorttext.setAttribute('maxlength', 250) 1880 1881 return shorttext 1882 }, 1883 renderReCaptcha: function () { 1884 1885 /* PRODUCT FIELD FILE UPLOAD START */ 1886 if (document.readyState === "complete" || document.readyState === "interactive") { 1887 1888 const propertiesForm = document.querySelector('#ProductPropertiesForm') 1889 1890 if (!propertiesForm) { 1891 console.error('Could not find #ProductPropertiesForm') 1892 return false 1893 } 1894 1895 propertiesForm.dataset.prevent = '1' 1896 1897 var recaptchaDiv = document.createElement('div') 1898 recaptchaDiv.setAttribute('class', 'g-recaptcha') 1899 recaptchaDiv.setAttribute('data-sitekey', recaptchaSiteKey) 1900 recaptchaDiv.setAttribute('data-callback', 'recaptchaSubmit') 1901 recaptchaDiv.setAttribute('data-size', 'invisible') 1902 propertiesForm.prepend(recaptchaDiv) 1903 1904 var url = "https://www.google.com/recaptcha/api.js" 1905 var script = document.createElement('script') 1906 script.src = url 1907 script.type = 'text/javascript' 1908 document.getElementsByTagName('head')[0].appendChild(script) 1909 } 1910 }, 1911 uploadFiles: function () { 1912 return new Promise((resolve, reject) => { 1913 const form = document.querySelector('#ProductPropertiesForm') 1914 1915 if (form && form.querySelectorAll('input[type="file"]').length > 0) { 1916 var filesToUpload = new FormData() 1917 Array.from(form.querySelectorAll('input[type="file"]')).forEach(function (tag) { 1918 Array.from(tag.files).forEach(function (file) { 1919 filesToUpload.append(tag.name, file) 1920 }) 1921 }) 1922 1923 var buy_btn_text = null, wait_btn_text = null 1924 var buybtn_selector = document.querySelector('.qs-cart-submit') 1925 1926 var xhr = new XMLHttpRequest() 1927 xhr.open('POST', '/cart/misc', true) 1928 xhr.setRequestHeader('cache-control', 'no-cache') 1929 xhr.responseType = 'json' 1930 1931 var app = this 1932 1933 xhr.onload = function () { 1934 if (xhr.status >= 200 && xhr.status < 300) { 1935 var data = xhr.response 1936 1937 // Reset button 1938 if (buy_btn_text && buy_btn_text.length !== 0) { 1939 buybtn_selector.value = buy_btn_text 1940 buybtn_selector.innerHTML = buy_btn_text 1941 } 1942 buybtn_selector.removeAttribute('disabled') 1943 1944 // Remove progress bar 1945 if (form.querySelector('.productproperties-file-progress')) { 1946 form.querySelector('.productproperties-file-progress').remove() 1947 } 1948 1949 if (data?.properties) { 1950 if (data.properties.errors) { 1951 alert(data.properties.errors) 1952 1953 return false 1954 } else {
1955 data.properties.forEach(function (property, key) { 1956 // document.querySelector('.product-fields-file:eq('+property.property_id+')').value = property.value 1957 }) 1958 1959 document.querySelector('.qs-cart').dataset.prevent = 0 1960 document.querySelector('.qs-cart').dataset.fileuploadstatus = 1 1961 // document.querySelector('.qs-cart').submit() 1962 1963 resolve(true) 1964 } 1965 } else { 1966 1967 // Handle error 1968 console.error('No properties returned from file upload.') 1969 reject(false) 1970 } 1971 } else { 1972 1973 // Handle error 1974 console.error(xhr.statusText) 1975 1976 reject(false) 1977 } 1978 } 1979 1980 xhr.upload.onloadstart = function (e) { 1981 1982 // Progress bar 1983 if (form.querySelectorAll('.productproperties-file-progress').length == 0) { 1984 1985 // Container 1986 const progressContainer = document.createElement('div') 1987 if (app.config?.styling?.fileUploadProgressContainerClasses) { 1988 progressContainer.classList.add(...app.config.styling.fileUploadProgressContainerClasses.split(' ')) 1989 } 1990 progressContainer.classList.add('progress', 'productproperties-file-progress') 1991 1992 // Background
1993 const progressBarBackground = document.createElement('div') 1994 if (app.config?.styling?.fileUploadProgressBarBackgroundClasses) { 1995 progressBarBackground.classList.add(...app.config.styling.fileUploadProgressBarBackgroundClasses.split(' ')) 1996 } 1997 progressBarBackground.style.zIndex = 1 1998 progressContainer.appendChild(progressBarBackground) 1999 2000 // Bar 2001 const progressBar = document.createElement('div') 2002 if (app.config?.styling?.fileUploadProgressBarClasses) { 2003 progressBar.classList.add(...app.config.styling.fileUploadProgressBarClasses.split(' ')) 2004 progressBar.innerText = " " 2005 } 2006 progressBar.style.zIndex = 2 2007 progressBar.classList.add('progress-bar') 2008 progressContainer.appendChild(progressBar) 2009 2010 form.querySelectorAll('input[type="file"]')[form.querySelectorAll('input[type="file"]').length - 1].parentNode.parentNode.insertAdjacentElement('beforeEnd', progressContainer) 2011 } 2012 } 2013 2014 xhr.upload.onloadend = function (e) { } 2015 2016 xhr.upload.onprogress = function (e) { 2017 var percentComplete = Math.ceil((e.loaded / e.total) * 100) 2018 } 2019 2020 xhr.upload.onprogress = function (evt) { 2021 2022 let progressBar 2023 if (form.querySelectorAll('.productproperties-file-progress').length == 0) { 2024 form.querySelectorAll('input[type="file"]')[0].insertAdjacentHTML('afterend', '<div class="progress productproperties-file-progress ' + app.config.styling.fileUploadProgressContainerClasses + '"><div class="progress-bar ' + app.config.styling.fileUploadProgressBarClasses + '"></div></div>') 2025 progressBar = form.querySelectorAll('.productproperties-file-progress') 2026 } 2027 2028 if (evt.lengthComputable) { 2029 var percentComplete = evt.loaded / evt.total 2030 percentComplete = Math.round(percentComplete * 100) 2031 2032 2033 let progressBarBar = form.querySelectorAll('.productproperties-file-progress')[0].querySelector('.progress-bar') 2034 progressBarBar.style.width = percentComplete + '%' 2035 progressBarBar.innerHTML = percentComplete + '%' 2036 } 2037 } 2038 2039 2040 xhr.onerror = function () { 2041 console.error(xhr.statusText) 2042 } 2043 2044 xhr.send(filesToUpload) 2045 2046 // output = false // set as false when this function is executed as sync. 2047 } else { 2048 return true 2049 } 2050 2051 // console.log('Returns: ' + output) 2052 // return output 2053 }) 2054 }, 2055 2056}) 2057 2058function recaptchaSubmit(token) { 2059 fetch('/apps/productproperties/recaptcha', { 2060 method: 'POST', 2061 body: 'recaptchatoken=' + token, 2062 headers: { 2063 'Content-Type': 'application/x-www-form-urlencoded' 2064 } 2065 }) 2066 .then((response) => response.json()) 2067 .then((data) => { 2068 if (data.success) { 2069 document.querySelector('#ProductPropertiesForm').dataset.prevent = 0 2070 2071 // Verification successful. Upload the file and add the item to the cart 2072 window.apps.QuickbutikProductProperties.uploadFiles().then(async (uploadSuccess) => { 2073 Alpine.store('cart').addItemToCart(window.bodyFormData) 2074 }) 2075 2076 try { 2077 // Reset after submit 2078 grecaptcha.reset() 2079 } catch (e) { 2080 console.log(e) 2081 } 2082 2083 document.querySelector('#ProductPropertiesForm').dataset.prevent = 1 2084 2085 return true 2086 } else { 2087 alert('ReCaptcha error: Please try again.') 2088 } 2089 }) 2090} 2091 2092 2093 2094</script>
vendor: 1 bytes, line 2094
2094
2095<script> 2096 qb_cookieconsent.run({ 2097 current_lang: 'en', 2098 autoclear_cookies: true, 2099 cookie_expiration: 90, 2100 autoload_css: true, 2101 theme_css: '/assets/shopassets/cookieconsent/cookieconsent.css?v=231', 2102 languages: { 2103 en: { 2104 consent_modal: { 2105 title: "Jag använder cookies", 2106 description: 'Jag använder cookies för att anpassa det innehåll som visas för dig och för att du ska få bästa tänkbara upplevelse när du handlar hos mig.', 2107 primary_btn: { 2108 text: 'Godkänn alla', 2109 role: 'accept_all' 2110 }, 2111 secondary_btn: { 2112 text: 'Inställningar', 2113 role: 'settings' 2114 } 2115 }, 2116 settings_modal: { 2117 title: 'Cookie inställningar', 2118 save_settings_btn: "Spara", 2119 accept_all_btn: "Godkänn alla", 2120 cookie_table_headers: [ 2121 { col1: "Name" }, 2122 { col2: "Domain" }, 2123 { col3: "Expiration" }, 2124 { col4: "Description" } 2125 ], 2126 blocks: qb_cookieconsent_blocks 2127 } 2128 } 2129 }, 2130 onAccept: function (cookie) { 2131 var event = new CustomEvent("QB_CookieConsentAccepted", { 2132 detail: { 2133 cookie: cookie 2134 } 2135 }); 2136 2137 document.dispatchEvent(event); 2138 }, 2139 onChange: function (cookie) { 2140 var event = new CustomEvent("QB_CookieConsentAccepted", { 2141 detail: { 2142 cookie: cookie 2143 } 2144 }); 2145 2146 document.dispatchEvent(event); 2147 }, 2148 }); 2149 2150</script>
2150 2151
2152<script type="text/javascript">window.registerApp({ 2153 name: 'Recaptcha', 2154 identifier: 'QuickbutikRecaptcha', 2155 description: 'Recaptcha', 2156 version: '1.1.0', 2157 pages: ['contact'], 2158 data: {}, 2159 config: {}, 2160 onMount: function ({ appData, page }) { 2161 console.debug(`${this.identifier}@onMount`) 2162 2163 let form = document.querySelector('form[role=form]') 2164 if (!form) { 2165 console.error('Aborting recaptcha, form not found.') 2166 return 2167 } 2168 2169 const createCaptchaDiv = () => { 2170 let captchaDiv = document.createElement('div'); 2171 captchaDiv.className = 'g-recaptcha' 2172 captchaDiv.dataset.sitekey = recaptchaSiteKey 2173 captchaDiv.style.marginBottom = '15px' 2174 captchaDiv.style.overflow = 'hidden' 2175 return captchaDiv 2176 } 2177 2178 let button = form.querySelector('button'); 2179 let inputSubmit = form.querySelector('input[type=submit]'); 2180 if (button) { 2181 button.insertAdjacentElement('beforebegin', createCaptchaDiv()) 2182 } else if (inputSubmit) { 2183 inputSubmit.insertAdjacentElement('beforebegin', createCaptchaDiv()) 2184 } 2185 2186 // Honeypot for contact page 2187 let contactForm = document.querySelector('form[role=form]#contact-form') 2188 if (contactForm) { 2189 let pottyDiv = document.createElement('div') 2190 pottyDiv.style.position = 'absolute' 2191 pottyDiv.style.left = '-5000px' 2192 pottyDiv.setAttribute('aria-hidden', true) 2193 2194 let codeLabel = document.createElement('label') 2195 codeLabel.innerText = 'Code' 2196 codeLabel.for = 'code' 2197 codeLabel.className = contactForm.querySelector('label').className 2198 codeLabel.setAttribute('aria-hidden', true) 2199 pottyDiv.appendChild(codeLabel) 2200 2201 let codeInput = document.createElement('input') 2202 codeInput.type = 'text' 2203 codeInput.id = 'code' 2204 codeInput.name = 'code' 2205 codeInput.tabIndex = -1 2206 codeInput.autocomplete = 'off' 2207 codeInput.className = contactForm.querySelector('input[name="name"]').className 2208 codeInput.setAttribute('aria-hidden', true) 2209 pottyDiv.appendChild(codeInput) 2210 2211 contactForm.querySelector('textarea').insertAdjacentElement('afterend', pottyDiv) 2212 } 2213 2214 // Inject Recaptcha script 2215 let existingScript = document.querySelector('script[src="https://www.google.com/recaptcha/api.js"]') 2216 if (!existingScript) { 2217 let script = document.createElement('script') 2218 script.src = 'https://www.google.com/recaptcha/api.js' 2219 script.type = 'text/javascript' 2220 script.async = true 2221 document.querySelector('head').appendChild(script) 2222 } 2223 2224 }, 2225}) 2226</script>
2226
2226<script src="/assets/shopassets/misc/magicbean.js?v=3" data-shop-id="88049" data-url="https://magic-bean.services.quickbutik.com/v1/sow" data-site-section="1" data-key="4fcf51edae99e817f86319ce902e3fe645cf628fe040178a720a95af0b90ceca" data-timestamp="1791436423" defer></script>
2226<script>;(function () { 2227 if (window.parent === window) return; 2228 2229 const urlParams = new URLSearchParams(window.location.search); 2230 if (urlParams.has('_qbdev')) { 2231 try { sessionStorage.setItem('_qbdev', '1'); } catch(e) {} 2232 } 2233 try { if (!sessionStorage.getItem('_qbdev')) return; } catch(e) { return; } 2234 2235 const report = () => { 2236 try { 2237 const params = new URLSearchParams(window.location.search); 2238 params.delete('_qbdev'); 2239 params.delete('_qbtid'); 2240 const qs = params.toString(); 2241 window.parent.postMessage( 2242 { type: 'navigation', path: window.location.pathname + (qs ? '?' + qs : '') }, 2243 '*' 2244 ); 2245 } catch (e) {} 2246 }; 2247 2248 report(); 2249 2250 const { pushState, replaceState } = history; 2251 history.pushState = function (...args) { 2252 const result = pushState.apply(this, args); 2253 report(); 2254 return result; 2255 }; 2256 history.replaceState = function (...args) { 2257 const result = replaceState.apply(this, args); 2258 report(); 2259 return result; 2260 }; 2261 window.addEventListener('popstate', report); 2262})()</script>
vendor: 1 bytes, line 2262
2262
2263<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v4bc70e2c01a94c73b74392e4234840661791215815920" integrity="sha512-L0ha0OXavK/8okipN9F8BtP84dg9DUhPERbBXzwI6dgTA55d2+yweo3pn5CSFYs45/r8md2+xvUPtTdvTNRfjA==" data-cf-beacon='{"version":"2024.11.0","token":"7a304e0c4e854d16bca3b20c50bbf8db","spa":2}' crossorigin="anonymous"></script>
2263 2264</body> 2265 2266</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.