PageSourceSearch

https://ettljusinorr.se/webbutik

html ettljusinorr.se collected 2026-10-08 05:13:43 UTC 103,343 bytes, 2,266 lines download raw bytes

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">&ndash; <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 = "&nbsp;"
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.