PageSourceSearch

https://uitgeverijpluim.nl/boeken/fictie

html uitgeverijpluim.nl collected 2026-09-24 19:10:20 UTC 205,603 bytes, 3,904 lines download raw bytes

1<!doctype html>
2<html lang="nl">
3<head >
4    <meta charset="utf-8"/>
5<meta name="title" content="Fictie  | Uitgeverij Pluim"/>
6<meta name="description" content="Uitgeverij Pluim"/>
7<meta name="keywords" content="boeken,uitgeverij,pluim"/>
8<meta name="robots" content="INDEX,FOLLOW"/>
9<meta name="viewport" content="width=device-width, initial-scale=1"/>
10<meta name="page-type" content="product-listing"/>
11<title>Fictie  | Uitgeverij Pluim</title>
12        <link rel="stylesheet" type="text/css" defer="defer" href="https://uitgeverijpluim.nl/static/version1790248052/frontend/Pluim/Hyva/nl_NL/css/styles.css" />
13<script type="text/javascript" src="https://uitgeverijpluim.nl/static/version1790248052/frontend/Pluim/Hyva/nl_NL/Smile_ElasticsuiteTracker/js/tracking.js"></script>
13
14<link rel="canonical" href="https://uitgeverijpluim.nl/boeken/fictie" />
15<link rel="icon" type="image/x-icon" href="https://uitgeverijpluim.nl/media/favicon/websites/1/logo_sxfncc_1.jpg" />
16<link rel="shortcut icon" type="image/x-icon" href="https://uitgeverijpluim.nl/media/favicon/websites/1/logo_sxfncc_1.jpg" />
17<link rel="dns-prefetch" href="https://acdn.adnxs.com">
18<link rel="dns-prefetch" href="https://ib.adnxs.com">
19
20<!-- Xandr Universal Pixel - Optimized -->
21<script async defer>
22(function() {
23  // Initialize pixie stub immediately
24  !function(e,i){
25    if(!e.pixie){
26      var n=e.pixie=function(e,i,a){
27        n.actionQueue.push({action:e,actionValue:i,params:a})
28      };
29      n.actionQueue=[];
30      var a=i.createElement("script");
31      a.async=!0;
32      a.src="https://acdn.adnxs.com/dmp/up/pixie.js";
33      var t=i.getElementsByTagName("head")[0];
34      t.insertBefore(a,t.firstChild)
35    }
36  }(window,document);
37  
38  // Queue consent and initialization
39  pixie('consent', 'default', { 'ad_storage': 'granted', 'wait_for_update': 1000});
40  pixie('init', 'cf677036-49f6-4ee6-8255-72e7d7a8944c');
41  pixie('event', 'PageView');
42})();
43</script>
43    
44<script nonce="eHFmYXJhOTZ4MG52czd4OGRxa2s3b2M4Z2RvdTVydGI=">
45    window.YIREO_GOOGLETAGMANAGER2_ENABLED = true;
46    (function (events) {
47        const initYireoGoogleTagManager2 = function () {
48            events.forEach(function (eventType) {
49                window.removeEventListener(eventType, initYireoGoogleTagManager2);
50            });
51
52                        (function (w, d, s, l, i) {
53                w[l] = w[l] || [];
54                w[l].push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
55                var f = d.getElementsByTagName(s)[0],
56                    j = d.createElement(s),
57                    dl = l != 'dataLayer' ? '&l=' + l : '';
58                j.async = true;
59                j.src = 'https\u003A\u002F\u002Fwww.googletagmanager.com' + '/gtm.js?id=' + i + dl;
60                f.parentNode.insertBefore(j, f);
61            })(window, document, 'script', 'dataLayer', 'GTM\u002DWDGVFB9');
62                    };
63
64        events.forEach(function (eventType) {
65            window.addEventListener(eventType, initYireoGoogleTagManager2, {once: true, passive: true})
66        });
67    })(['load', 'keydown', 'mouseover', 'scroll', 'touchstart', 'wheel']);
68
69    </script>
vendor: 1 bytes, line 69
69
70<script>
71    (function( hyva, undefined ) {
72
73        function lifetimeToExpires(options, defaults) {
74
75            const lifetime = options.lifetime || defaults.lifetime;
76
77            if (lifetime) {
78                const date = new Date;
79                date.setTime(date.getTime() + lifetime * 1000);
80                return date;
81            }
82
83            return null;
84        }
85
86        function generateRandomString() {
87
88            const allowedCharacters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
89                length = 16;
90
91            let formKey = '',
92                charactersLength = allowedCharacters.length;
93
94            for (let i = 0; i < length; i++) {
95                formKey += allowedCharacters[Math.round(Math.random() * (charactersLength - 1))]
96            }
97
98            return formKey;
99        }
100
101        const sessionCookieMarker = {noLifetime: true}
102
103        const cookieTempStorage = {};
104
105        const internalCookie = {
106            get(name) {
107                const v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)');
108                return v ? v[2] : null;
109            },
110            set(name, value, days, skipSetDomain) {
111                let expires,
112                    path,
113                    domain,
114                    secure,
115                    samesite;
116
117                const defaultCookieConfig = {
118                    expires: null,
119                    path: '/',
120                    domain: null,
121                    secure: false,
122                    lifetime: null,
123                    samesite: 'lax'
124                };
125
126                const cookieConfig = window.COOKIE_CONFIG || {};
127
128                expires = days && days !== sessionCookieMarker
129                    ? lifetimeToExpires({lifetime: 24 * 60 * 60 * days, expires: null}, defaultCookieConfig)
130                    : lifetimeToExpires(window.COOKIE_CONFIG, defaultCookieConfig) || defaultCookieConfig.expires;
131
132                path = cookieConfig.path || defaultCookieConfig.path;
133                domain = !skipSetDomain && (cookieConfig.domain || defaultCookieConfig.domain);
134                secure = cookieConfig.secure || defaultCookieConfig.secure;
135                samesite = cookieConfig.samesite || defaultCookieConfig.samesite;
136
137                document.cookie = name + "=" + encodeURIComponent(value) +
138                    (expires && days !== sessionCookieMarker ? '; expires=' + expires.toGMTString() : '') +
139                    (path ? '; path=' + path : '') +
140                    (domain ? '; domain=' + domain : '') +
141                    (secure ? '; secure' : '') +
142                    (samesite ? '; samesite=' + samesite : 'lax');
143            },
144            isWebsiteAllowedToSaveCookie() {
145                const allowedCookies = this.get('user_allowed_save_cookie');
146                if (allowedCookies) {
147                    const allowedWebsites = JSON.parse(unescape(allowedCookies));
148
149                    return allowedWebsites[CURRENT_WEBSITE_ID] === 1;
150                }
151                return false;
152            },
153            getGroupByCookieName(name) {
154                const cookieConsentConfig = window.cookie_consent_config || {};
155                let group = null;
156                for (let prop in cookieConsentConfig) {
157                    if (!cookieConsentConfig.hasOwnProperty(prop)) continue;
158                    if (cookieConsentConfig[prop].includes(name)) {
159                        group = prop;
160                        break;
161                    }
162                }
163                return group;
164            },
165            isCookieAllowed(name) {
166                const cookieGroup = this.getGroupByCookieName(name);
167                return cookieGroup
168                    ? window.cookie_consent_groups[cookieGroup]
169                    : this.isWebsiteAllowedToSaveCookie();
170            },
171            saveTempStorageCookies() {
172                for (const [name, data] of Object.entries(cookieTempStorage)) {
173                    if (this.isCookieAllowed(name)) {
174                        this.set(name, data['value'], data['days'], data['skipSetDomain']);
175                        delete cookieTempStorage[name];
176                    }
177                }
178            }
179        };
180
181        hyva.getCookie = (name) => {
182            const cookieConfig = window.COOKIE_CONFIG || {};
183
184            if (cookieConfig.cookie_restriction_enabled && ! internalCookie.isCookieAllowed(name)) {
185                return cookieTempStorage[name] ? cookieTempStorage[name]['value'] : null;
186            }
187
188            return internalCookie.get(name);
189        }
190
191        hyva.setCookie = (name, value, days, skipSetDomain) => {
192            const cookieConfig = window.COOKIE_CONFIG || {};
193
194            if (cookieConfig.cookie_restriction_enabled && ! internalCookie.isCookieAllowed(name)) {
195                cookieTempStorage[name] = {value, days, skipSetDomain};
196                return;
197            }
198            return internalCookie.set(name, value, days, skipSetDomain);
199        }
200
201
202        hyva.setSessionCookie = (name, value, skipSetDomain) => {
203            return hyva.setCookie(name, value, sessionCookieMarker, skipSetDomain)
204        }
205
206        hyva.getBrowserStorage = () => {
207            const browserStorage = window.localStorage || window.sessionStorage;
208            if (!browserStorage) {
209                console.warn('Browser Storage is unavailable');
210                return false;
211            }
212            try {
213                browserStorage.setItem('storage_test', '1');
214                browserStorage.removeItem('storage_test');
215            } catch (error) {
216                console.warn('Browser Storage is not accessible', error);
217                return false;
218            }
219            return browserStorage;
220        }
221
222        hyva.postForm = (postParams) => {
223            const form = document.createElement("form");
224
225            let data = postParams.data;
226
227            if (! postParams.skipUenc && ! data.uenc) {
228                data.uenc = btoa(window.location.href);
229            }
230            form.method = "POST";
231            form.action = postParams.action;
232
233            Object.keys(postParams.data).map(key => {
234                const field = document.createElement("input");
235                field.type = 'hidden'
236                field.value = postParams.data[key];
237                field.name = key;
238                form.appendChild(field);
239            });
240
241            const form_key = document.createElement("input");
242            form_key.type = 'hidden';
243            form_key.value = hyva.getFormKey();
244            form_key.name="form_key";
245            form.appendChild(form_key);
246
247            document.body.appendChild(form);
248
249            form.submit();
250        }
251
252        hyva.getFormKey = function () {
253            let formKey = hyva.getCookie('form_key');
254
255            if (!formKey) {
256                formKey = generateRandomString();
257                hyva.setCookie('form_key', formKey);
258            }
259
260            return formKey;
261        }
262
263        hyva.formatPrice = (value, showSign, options = {}) => {
264            const groupSeparator = options.groupSeparator;
265            const decimalSeparator = options.decimalSeparator
266            delete options.groupSeparator;
267            delete options.decimalSeparator;
268            const formatter = new Intl.NumberFormat(
269                'nl\u002DNL',
270                Object.assign({
271                    style: 'currency',
272                    currency: 'EUR',
273                    signDisplay: showSign ? 'always' : 'auto'
274                }, options)
275            );
276            return (typeof Intl.NumberFormat.prototype.formatToParts === 'function') ?
277                formatter.formatToParts(value).map(({type, value}) => {
278                    switch (type) {
279                        case 'currency':
280                            return '\u20AC' || value;
281                        case 'minusSign':
282                            return '- ';
283                        case 'plusSign':
284                            return '+ ';
285                        case 'group':
286                            return groupSeparator !== undefined ? groupSeparator : value;
287                        case 'decimal':
288                            return decimalSeparator !== undefined ? decimalSeparator : value;
289                        default :
290                            return value;
291                    }
292                }).reduce((string, part) => string + part) :
293                formatter.format(value);
294        }
295
296        const formatStr = function (str, nStart) {
297            const args = Array.from(arguments).slice(2);
298
299            return str.replace(/(%+)([0-9]+)/g, (m, p, n) => {
300                const idx = parseInt(n) - nStart;
301
302                if (args[idx] === null || args[idx] === void 0) {
303                    return m;
304                }
305                return p.length % 2
306                    ? p.slice(0, -1).replace('%%', '%') + args[idx]
307                    : p.replace('%%', '%') + n;
308            })
309        }
310
311        hyva.str = function (string) {
312            const args = Array.from(arguments);
313            args.splice(1, 0, 1);
314
315            return formatStr.apply(undefined, args);
316        }
317
318        hyva.strf = function () {
319            const args = Array.from(arguments);
320            args.splice(1, 0, 0);
321
322            return formatStr.apply(undefined, args);
323        }
324
325        /**
326         * Take a html string as `content` parameter and
327         * extract an element from the DOM to replace in
328         * the current page under the same selector,
329         * defined by `targetSelector`
330         */
331        hyva.replaceDomElement = (targetSelector, content) => {
332            // Parse the content and extract the DOM node using the `targetSelector`
333            const parser = new DOMParser();
334            const doc = parser.parseFromString(content, 'text/html');
335            const contentNode = doc.querySelector(targetSelector);
336
337            // Bail if content or target can't be found
338            if (!contentNode || !document.querySelector(targetSelector)) {
339                return;
340            }
341
342                        hyva.activateScripts(contentNode);
343            
344            // Replace the old DOM node with the new content
345            document.querySelector(targetSelector).replaceWith(contentNode);
346
347            // Reload customerSectionData and display cookie-messages if present
348            window.dispatchEvent(new CustomEvent("reload-customer-section-data"));
349            hyva.initMessages();
350        }
351
352        hyva.removeScripts = (contentNode) => {
353            const scripts = contentNode.getElementsByTagName('script');
354            for (let i = 0; i < scripts.length; i++) {
355                scripts[i].parentNode.removeChild(scripts[i]);
356            }
357            const templates = contentNode.getElementsByTagName('template');
358            for (let i = 0; i < templates.length; i++) {
359                const container = document.createElement('div');
360                container.innerHTML = templates[i].innerHTML;
361                hyva.removeScripts(container);
362                templates[i].innerHTML = container.innerHTML;
363            }
364        }
365
366        hyva.activateScripts = (contentNode) => {
367            // Create new array from HTMLCollection to avoid mutation of collection while manipulating the DOM.
368            const scripts = Array.from(contentNode.getElementsByTagName('script'));
369
370            // Iterate over all script tags to duplicate+inject each into the head
371            for (const original of scripts) {
372                const script = document.createElement('script');
373                original.type && (script.type = original.type);
374                script.innerHTML = original.innerHTML;
375
376                // Remove the original (non-executing) script from the contentNode
377                original.parentNode.removeChild(original)
378
379                // Add script to head
380                document.head.appendChild(script);
381            }
382
383            return contentNode;
384        }
385
386        const replace = {['+']: '-', ['/']: '_', ['=']: ','};
387        hyva.getUenc = () => btoa(window.location.href).replace(/[+/=]/g, match => replace[match]);
388
389        let currentTrap;
390
391        const focusableElements = (rootElement) => {
392            const selector = 'button, [href], input, select, textarea, details, [tabindex]:not([tabindex="-1"]';
393            return Array.from(rootElement.querySelectorAll(selector))
394                .filter(el => {
395                    return el.style.display !== 'none'
396                        && !el.disabled
397                        && el.tabIndex !== -1
398                        && (el.offsetWidth || el.offsetHeight || el.getClientRects().length)
399                })
400        }
401
402        const focusTrap = (e) => {
403            const isTabPressed = e.key === 'Tab' || e.keyCode === 9;
404            if (!isTabPressed) return;
405
406            const focusable = focusableElements(currentTrap)
407            const firstFocusableElement = focusable[0]
408            const lastFocusableElement = focusable[focusable.length - 1]
409
410            e.shiftKey
411                ? document.activeElement === firstFocusableElement && (lastFocusableElement.focus(), e.preventDefault())
412                : document.activeElement === lastFocusableElement && (firstFocusableElement.focus(), e.preventDefault())
413        };
414
415        hyva.releaseFocus = (rootElement) => {
416            if (currentTrap && (!rootElement || rootElement === currentTrap)) {
417                currentTrap.removeEventListener('keydown', focusTrap)
418                currentTrap = null
419            }
420        }
421        hyva.trapFocus = (rootElement) => {
422            if (!rootElement) return;
423            hyva.releaseFocus()
424            currentTrap = rootElement
425            rootElement.addEventListener('keydown', focusTrap)
426            const firstElement = focusableElements(rootElement)[0]
427            firstElement && firstElement.focus()
428        }
429
430        hyva.safeParseNumber = (rawValue) => {
431            const number = rawValue ? parseFloat(rawValue) : null;
432
433            return Array.isArray(number) || isNaN(number) ? rawValue : number;
434        }
435
436        const toCamelCase = s => s.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join('');
437        hyva.createBooleanObject = (name, value = false, additionalMethods = {}) => {
438
439            const camelCase = toCamelCase(name);
440            const key = '__hyva_bool_' + name
441
442            return new Proxy(Object.assign(
443                additionalMethods,
444                {
445                    [key]: !!value,
446                    [name]() {return !!this[key]},
447                    ['!' + name]() {return !this[key]}, // @deprecated This does not work with non-CSP Alpine
448                    ['not' + camelCase]() {return !this[key]},
449                    ['toggle' + camelCase]() {this[key] = !this[key]},
450                    [`set${camelCase}True`]() {this[key] = true},
451                    [`set${camelCase}False`]() {this[key] = false},
452                }
453            ), {
454                set(target, prop, value) {
455                    return prop === name
456                        ? (target[key] = !!value)
457                        : Reflect.set(...arguments);
458                }
459            })
460        }
461
462                hyva.alpineInitialized = (fn) => window.addEventListener('alpine:initialized', fn, {once: true})
463        window.addEventListener('alpine:init', () => Alpine.data('{}', () => ({})), {once: true});
464                window.addEventListener('user-allowed-save-cookie', () => internalCookie.saveTempStorageCookies())
465
466    }( window.hyva = window.hyva || {} ));
467    //# sourceURL=hyva.js
468</script>
vendor: 1 bytes, line 468
468
469<script>
470    if (HTMLScriptElement.supports && HTMLScriptElement.supports('speculationrules') && document.body) {
471        const specScript = document.createElement('script');
472        specScript.type = 'speculationrules';
473        specRules = {
474            'prerender': [{
475                'where': {
476                    'and': [
477                        { 'href_matches': '/*' },
478                        { "not": { "href_matches": "/(customer|checkout|wishlist)*" } },
479                        { 'not': { 'selector_matches': '.do-not-prerender'}},
480                    { 'not': { 'selector_matches': '[rel~=nofollow]' } }
481                    ]
482                },
483                'eagerness': 'moderate'
484            }]
485        };
486        specScript.textContent = JSON.stringify(specRules);
487        document.body.append(specScript);
488    }
489</script>
489
490</head>
491<body id="html-body" class="page-with-filter page-products categorypath-boeken-fictie category-fictie catalog-category-view page-layout-2columns-left">
vendor: 68 bytes, lines 491-492
491
492<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=
492GTM-WDGVFB9
vendor: 83 bytes, line 492
492" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
493<script>
494    var BASE_URL = 'https://uitgeverijpluim.nl/';
495    var THEME_PATH = 'https://uitgeverijpluim.nl/static/version1790248052/frontend/Pluim/Hyva/nl_NL';
496    var COOKIE_CONFIG = {
497        "expires": null,
498        "path": "\u002F",
499        "domain": ".uitgeverijpluim.nl",
500        "secure": false,
501        "lifetime": "3600",
502        "cookie_restriction_enabled": false    };
503    var CURRENT_STORE_CODE = 'default';
504    var CURRENT_WEBSITE_ID = '1';
505
506    window.hyva = window.hyva || {}
507
508    window.cookie_consent_groups = window.cookie_consent_groups || {}
509    window.cookie_consent_groups['necessary'] = true;
510
511    window.cookie_consent_config = window.cookie_consent_config || {};
512    window.cookie_consent_config['necessary'] = [].concat(
513        window.cookie_consent_config['necessary'] || [],
514        [
515            'user_allowed_save_cookie',
516            'form_key',
517            'mage-messages',
518            'private_content_version',
519            'mage-cache-sessid'
520        ]
521    );
522</script>
522
523<input name="form_key" type="hidden" value="Ueui8SM3dgSPd5qv" />
523<script nonce="eHFmYXJhOTZ4MG52czd4OGRxa2s3b2M4Z2RvdTVydGI=">
524    function yireoGoogleTagManager2Pusher(eventData, message, callback) {
525        window.YIREO_GOOGLETAGMANAGER2_PAST_EVENTS = window.YIREO_GOOGLETAGMANAGER2_PAST_EVENTS || [];
526
527        function doCallback(cb) {
528            if (undefined === cb) {
529                return;
530            }
531
532            cb();
533        }
534
535
536        const copyEventData = Object.assign({}, eventData);
537        let metaData = {};
538        if (copyEventData.meta) {
539            metaData = copyEventData.meta;
540            delete copyEventData.meta;
541        }
542
543        const eventHash = btoa(encodeURIComponent(JSON.stringify(copyEventData)));
544        if (window.YIREO_GOOGLETAGMANAGER2_PAST_EVENTS.includes(eventHash)) {
545            yireoGoogleTagManager2Logger('Warning: Event already triggered', eventData);
546            doCallback(callback);
547            return;
548        }
549
550        if (metaData && metaData.allowed_pages && metaData.allowed_pages.length > 0
551            && false === metaData.allowed_pages.some(page => window.location.pathname.includes(page))) {
552            yireoGoogleTagManager2Logger('Warning: Skipping event, not in allowed pages', window.location.pathname, eventData);
553            doCallback(callback);
554            return;
555        }
556
557        if (metaData && metaData.allowed_events && metaData.allowed_events.length > 0) {
558            for (const [allowedEventKey, allowedEvent] of Object.entries(metaData.allowed_events)) {
559                window.addEventListener(allowedEvent, function () {
560                    const eventDataCopy = Object.assign({}, eventData);
561                    eventDataCopy.meta.allowed_events = false;
562                    yireoGoogleTagManager2Pusher(eventDataCopy, 'push (allowed event "' + eventData.event + '") [script-pusher]');
563                });
564            }
565
566            yireoGoogleTagManager2Logger('Warning: Skipping event, not in allowed events', window.location.pathname, eventData);
567            doCallback(callback);
568            return;
569        }
570
571        if (!message) {
572            message = 'push (unknown) [unknown]';
573        }
574
575        yireoGoogleTagManager2Logger(message, eventData);
576        window.dataLayer = window.dataLayer || [];
577
578        if (eventData.ecommerce) {
579            window.dataLayer.push({ecommerce: null});
580        }
581
582        try {
583            window.dataLayer.push(eventData);
584            window.YIREO_GOOGLETAGMANAGER2_PAST_EVENTS.push(eventHash);
585        } catch(error) {
586            doCallback(callback);
587        }
588
589        doCallback(callback);
590    }
591</script>
vendor: 1 bytes, line 591
591
592<script nonce="eHFmYXJhOTZ4MG52czd4OGRxa2s3b2M4Z2RvdTVydGI=">
593    function yireoGoogleTagManager2Logger(...args) {
594        const debug = window.YIREO_GOOGLETAGMANAGER2_DEBUG || false;
595        if (false === debug) {
596            return;
597        }
598
599        var color = 'gray';
600        if (args[0].toLowerCase().startsWith('push')) {
601            color = 'green';
602        }
603
604        if (args[0].toLowerCase().startsWith('warning')) {
605            color = 'orange';
606        }
607
608        var css = 'color:white; background-color:' + color + '; padding:1px;'
609        console.log('%cYireo_GoogleTagManager2', css, ...args);
610    }
611</script>
611
612
613<script>
614    (function(hyva) {
615                const formValidationRules = {
616            required(value, options, field, context) {
617                const el = field.element.type === 'hidden' ? createTextInputFrom(field.element) : field.element,
618                    fieldName = getFieldName(field),
619                    msg = fieldName
620                        ? hyva.str('Het\u0020veld\u0020\u00251\u0020is\u0020verplicht.', fieldName)
621                        : 'Dit\u0020is\u0020een\u0020verplicht\u0020veld.';
622
623                if (el.type === 'radio' || el.type === 'checkbox') {
624                    return (value === undefined || value.length === 0) ? msg : true;
625                }
626
627                el.setAttribute('required', '');
628                el.checkValidity();
629
630                return el.validity.valueMissing ? msg : true;
631            },
632            maxlength(value, options, field, context) {
633                const n = Number(options)
634                if (value.length > n) {
635                    const fieldName = getFieldName(field);
636                    return fieldName
637                        ? n === 1
638                            ? hyva.str('Het\u0020veld\u0020\u00251\u0020mag\u0020niet\u0020langer\u0020zijn\u0020dan\u00201\u0020teken.', fieldName)
639                            : hyva.str('Het\u0020veld\u0020\u00251\u0020mag\u0020niet\u0020langer\u0020zijn\u0020dan\u0020\u00252\u0020tekens.', fieldName, options)
640                        : n === 1
641                            ? 'Voer\u0020niet\u0020meer\u0020dan\u00201\u0020teken\u0020in.'
642                            : hyva.strf('Voer\u0020niet\u0020meer\u0020dan\u0020\u00250\u0020tekens\u0020in.', options);
643                }
644                return true;
645            },
646            minlength(value, options, field, context) {
647                const n = Number(options)
648                if (value.length > 0 && value.length < n) {
649                    const fieldName = getFieldName(field);
650                    return fieldName
651                        ? n === 1
652                            ? hyva.str('Het\u0020veld\u0020\u00251\u0020moet\u0020minimaal\u00201\u0020teken\u0020lang\u0020zijn.', fieldName)
653                            : hyva.str('Het\u0020veld\u0020\u00251\u0020moet\u0020minimaal\u0020\u00252\u0020tekens\u0020lang\u0020zijn.', fieldName, options)
654                        : n === 1
655                            ? 'Voer\u0020minimaal\u00201\u0020teken\u0020in.'
656                            : hyva.strf('Voer\u0020minimaal\u0020\u00250\u0020tekens\u0020in.', options);
657                }
658                return true;
659            },
660            max(value, options, field, context) {
661                field.element.setAttribute('max', options);
662                field.element.checkValidity();
663                if (field.element.validity.rangeOverflow) {
664                    const fieldName = getFieldName(field);
665                    return fieldName
666                        ? hyva.str('Het\u0020veld\u0020\u00251\u0020moet\u0020een\u0020waarde\u0020kleiner\u0020dan\u0020of\u0020gelijk\u0020aan\u0020\u0022\u00252\u0022\u0020bevatten.', fieldName, options)
667                        : hyva.strf('Voer\u0020een\u0020waarde\u0020kleiner\u0020dan\u0020of\u0020gelijk\u0020aan\u0020\u0022\u00250\u0022\u0020in.', options);
668                }
669                return true;
670            },
671            min(value, options, field, context) {
672                field.element.setAttribute('min', options);
673                field.element.checkValidity();
674                if (field.element.validity.rangeUnderflow) {
675                    const fieldName = getFieldName(field);
676                    return fieldName
677                        ? hyva.str('Het\u0020veld\u0020\u00251\u0020moet\u0020een\u0020waarde\u0020groter\u0020dan\u0020of\u0020gelijk\u0020aan\u0020\u0022\u00252\u0022\u0020bevatten.', fieldName, options)
678                        : hyva.strf('Voer\u0020een\u0020waarde\u0020groter\u0020dan\u0020of\u0020gelijk\u0020aan\u0020\u0022\u00250\u0022\u0020in.', options);
679                }
680                return true;
681            },
682            step(value, options, field, context) {
683                field.element.setAttribute('step', options);
684                field.element.checkValidity();
685                if (field.element.validity.stepMismatch) {
686                    const val = Number(value),
687                        step = Number(options),
688                        fieldName = getFieldName(field),
689                        nearestLowerValue = Math.floor(val / step) * step,
690                        nearestUpperValue = Math.ceil(val / step) * step;
691                    return fieldName
692                        ? hyva.str('Het\u0020veld\u0020\u00251\u0020is\u0020ongeldig.\u0020De\u0020twee\u0020dichtstbijzijnde\u0020geldige\u0020waarden\u0020zijn\u0020\u0022\u00252\u0022\u0020en\u0020\u0022\u00253\u0022.', fieldName, nearestLowerValue, nearestUpperValue)
693                        : hyva.strf('Voer\u0020een\u0020geldige\u0020waarde\u0020in.\u0020De\u0020twee\u0020dichtstbijzijnde\u0020geldige\u0020waarden\u0020zijn\u0020\u0022\u00250\u0022\u0020en\u0020\u0022\u00251\u0022.', nearestLowerValue, nearestUpperValue);
694                }
695                return true;
696            },
697            pattern(value, options, field, context) {
698                field.element.setAttribute('pattern', options);
699                field.element.checkValidity();
700                if (field.element.validity.patternMismatch) {
701                    const fieldName = getFieldName(field);
702                    return fieldName
703                        ? field.element.pattern
704                            ? hyva.str('Het\u0020veld\u0020\u00251\u0020moet\u0020overeenkomen\u0020met\u0020het\u0020gevraagde\u0020formaat\u003A\u0020\u00252.', fieldName, field.element.pattern)
705                            : hyva.str('Het\u0020veld\u0020\u00251\u0020moet\u0020overeenkomen\u0020met\u0020het\u0020gevraagde\u0020formaat.', fieldName)
706                        : field.element.pattern
707                            ? hyva.strf('Houd\u0020je\u0020aan\u0020het\u0020gevraagde\u0020formaat\u003A\u0020\u00250.', field.element.pattern)
708                            : 'Houd\u0020je\u0020aan\u0020het\u0020gevraagde\u0020formaat.';
709                }
710                return true;
711            },
712            email(value, options, field, context) {
713                                const rule = /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i;
714                if (value.length > 0 && !rule.test(value)) {
715                    const fieldName = getFieldName(field);
716                    return fieldName
717                        ? hyva.str('Het\u0020veld\u0020\u00251\u0020moet\u0020een\u0020geldig\u0020e\u002Dmailadres\u0020bevatten\u0020\u0028bijv.\u0020naam\u0040domein.nl\u0029.', fieldName)
718                        : 'Voer\u0020een\u0020geldig\u0020e\u002Dmailadres\u0020in\u0020\u0028Bijv.\u003A\u0020johndoe\u0040domain.com\u0029.';
719                }
720                return true;
721            },
722            password(value, options, field, context) {
723                const rule = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/;
724                if (value.length > 0 && !rule.test(value)) {
725                    const fieldName = getFieldName(field);
726                    return fieldName
727                        ? hyva.str('Het\u0020veld\u0020\u00251\u0020moet\u0020minimaal\u0020\u00E9\u00E9n\u0020hoofdletter,\u0020\u00E9\u00E9n\u0020kleine\u0020letter,\u0020\u00E9\u00E9n\u0020cijfer\u0020en\u0020\u00E9\u00E9n\u0020speciaal\u0020teken\u0020bevatten\u0020\u0028\u0023\u003F\u0021\u0040\u0024\u0025\u005E\u0026\u002A\u002D\u0029', fieldName)
728                        : 'Voer\u0020minimaal\u0020\u00E9\u00E9n\u0020hoofdletter,\u0020\u00E9\u00E9n\u0020kleine\u0020letter,\u0020\u00E9\u00E9n\u0020cijfer\u0020en\u0020\u00E9\u00E9n\u0020speciaal\u0020teken\u0020in\u0020\u0028\u0023\u003F\u0021\u0040\u0024\u0025\u005E\u0026\u002A\u002D\u0029';
729                }
730                return true;
731            },
732            equalTo(value, options, field, context) {
733                const dependencyField = context.fields[options].element;
734                if (value !== dependencyField.value) {
735                    const dependencyFieldName =
736                        dependencyField.label ||
737                        dependencyField.title ||
738                        (dependencyField.labels && dependencyField.labels[0] && dependencyField.labels[0].innerText) ||
739                        dependencyField.name,
740                        fieldName = getFieldName(field);
741                    return fieldName
742                        ? hyva.str('De\u0020waarde\u0020van\u0020\u00251\u0020moet\u0020gelijk\u0020zijn\u0020aan\u0020\u0022\u00252\u0022.', fieldName, dependencyFieldName)
743                        : hyva.strf('De\u0020waarde\u0020van\u0020dit\u0020veld\u0020moet\u0020gelijk\u0020zijn\u0020aan\u0020\u0022\u00250\u0022.', dependencyFieldName);
744                }
745                return true;
746            }
747        };
748
749                function raceSome(promises, pred) {
750            return new Promise((resolve, reject) => {
751
752                if (promises.length === 0) {
753                    return resolve();
754                }
755
756                let settled = false, nDone = 0;
757
758                const resolveIf = v => {
759                    if (!settled && (pred(v) || ++nDone === promises.length)) {
760                        settled = true;
761                        resolve(v);
762                    }
763                    return v;
764                }
765
766                promises.map(promise => {
767                    promise.then(resolveIf).catch(reason => {
768                        settled = true;
769                        reject(reason)
770                    });
771                    return promise;
772                });
773            });
774        }
775
776        const INPUT_ATTRIBUTE_RULES = {min: 'min', max: 'max', required: 'required', minlength: 'minlength', maxlength: 'maxlength', step: 'step', pattern: 'pattern'}
777        const INPUT_TYPE_RULES = {email: 'email'}
778
779        function getRules(element) {
780            let rules = {};
781            Object.keys(INPUT_ATTRIBUTE_RULES).forEach(attrName => {
782                if (element.hasAttribute(attrName)) {
783                    rules[INPUT_ATTRIBUTE_RULES[attrName]] = element.getAttribute(attrName);
784                }
785            })
786            if (INPUT_TYPE_RULES[element.type]) {
787                rules[INPUT_TYPE_RULES[element.type]] = true;
788            }
789
790            if (element.dataset.validate) {
791                try {
792                    Object.assign(rules, JSON.parse(element.dataset.validate));
793                } catch (error) {
794                    console.error('Validator error. Cannot parse data-validate attribute of element:\n', element);
795                }
796            }
797
798            return rules;
799        }
800
801        function isInvalidRuleResult(ruleState) {
802            return typeof ruleState === 'string' || !ruleState || (ruleState.type && ruleState.content);
803        }
804
805        async function runValidateFn(rule, options, value, field) {
806            return formValidationRules[rule](value, options, field, this);
807        }
808
809        function generateId() {
810            let id;
811            do {
812                id = `${this.idPrefix}-${++this.idSeq}`;
813            } while (document.getElementById(id));
814            return id;
815        }
816
817        function isVisible(element) {
818            const el = element.type !== 'hidden' ? element : (element.parentElement || {});
819            return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length)
820        }
821
822        function elementWillValidate(element) {
823            return (element.willValidate || element.type === 'hidden')
824                && element.tagName !== 'BUTTON'
825                && element.disabled === false
826                && !(element.tagName === 'INPUT' && element.type === 'submit')
827                && (element.hasAttribute('data-validate-hidden') || isVisible(element))
828        }
829
830        function createMessageContainer(el, fieldWrapperClassName) {
831            if (! el.parentElement) {
832                return;
833            }
834            const refocus = document.activeElement === el;
835            const wrapper = document.createElement('div');
836            wrapper.classList.add.apply(wrapper.classList, fieldWrapperClassName.split(' '));
837            el.parentElement.insertBefore(wrapper, el);
838            wrapper.appendChild(el);
839            refocus && document.activeElement !== el && el.focus();
840            return wrapper;
841        }
842
843        function containerNotFound(selector, el) {
844            const msg = `Cannot find message container element ${selector} of ${el.name}`;
845            console.error(msg, el);
846            throw msg;
847        }
848
849        function createTextInputFrom(el) {
850            const text = document.createElement('INPUT');
851            text.type = 'text';
852            text.value = el.value;
853            return text;
854        }
855
856        function classNamesToSelector(classNames) {
857            return classNames.split(' ')
858                .filter(className => className.length > 0)
859                .map(className => `.${className}`)
860                .join('')
861        }
862
863        function hasMessagesWrapper(field, messagesWrapperClassName) {
864            return this.getMessageContainer(field).querySelector(classNamesToSelector(messagesWrapperClassName));
865        }
866
867        function getMessagesWrapper(field, messagesWrapperClassName) {
868            if (hasMessagesWrapper.call(this, field, messagesWrapperClassName)) {
869                return this.getMessageContainer(field).querySelector(classNamesToSelector(messagesWrapperClassName));
870            }
871
872            const msgWrapper = document.createElement('ul');
873            const msgId = generateId.call(this);
874            msgWrapper.id = msgId;
875            field.element.setAttribute('aria-errormessage', msgId);
876            field.element.setAttribute('aria-describedby', msgId);
877            msgWrapper.classList.add.apply(msgWrapper.classList, messagesWrapperClassName.split(' '));
878            if (field.validateOnChange) {
879                msgWrapper.setAttribute('aria-live', 'polite');
880            }
881            this.getMessageContainer(field).appendChild(msgWrapper);
882
883            return msgWrapper;
884        }
885
886        function getCheckedValues(field) {
887            const name = field.element.name.replace(/([\\"])/g, '\\$1');
888            const elements = field.element.form.querySelectorAll('input[name="' + name + '"]:checked');
889            return Array.from(elements).map(el => el.value);
890        }
891
892        function escapeHtml(s) {
893            const div = document.createElement('div')
894            div.innerText = s;
895            return div.innerHTML;
896        }
897
898        
899        function formValidation(form, options = {}) {
900            const formElement = form || this.$el;
901
902            if (formElement.dataset && formElement.dataset.options) {
903                try {
904                    options = Object.assign(options, JSON.parse(formElement.dataset.options || '{}'));
905                } catch (e) {
906                    throw new Error('Cannot read the form options from the data-options attribute: not val
906id JSON');
907                }
908            }
909
910            // Disable browser default validation
911            if (formElement.tagName === 'FORM') {
912                formElement.setAttribute('novalidate', '');
913            } else {
914                console.error('formValidation can be initialized only on FORM element', formElement);
915                return;
916            }
917
918            options = Object.assign({
919                fieldWrapperClassName: 'field field-reserved',
920                messagesWrapperClassName: 'messages',
921                validClassName: 'field-success',
922                invalidClassName: 'field-error',
923                pageMessagesWrapperSelector: null,
924                scrollToFirstError: true,
925            }, options || {});
926
927            return {
928                state: {
929                    valid: false,
930                },
931                isSubmitting: false,
932                fields: {},
933                idSeq: 0,
934                idPrefix: formElement.id || 'vld-msg',
935                setupFields(elements) {
936                    this.fields = {};
937                    Array.from(elements).forEach(element => {
938                        if (elementWillValidate(element)) {
939                            this.setupField(element);
940                        }
941                    });
942                },
943                setupField(element) {
944                    if (! element) return;
945                    const onChange = !!element.dataset.onChange;
946                    if (elementWillValidate(element)) {
947                        const rules = getRules(element);
948                        if (Object.keys(rules).length > 0) {
949                            if (this.fields[element.name]) {
950                                Object.assign(this.fields[element.name].rules, rules);
951                            } else {
952                                this.fields[element.name] = {
953                                    element,
954                                    rules: rules,
955                                    validateOnChange: onChange,
956                                    state: {
957                                        valid: null,
958                                        rules: {}
959                                    }
960                                }
961                            }
962                        }
963                    } else {
964                        console.error('Element will not validate', element);
965                    }
966                },
967                onSubmit(event) {
968                    if (event.target.tagName === 'FORM') {
969                        event.preventDefault();
970
971                        if (this.isSubmitting) return;
972                        this.isSubmitting = true;
973
974                        this.validate()
975                            .then(() => event.target.submit())
976                            .catch(invalidElements => {
977                                this.isSubmitting = false;
978                            });
979                    }
980                },
981                onChange(event) {
982                    event.target.dataset.onChange = 'true';
983                    if (!Object.keys(this.fields).length) {
984                        this.setupFields(formElement.elements);
985                    }
986                    if (!Object.keys(this.fields).includes(event.target.name)) {
987                        this.setupField(event.target);
988                    }
989                    const field = this.fields[event.target.name];
990
991                    this.validateField(field);
992                    field && field.element.removeAttribute('data-on-change')
993                },
994                validateSafe() {
995                    return new Promise(resolve => this.validate().then(() => resolve(true)).catch(() => {}))
996                },
997                validate() {
998                    if (!Object.keys(this.fields).length || !Object.keys(this.fields).length !== formElement.elements.length) {
999                        this.setupFields(formElement.elements);
1000                    }
1001                    return new Promise(async (resolve, reject) => {
1002                        if (formElement.elements) {
1003                                                        await raceSome(this.validateFields(), result => result !== true)
1004                            const invalidFields = Object.values(this.fields).filter(field => !field.state.valid);
1005                            this.state.valid = invalidFields.length === 0;
1006                            if (this.state.valid) {
1007                                resolve();
1008                            } else {
1009                                if (options.scrollToFirstError && invalidFields.length > 0) {
1010                                    invalidFields[0].element.focus()
1011                                    invalidFields[0].element.select && invalidFields[0].element.select();
1012                                }
1013                                reject(invalidFields.map(field => field.element));
1014                            }
1015                        }
1016                    });
1017                },
1018                                validateFields() {
1019                    const fields = Object.values(this.fields);
1020
1021                                        fields.forEach(field => {
1022                        this.getMessageContainer(field).classList.remove(options.validClassName, options.invalidClassName)
1023                    });
1024                                        return fields.map(field => this.validateField(field))
1025                },
1026                                validateField(field) {
1027                                        if (! field || ! elementWillValidate(field.element)) {
1028                        return new Promise(resolve => resolve(true))
1029                    }
1030
1031                    let value;
1032                    if (field.element.type === 'checkbox') {
1033                        value = getCheckedValues(field);
1034                    } else if (field.element.type === 'radio') {
1035                        value = getCheckedValues(field)[0] || undefined;
1036                    } else if (field.element.tagName === 'SELECT' && field.element.multiple) {
1037                        value = Array.from(field.element.selectedOptions).map(opt => opt.value);
1038                    } else {
1039                        value = field.element.value;
1040                    }
1041
1042                    const rules = field.rules || {};
1043
1044                                        field.state.valid = true;
1045                    this.showFieldState(field);
1046
1047                
1048                                        const fieldValidations = Object.keys(rules).filter(rule => formValidationRules[rule]).map(async rule => {
1049                        return runValidateFn.call(this, rule, rules[rule], value, field).then(result => {
1050                            field.state.rules[rule] = result;
1051                            return result;
1052                        })
1053                    });
1054
1055                    return new Promise(resolve => {
1056                                                Promise.all(fieldValidations).then(results => {
1057                                                                                    field.state.valid = !elementWillValidate(field.element) || rules.length === 0 || !results.some(isInvalidRuleResult)
1058                            this.showFieldState(field);
1059                            resolve(field.state.valid);
1060                        })
1061                    });
1062                },
1063                                getMessagesByField(field) {
1064                    const messages = [];
1065                    const invalidRules = Object.keys(field.state.rules).filter(rule => isInvalidRuleResult(field.state.rules[rule]));
1066
1067                    field.rules && Object.keys(field.rules).forEach((rule) => {
1068                        if (invalidRules.includes(rule)) {
1069                            const customMessage = field.element.getAttribute('data-msg-' + rule);
1070                            const message = customMessage ? customMessage : field.state.rules[rule];
1071                            const ruleOptions = JSON.parse(JSON.stringify(field.rules[rule]));
1072
1073                            if (typeof message === 'undefined' || message === null || (typeof message !== 'string' && ! message.type)) {
1074                                messages.push(hyva.strf('Validation rule "%0" failed.', rule));
1075                            } else if (Array.isArray(ruleOptions)) {
1076                                ruleOptions.unshift(message.type ? message.content : message);
1077                                const content = hyva.strf.apply(null, ruleOptions);
1078                                messages.push(message.type ? {type: message.type, content} : content);
1079                            } else {
1080                                const content = hyva.strf(message.type ? message.content : message, ruleOptions)
1081                                messages.push(message.type ? {type: message.type, content} : content);
1082                            }
1083                        }
1084                    });
1085                    return messages;
1086                },
1087                /** @deprecated */
1088                getFieldWrapper(field) {
1089                                        return this.getMessageContainer(field)
1090                },
1091                getMessageContainer(field) {
1092                    let container;
1093                    const pageSelector = field.element.getAttribute('data-validation-container') || options.pageMessagesContainerSelector;
1094                    if (pageSelector) {
1095                        container = document.querySelector(pageSelector)
1096                            || containerNotFound(pageSelector, field.element)
1097                    } else {
1098                        const containerSelector = classNamesToSelector(options.fieldWrapperClassName);
1099                        container = field.element.closest(containerSelector)
1100                            || createMessageContainer(field.element, options.fieldWrapperClassName)
1101                            || containerNotFound(containerSelector, field.element);
1102                    }
1103
1104                    return container;
1105                },
1106                showFieldState(field) {
1107                    const container = this.getMessageContainer(field),
1108                        hasErrorMessages = hasMessagesWrapper.call(this, field, options.messagesWrapperClassName),
1109                        messages = this.getMessagesByField(field).map(m => {
1110                            return m.type !== 'html' ? escapeHtml(m.type ? m.content : m) : m.content;
1111                        });
1112                    container.classList.toggle(options.validClassName, field.state.valid && ! hasErrorMessages);
1113                    container.classList.toggle(options.invalidClassName, !field.state.valid || hasErrorMessages);
1114                    this.createHtmlErrorMessage(field, messages);
1115
1116                    if (field.state.valid) {
1117                        field.element.removeAttribute('aria-invalid');
1118                    } else {
1119                        field.element.setAttribute('aria-invalid', 'true');
1120                        if (! document.activeElement) {
1121                            field.element.focus();
1122                        }
1123                    }
1124                },
1125                removeMessages(field, messagesClass) {
1126                    if (! hasMessagesWrapper.call(this, field, messagesClass || options.messagesWrapperClassName)) {
1127                        return;
1128                    }
1129
1130                    const msgWrapper = getMessagesWrapper.call(this, field, messagesClass || options.messagesWrapperClassName);
1131                    const messages = msgWrapper.querySelectorAll(`[data-msg-field='${field.element.name}']`);
1132                    Array.from(messages).forEach(msg => msg.remove());
1133                    if (msgWrapper && msgWrapper.childElementCount === 0) {
1134                        field.element.removeAttribute('aria-errormessage');
1135                        field.element.removeAttribute('aria-describedby');
1136                        msgWrapper.remove();
1137                    }
1138                },
1139                createErrorMessage(field, messages) {
1140                    const htmlMessages = (Array.isArray(messages) ? messages : [messages]).map(escapeHtml)
1141                    this.createHtmlErrorMessage(field, htmlMessages);
1142                },
1143                createHtmlErrorMessage(field, messages) {
1144                    this.removeMessages(field, options.messagesWrapperClassName);
1145                    field.element.removeAttribute('aria-errormessage');
1146                    field.element.removeAttribute('aria-describedby');
1147
1148                    if (!field.state.valid) {
1149                        const msgWrapper = this.addHtmlMessages(field, options.messagesWrapperClassName, messages);
1150                        field.element.setAttribute('aria-errormessage', msgWrapper.id);
1151                        field.element.setAttribute('aria-describedby', msgWrapper.id);
1152                    }
1153                },
1154                /** @deprecated */
1155                createMessage(field, message) {
1156                                        return this.addMessages(field, options.messagesWrapperClassName, message);
1157                },
1158                addMessages(field, messagesClass, messages) {
1159                    const htmlMessages = (Array.isArray(messages) ? messages : [messages]).map(escapeHtml)
1160                    return this.addHtmlMessages(field, messagesClass, htmlMessages);
1161                },
1162                addHtmlMessages(field, messagesClass, htmlMessages) {
1163                    const msgWrapper = getMessagesWrapper.call(this, field, messagesClass);
1164
1165                    (Array.isArray(htmlMessages) ? htmlMessages : [htmlMessages]).forEach((htmlMessage) => {
1166                        const li = document.createElement('li');
1167                        li.innerHTML = htmlMessage;
1168                        li.setAttribute('data-msg-field', field.element.name);
1169                        msgWrapper.appendChild(li);
1170                    });
1171
1172                    return msgWrapper;
1173                },
1174                setField(name, value) {
1175                    this.fields[name].element.value = value;
1176                    this.fields[name].element.dispatchEvent((new Event('input')));
1177                    this.validateField(this.fields[name]);
1178                }
1179            }
1180        }
1181
1182        function getFieldName(field) {
1183                        const fieldName = field.element?.getAttribute('title')
1184                || document.querySelector(`label[for="${field.element?.id}"]`)?.innerText;
1185
1186            return fieldName
1187                ? fieldName.trim()
1188                : '';
1189        }
1190
1191        hyva.formValidation = formValidation;
1192        hyva.formValidation.rules = formValidationRules;
1193        hyva.formValidation.setInputAttributeRuleName = (attrName, ruleName) => INPUT_ATTRIBUTE_RULES[attrName] = ruleName || attrName;
1194        hyva.formValidation.setInputTypeRuleName = (typeName, ruleName) => INPUT_TYPE_RULES[typeName] = ruleName || typeName;
1195        hyva.formValidation.addRule = (name, validator) => formValidationRules[name] = validator;
1196        hyva.formValidation.getFieldName = getFieldName;
1197    }(window.hyva = window.hyva || {}));
1198
1199    window.addEventListener('alpine:init', () => {
1200        Alpine.data('hyva.formValidation', hyva.formValidation);
1201        Alpine.data('hyva.formValidation($el)', hyva.formValidation);
1202    }, {once: true});
1203    //# sourceURL=hyva-advanced-form-validation.js
1204</script>
1204
1205<div class="page-wrapper">
1205<script>
1206    function initHeader () {
1207        return {
1208            menuOpen: false,
1209            cart: {},
1210            getData: function getData(data) {
1211                if (data.cart) { this.cart = data.cart }
1212            }
1213        }
1214    }
1215</script>
1215
1216
1217<main :class="{'translate-x-64 transform': menuOpen}" class="duration-500 transition-all main" role="main" x-data="initHeader()" @toggle-mobile-menu.window="menuOpen = !menuOpen" @private-content-loaded.window="getData(event.detail.data)">
1218    <div>
1219        <div class="container mx-auto px-3 sm:px-4">
1220            <div class="grid grid-cols-12">
1221                <header class="col-span-12 lg:col-span-3 xl:col-span-2">
1222                    <h1 class="pt-4 pb-2 lg:pb-6 mt-4 lg:my-6 flex justify-center lg:justify-start">
1223                        <div class="order-1 md:order-1 w-full sm:w-auto pb-2 sm:pb-0">
1224    <a class="pt-4 flex items-center justify-center tracking-wide no-underline hover:no-underline font-medium font-title text-gray-800 text-xl leading-normal"
1225       href="https://uitgeverijpluim.nl/"
1226       title="Uitgeverij&#x20;Pluim"
1227       aria-label="store logo">
1228        <img src="https://uitgeverijpluim.nl/media/logo/websites/1/logo_sxfncc_1.jpg"
1229             title="Uitgeverij&#x20;Pluim"
1230             alt="Uitgeverij&#x20;Pluim"
1231            width="200"            height="130"        />
1232    </a>
1233</div>
1234                    </h1>
1235                    <div x-cloak @click="$dispatch('toggle-mobile-menu')"  :class="{'hidden': !menuOpen}" class="fixed  top-0 left-0 w-full h-full z-40 bg-opacity-25 bg-black transform transition-all duration-500"></div>
1236
1237<nav x-cloak class="-translate-x-64 fixed left-0 top-0 w-64 lg:w-full h-screen overflow-y-auto z-40 transform transition-all lg:translate-x-0 duration-500 bg-white p-5 lg:p-0 lg:relative">
1238    <ul>
1239        <li>
1240            <a href="/" class="block py-1 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400">Home</a>
1241        </li>
1242        <li>
1243            <a href="/schrijvers" class="block py-1 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400">Schrijvers</a>
1244        </li>
1245                    <li>
1246                <a href="https://uitgeverijpluim.nl/boeken"
1247                    title="Boeken"
1248                    class="block py-1 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400">Boeken</a>
1249            </li>
1250                            <li>
1251                    <ul class="pt-1 pb-4">
1252                                                    <li><a href="https://uitgeverijpluim.nl/boeken/fictie" title="Fictie" class="block py-1 px-2 leading-snug text-gray-900 hover:text-gray-1400 transition-colors duration-400 underline">Fictie</a></li>
1253                                                    <li><a href="https://uitgeverijpluim.nl/boeken/poezie" title="Poëzie" class="block py-1 px-2 leading-snug text-gray-900 hover:text-gray-1400 transition-colors duration-400">Poëzie</a></li>
1254                                                    <li><a href="https://uitgeverijpluim.nl/boeken/non-fictie" title="Non-fictie" class="block py-1 px-2 leading-snug text-gray-900 hover:text-gray-1400 transition-colors duration-400">Non-fictie</a></li>
1255                                                    <li><a href="https://uitgeverijpluim.nl/boeken/verwacht" title="Verwacht" class="block py-1 px-2 leading-snug text-gray-900 hover:text-gray-1400 transition-colors duration-400">Verwacht</a></li>
1256                                            </ul>
1257                </li>
1258                            <li>
1259            <a href="/nieuws" class="block py-1 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400">Nieuws</a>
1260        </li>
1261        <li>
1262            <a href="/agenda" class="block py-1 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400">Agenda</a>
1263        </li>
1264        <li>
1265            <a href="/over-ons" class="block py-1 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400">Over ons</a>
1266        </li>
1267        <li>
1268            <a href="/contact" class="block py-1 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400">Contact</a>
1269        </li>
1270    </ul>
1271</nav>
1272                </header>
1273                <div class="col-span-12 lg:col-span-9 xl:col-span-10">
1274                    
1275<script>
1276    function initCartSummary() {
1277        return {
1278            searchOpen: false,
1279            cart: {},
1280            getSummary: function (data) {
1281                if (data.cart) {
1282                    this.cart = data.cart
1283                }
1284            }
1285        }
1286    }
1287</script>
1287
1288<div id="cart-drawer" class="relative flex justify-between py-4 lg:py-6">
1289    <div class="flex items-center">
1290    <button x-data @click="$dispatch('toggle-mobile-menu')" aria-label="Menu openen"
1291                name="menu"
1292                class="-ml-2 sm:-ml-4 text-gray-1400 px-2 sm:px-4 py-4 lg:hidden focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-1400">
1293            <svg viewBox="0 0 120 80" fill="currentColor" stroke="currentColor"
1294                 stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-2" >
1295                <rect width="120" height="15"></rect>
1296                <rect y="35" width="120" height="15"></rect>
1297                <rect y="70" width="120" height="15"></rect>
1298            </svg>
1299        </button>
1300    </div>
1301    <nav class="flex-1 navbar" @keydown.window.escape="searchOpen = false;" x-data="initCartSummary()"
1302         @private-content-loaded.window="getSummary(event.detail.data)">
1303        <ul class="flex justify-end -mr-2 sm:-mr-4">
1304            <li class="justify-end w-full lg:w-auto flex lg:relative">
1305                <a href="#" aria-label="Search" class="order-2 flex items-center h-full px-2 sm:px-4 text-gray-900 text-base leading-normal sm:text-lg leading-normal"
1306                   @click.prevent="searchOpen = !searchOpen; $nextTick(function () { $refs.searchInput.select(); });" id="menu-search-icon">
1307                    <svg class="h-5 w-5 -mt-1" stroke="currentColor" fill="none" viewBox="0 0 24 24">
1308                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
1309                              d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
1310                    </svg>
1311                </a>
1312                <div
1313                    :class="{ 'block': searchOpen, 'hidden': !searchOpen }"
1314                    @click.outside="searchOpen = false"
1315                    x-show="searchOpen"
1316                    class="hidden order-1 flex-1 lg:flex-0 w-full top-full sm:h-full sm:w-64 lg:w-96">
1317                    <form class="pt-3" id="search_mini_form" action="https://uitgeverijpluim.nl/catalogsearch/result/"
1318                          method="get">
1319                        <input x-ref="searchInput"
1320                               type="search"
1321                               placeholder="Doorzoek&#x20;de&#x20;hele&#x20;winkel"
1322                               id="search-menu"
1323                               name="q"
1324                               value=""
1325                               maxlength="128"
1326                               class="text-gray-1000 placeholder-gray-800 w-full px-3 h-2md focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-1400"
1327                               style="background-color: #fafafa; border-color: #fafafa;">
1328                    </form>
1329                </div>
1330            </li>
1331            <li>
1332                <a title="Mijn account"
1333                   href="https://uitgeverijpluim.nl/customer/account/index/"
1334                   class="block px-2 sm:px-4 py-4 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400">
1335                    <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5">
1336                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
1337                              d="M224 256c70.7 0 128-57.3 128-128S294.7 0 224 0 96 57.3 96 128s57.3 128 128 128zm89.6 32h-16.7c-22.2 10.2-46.9 16-72.9 16s-50.6-5.8-72.9-16h-16.7C60.2 288 0 348.2 0 422.4V464c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48v-41.6c0-74.2-60.2-134.4-134.4-134.4z"></path>
1338                    </svg>
1339                </a>
1340            </li>
1341            <li class="relative">
1342                <template x-if="cart && cart.summary_count > 0">
1343                    <div class="right-0 top-0 mt-2 absolute bg-gray-800 text-white h-5 w-5 rounded-full flex items-center justify-center text-xs leading-normal" x-html="cart.summary_count"></div>
1344                </template>
1345                <a title="Winkelwagen"
1346                   href="https://uitgeverijpluim.nl/checkout/cart/index/"
1347                   class="block px-2 sm:px-4 py-4 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400">
1348                    <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5">
1349                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
1350                              d="M576 216v16c0 13.255-10.745 24-24 24h-8l-26.113 182.788C514.509 462.435 494.257 480 470.37 480H105.63c-23.887 0-44.139-17.565-47.518-41.212L32 256h-8c-13.255 0-24-10.745-24-24v-16c0-13.255 10.745-24 24-24h67.341l106.78-146.821c10.395-14.292 30.407-17.453 44.701-7.058 14.293 10.395 17.453 30.408 7.058 44.701L170.477 192h235.046L326.12 82.821c-10.395-14.292-7.234-34.306 7.059-44.701 14.291-10.395 34.306-7.235 44.701 7.058L484.659 192H552c13.255 0 24 10.745 24 24zM312 392V280c0-13.255-10.745-24-24-24s-24 10.745-24 24v112c0 13.255 10.745 24 24 24s24-10.745 24-24zm112 0V280c0-13.255-10.745-24-24-24s-24 10.745-24 24v112c0 13.255 10.745 24 24 24s24-10.745 24-24zm-224 0V280c0-13.255-10.745-24-24-24s-24 10.745-24 24v112c0 13.255 10.745 24 24 24s24-10.745 24-24z"></path>
1351                    </svg>
1352                </a>
1353            </li>
1354            <li class="hidden md:block">
1355                <div class="p-4 text-lg leading-normal font-bold">/</div>
1356            </li>
1357            <li class="hidden md:block">
1358                <a title="Instagram" target="_new" href="https://www.instagram.com/uitgeverijpluim/"
1359                   class="block px-2 sm:px-4 py-4 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400">
1360                    <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5">
1361                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
1362                              d="M224.1 141c-63.6 0-114.9 51.3-114.9 114.9s51.3 114.9 114.9 114.9S339 319.5 339 255.9 287.7 141 224.1 141zm0 189.6c-41.1 0-74.7-33.5-74.7-74.7s33.5-74.7 74.7-74.7 74.7 33.5 74.7 74.7-33.6 74.7-74.7 74.7zm146.4-194.3c0 14.9-12 26.8-26.8 26.8-14.9 0-26.8-12-26.8-26.8s12-26.8 26.8-26.8 26.8 12 26.8 26.8zm76.1 27.2c-1.7-35.9-9.9-67.7-36.2-93.9-26.2-26.2-58-34.4-93.9-36.2-37-2.1-147.9-2.1-184.9 0-35.8 1.7-67.6 9.9-93.9 36.1s-34.4 58-36.2 93.9c-2.1 37-2.1 147.9 0 184.9 1.7 35.9 9.9 67.7 36.2 93.9s58 34.4 93.9 36.2c37 2.1 147.9 2.1 184.9 0 35.9-1.7 67.7-9.9 93.9-36.2 26.2-26.2 34.4-58 36.2-93.9 2.1-37 2.1-147.8 0-184.8zM398.8 388c-7.8 19.6-22.9 34.7-42.6 42.6-29.5 11.7-99.5 9-132.1 9s-102.7 2.6-132.1-9c-19.6-7.8-34.7-22.9-42.6-42.6-11.7-29.5-9-99.5-9-132.1s-2.6-102.7 9-132.1c7.8-19.6 22.9-34.7 42.6-42.6 29.5-11.7 99.5-9 132.1-9s102.7-2.6 132.1 9c19.6 7.8 34.7 22.9 42.6 42.6 11.7 29.5 9 99.5 9 132.1s2.7 102.7-9 132.1z"></path>
1363                    </svg>
1364                </a>
1365            </li>
1366            <li class="hidden md:block">
1367                <a title="Facebook" target="_new" href="https://www.facebook.com/uitgeverijpluim"
1368                   class="block px-2 sm:px-4 py-4 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400">
1369                    <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5">
1370                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
1371                              d="M279.14 288l14.22-92.66h-88.91v
1371-60.13c0-25.35 12.42-50.06 52.24-50.06h40.42V6.26S260.43 0 225.36 0c-73.22 0-121.08 44.38-121.08 124.72v70.62H22.89V288h81.39v224h100.17V288z"></path>
1372                    </svg>
1373                </a>
1374            </li>
1375            <li class="hidden md:block">
1376                <a title="YouTube" target="_new"
1377                   href="https://www.youtube.com/channel/UCRr07oGz4RdkdjrntFZ76nw"
1378                   class="block px-2 sm:px-4 py-4 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400">
1379                    <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5">
1380                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
1381                              d="M549.655 124.083c-6.281-23.65-24.787-42.276-48.284-48.597C458.781 64 288 64 288 64S117.22 64 74.629 75.486c-23.497 6.322-42.003 24.947-48.284 48.597-11.412 42.867-11.412 132.305-11.412 132.305s0 89.438 11.412 132.305c6.281 23.65 24.787 41.5 48.284 47.821C117.22 448 288 448 288 448s170.78 0 213.371-11.486c23.497-6.321 42.003-24.171 48.284-47.821 11.412-42.867 11.412-132.305 11.412-132.305s0-89.438-11.412-132.305zm-317.51 213.508V175.185l142.739 81.205-142.739 81.201z"></path>
1382                    </svg>
1383                </a>
1384            </li>
1385            <li class="hidden md:block">
1386                <a title="Soundcloud" target="_new" href="https://soundcloud.com/uitgeverijpluim"
1387                   class="block px-2 sm:px-4 py-4 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400">
1388                    <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5">
1389                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
1390                              d="M256 32C114.52 32 0 146.496 0 288v48a32 32 0 0 0 17.689 28.622l14.383 7.191C34.083 431.903 83.421 480 144 480h24c13.255 0 24-10.745 24-24V280c0-13.255-10.745-24-24-24h-24c-31.342 0-59.671 12.879-80 33.627V288c0-105.869 86.131-192 192-192s192 86.131 192 192v1.627C427.671 268.879 399.342 256 368 256h-24c-13.255 0-24 10.745-24 24v176c0 13.255 10.745 24 24 24h24c60.579 0 109.917-48.098 111.928-108.187l14.382-7.191A32 32 0 0 0 512 336v-48c0-141.479-114.496-256-256-256z"
1391                        "></path>
1392                    </svg>
1393                </a>
1394            </li>
1395        </ul>
1396    </nav>
1397</div>
1398                    <!--Cart Drawer-->
1399                    
1399<script>
1400    function initCartDrawer() {
1401        return {
1402            open: false,
1403            cart: {},
1404            getData: function (data) {
1405                if (data.cart) {
1406                    this.cart = data.cart
1407                }
1408            },
1409            getCartItems: function getCartItems () {
1410                return this.cart && this.cart.items.sort(function(a,b) { return a.item_id - b.item_id }) || {}
1411            },
1412            deleteItemFromCart: function deleteItemFromCart (itemId) {
1413                var formKey = document.querySelector('input[name=form_key]').value;
1414                fetch(BASE_URL+"checkout/sidebar/removeItem/", {
1415                    "headers": {
1416                        "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
1417                    },
1418                    "body": "form_key="+ formKey + "&item_id="+itemId,
1419                    "method": "POST",
1420                    "mode": "cors",
1421                    "credentials": "include"
1422                }).then(function (response) {
1423                    if (response.redirected) {
1424                        window.location.href = response.url;
1425                    } else if (response.ok) {
1426                        return response.json();
1427                    } else {
1428                        typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
1429                            [{
1430                                type: "warning",
1431                                text: "Kan item niet uit offerte verwijderen."
1432                            }], 5000
1433                        );
1434                    }
1435                }).then(function (response) {
1436                    typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
1437                        [{
1438                            type: response.success ? "success" : "error",
1439                            text: response.success
1440                                ? "Je hebt het product verwijderd."
1441                                : response.error_message
1442                        }], 5000
1443                    );
1444                    var reloadCustomerDataEvent = new CustomEvent("reload-customer-section-data");
1445                    window.dispatchEvent(reloadCustomerDataEvent);
1446                });
1447            }
1448        }
1449    }
1450</script>
1450
1451<section id="cart-drawer"
1452         x-data="initCartDrawer()"
1453         @private-content-loaded.window="getData(event.detail.data)"
1454         @toggle-cart.window="open=true;"
1455         @keydown.window.escape="open=false"
1456>
1457    <template x-if="cart && cart.summary_count">
1458        <div role="dialog"
1459             aria-labelledby="cart-drawer-title"
1460             aria-modal="true"
1461             @click.outside="open = false"
1462             class="fixed inset-y-0 right-0 max-w-full flex">
1463            <div class="relative w-screen max-w-md"
1464                 x-show="open"
1465                 x-transition:enter="transform transition ease-in-out duration-500 sm:duration-700"
1466                 x-transition:enter-start="translate-x-full"
1467                 x-transition:enter-end="translate-x-0"
1468                 x-transition:leave="transform transition ease-in-out duration-500 sm:duration-700"
1469                 x-transition:leave-start="translate-x-0"
1470                 x-transition:leave-end="translate-x-full"
1471            >
1472                <div
1473                    x-show="open"
1474                    x-transition:enter="ease-in-out duration-500"
1475                    x-transition:enter-start="opacity-0"
1476                    x-transition:enter-end="opacity-100"
1477                    x-transition:leave="ease-in-out duration-500"
1478                    x-transition:leave-start="opacity-100"
1479                    x-transition:leave-end="opacity-0" class="absolute top-0 right-0 mt-2 p-2 flex">
1480                    <button @click="open = false;" aria-label="Close panel" class="text-gray-300 hover:text-black transition ease-in-out duration-150 p-2">
1481                        <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
1482                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
1483                        </svg>
1484                    </button>
1485                </div>
1486                <div class="h-full flex flex-col space-y-6 py-6 bg-white shadow-xl">
1487                    <header class="px-4 sm:px-6">
1488                        <h2 id="cart-drawer-title" class="text-lg leading-normal leading-7 font-medium text-gray-900">
1489                            Winkelwagen                        </h2>
1490                    </header>
1491                    <div class="relative grid gap-6 bg-white py-6 sm:gap-8 px-4 sm:px-6 border-b border-container overflow-y-auto">
1492                        <template x-for="item in getCartItems()">
1493                            <div class="-m-3 p-3 flex items-start space-x-4 rounded-lg hover:bg-gray-100 transition ease-in-out duration-150">
1494                                <a :href="item.product_url" class="w-1/4">
1495                                    <img
1496                                        :src="item.product_image.src"
1497                                        :alt="item.product_name"
1498                                        :width="item.product_image.width"
1499                                        :height="item.product_image.height"
1500                                        loading="lazy"
1501                                    />
1502                                </a>
1503                                <div class="space-y-2 w-3/4">
1504                                    <div>
1505                                        <p class="text-xl leading-normal"><span x-html="item.qty"></span> x <span x-html="item.product_name"></span></p>
1506                                        <p class="text-sm leading-normal"><span x-html="item.product_sku"></p>
1507                                    </div>
1508                                    <template x-for="option in item.options">
1509                                        <div class="pt-2">
1510                                            <p class="font-semibold" x-text="option.label + ':'"></p>
1511                                            <p class="text-secondary" x-text="option.value"></p>
1512                                        </div>
1513                                    </template>
1514                                    <p><span x-html="item.product_price"></span></p>
1515                                    <div class="pt-4">
1516                                        <a :href="item.configure_url"
1517                                           class="btn btn-primary inline-flex mr-2 p-2">
1518                                            <svg fill="none" viewBox="0 0 24 24" stroke="currentColor" size="16" class="h-5 w-5">
1519                                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z">
1520
1521                                                </path>
1522                                            </svg>
1523                                        </a>
1524                                        <button class="btn btn-primary inline-flex p-2"
1525                                            @click="deleteItemFromCart(item.item_id)"
1526                                        >
1527                                            <svg fill="none" viewBox="0 0 24 24" stroke="currentColor" size="16" class="h-5 w-5">
1528                                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
1529                                            </svg>
1530                                        </button>
1531                                    </div>
1532
1533                                </div>
1534                            </div>
1535                        </template>
1536                    </div>
1537                    <div class="relative grid gap-6 bg-white py-6 sm:gap-8 px-4 sm:px-6">
1538                        <div class="w-full -m-3 p-3 space-x-4 roun
1538ded-lg hover:bg-gray-100 transition ease-in-out duration-150">
1539                            <p>Subtotal: <span x-html="cart.subtotal"></span></p>
1540                        </div>
1541                        <div class="w-full -m-3 p-3 space-x-4 rounded-lg hover:bg-gray-100 transition ease-in-out duration-150">
1542                            <a href="https://uitgeverijpluim.nl/checkout/index/" class="btn btn-primary inline-flex">Checkout</a>
1543                            <span>or</span> <a href="https://uitgeverijpluim.nl/checkout/cart/" class="underline">Go to Cart</a>
1544                        </div>
1545                    </div>
1546                </div>
1547            </div>
1548        </div>
1549    </template>
1550</section>
1551<main id="maincontent" class="page-main"><div class="page messages">
1551<script>
1552        function initMessages() {
1553        "use strict";
1554        return {
1555            messages: window.mageMessages || [],
1556            isEmpty() {
1557                return this.messages.reduce(
1558                    function (isEmpty, message) {
1559                        return isEmpty && message === undefined
1560                    }, true
1561                )
1562            },
1563            hasMessages() {
1564                return !this.isEmpty();
1565            },
1566            hasMessage() {
1567                return !!this.message;
1568            },
1569            removeMessage() {
1570                this.messages[this.index] = undefined;
1571            },
1572            addMessages(messages, hideAfter) {
1573                messages.map((message) => {
1574                    this.messages = this.messages.concat(message);
1575                    if (hideAfter === undefined && message.type === 'success' && window.defaultSuccessMessageTimeout) {
1576                        hideAfter = window.defaultSuccessMessageTimeout;
1577                    }
1578                    if (hideAfter) {
1579                        this.setHideTimeOut(this.messages.length -1, hideAfter);
1580                    }
1581                });
1582            },
1583            setHideTimeOut(messageIndex, hideAfter) {
1584                setTimeout((messageIndex) => {
1585                    this.removeMessage(messageIndex);
1586                }, hideAfter, messageIndex);
1587            },
1588            eventListeners: {
1589                ['@messages-loaded.window'](event) {
1590                    this.addMessages(event.detail.messages, event.detail.hideAfter)
1591                },
1592                ['@private-content-loaded.window'](event) {
1593                    const data = event.detail.data;
1594                    if (
1595                        data.messages &&
1596                        data.messages.messages &&
1597                        data.messages.messages.length
1598                    ) {
1599                        this.addMessages(data.messages.messages);
1600                    }
1601                },
1602                ['@clear-messages.window']() {
1603                    this.messages = [];
1604                }
1605            },
1606            getMessageUiId() {
1607                return 'message-' + this.message.type;
1608            }
1609        }
1610    }
1611
1612    window.addEventListener('alpine:init', () => Alpine.data('initMessages', initMessages), {once: true})
1613</script>
1613
1614<section id="messages"
1615         x-data="initMessages"
1616         x-bind="eventListeners"
1617         aria-live="assertive"
1618         role="alert"
1619>
1620    <template x-if="hasMessages">
1621        <div class="w-full">
1622            <div class="messages container mx-auto py-3">
1623                <template x-for="(message, index) in messages" :key="index">
1624                    <div>
1625                        <template x-if="hasMessage">
1626                            <div class="message"
1627                                 :class="message.type"
1628                                 :ui-id="getMessageUiId"
1629                            >
1630                                <span x-html="message.text"></span>
1631                                <button
1632                                    type="button"
1633                                    class="text-gray-600 hover:text-black"
1634                                    aria-label="Bericht sluiten"
1635                                    @click.prevent="removeMessage"
1636                                >
1637                                    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="stroke-current" width="18" height="18" aria-hidden="true">
1638  <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
1639</svg>
1640                                </button>
1641                            </div>
1642                        </template>
1643                    </div>
1644                </template>
1645            </div>
1646        </div>
1647    </template>
1648</section>
1649</div><div class="category-view mx-auto flex flex-col items-center"><div class="w-full border-2 border-solid border-gray-900 bg-none text-center uppercase mx-auto mb-4 p-4 lg:p-8 bg-cover">
1650    <h1>Fictie</h1>
1651</div>
1652</div><div class="row"><div><div class="row"><div class="columns"><div class="column main">    <section class="py-2 lg:py-8">
1653    
1653<script>
1654    function initToolbar_6ab5759bc3877() {
1655        return {
1656            options: {
1657                url: 'https://uitgeverijpluim.nl/boeken/fictie'
1658            },
1659            oneDirection: false,
1660            changeUrl: function (paramName, paramValue, defaultValue) {
1661                var decode = window.decodeURIComponent,
1662                    urlPaths = this.options.url.split('?'),
1663                    baseUrl = urlPaths[0],
1664                    urlParams = urlPaths[1] ? urlPaths[1].split('&') : [],
1665                    paramData = {},
1666                    parameters, i, form, params, key, input, formKey;
1667
1668                for (i = 0; i < urlParams.length; i++) {
1669                    parameters = urlParams[i].split('=');
1670                    paramData[decode(parameters[0])] = parameters[1] !== undefined ?
1671                        decode(parameters[1].replace(/\+/g, '%20')) :
1672                        '';
1673                }
1674                paramData[paramName] = paramValue;
1675
1676                if (this.options.post) {
1677                    form = document.createElement('form');
1678                    params = [this.options.mode, this.options.direction, this.options.order, this.options.limit];
1679
1680                    for (key in paramData) {
1681                        if (params.indexOf(key) !== -1) { //eslint-disable-line max-depth
1682                            input = document.createElement('input');
1683                            input.name = key;
1684                            input.value = paramData[key];
1685                            form.appendChild(input);
1686                            delete paramData[key];
1687                        }
1688                    }
1689                    formKey = document.createElement('input');
1690                    formKey.name = 'form_key';
1691                    formKey.value = this.options.formKey;
1692                    form.appendChild(formKey);
1693
1694                    paramData = $.param(paramData);
1695                    baseUrl += paramData.length ? '?' + paramData : '';
1696
1697                    form.action = baseUrl;
1698                    form.method = 'POST';
1699                    document.body.appendChild(form);
1700                    form.submit();
1701                } else {
1702                    if (paramValue === defaultValue) { //eslint-disable-line eqeqeq
1703                        delete paramData[paramName];
1704                    }
1705                    paramData = Object.keys(paramData).map(function(k) {
1706                        return encodeURIComponent(k) + '=' + encodeURIComponent(paramData[k])
1707                    }).join('&');
1708
1709                    location.href = baseUrl + (paramData.length ? '?' + paramData : '');
1710                }
1711            }
1712        }
1713    }
1714</script>
1714
1715    <div x-data="initToolbar_6ab5759bc3877()"
1716         class="toolbar toolbar-products h-28 grid grid-cols-3 grid-flow-row gap-2 items-center">
1717                            <div class="toolbar-sorter sorter flex items-center order-1 justify-left col-span-1 invisible md:inline-block">
1718    <label class="sr-only sorter-label" for="sorter">Sorteer op</label>
1719    <select id="sorter" data-role="sorter" class="form-select text-sm leading-normal w-full rounded-none h-2md py-1 px-3 text-gray-700 border border-gray-200 focus:outline-none focus:shadow-none focus:border-gray-1400 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-900"
1720            @change="changeUrl('product_list_order',event.currentTarget.options[event.currentTarget.selectedIndex].value,'position')">
1721                    <option value="position"
1722                                >
1723                Positie            </option>
1724                    <option value="name"
1725                                >
1726                Productnaam            </option>
1727                    <option value="price"
1728                                >
1729                prijs            </option>
1730                    <option value="publish_date"
1731                                    selected="selected"
1732                                >
1733                Verschijningsdatum            </option>
1734            </select>
1735                <a title="Van&#x20;laag&#x20;naar&#x20;hoog&#x20;sorteren"
1736           href="#"
1737           class="action sorter-action sort-desc text-gray-400 ml-2"
1738           data-role="direction-switcher"
1739           data-value="asc"
1740           @click.prevent="changeUrl('product_list_dir','asc','asc')"
1741           x-show="!oneDirection"
1742        >
1743            <span>
1744                <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
1745                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4h13M3 8h9m-9 4h6m4 0l4-4m0 0l4 4m-4-4v12" />
1746                </svg>
1747            </span>
1748        </a>
1749    </div>
1750        
1751                
1752    <div class="pages order-2 col-span-2 justify-end flex">
1753                    <strong class="sr-only label pages-label" id="paging-label_845">Pagina</strong>
1754            <ul class="pagination flex ml-auto -mr-3" aria-labelledby="paging-label_845">
1755            
1756            
1757            
1758                                            <li>
1759                    <a class="inline-block py-3 px-5 uppercase hover:text-gray-800 border-b-2&#x20;text-gray-800 border-gray-800 hover:text-gray-1400 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken/fictie" title="Pagina 1">
1760                        1                    </a>
1761                </li>
1762                                            <li>
1763                    <a class="inline-block py-3 px-5 uppercase hover:text-gray-800 text-gray-700 border-gray-800 hover:text-gray-1400 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken/fictie?p=2" title="Pagina 2">
1764                        2                    </a>
1765                </li>
1766                                            <li>
1767                    <a class="inline-block py-3 px-5 uppercase hover:text-gray-800 text-gray-700 border-gray-800 hover:text-gray-1400 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken/fictie?p=3" title="Pagina 3">
1768                        3                    </a>
1769                </li>
1770                                            <li>
1771                    <a class="inline-block py-3 px-5 uppercase hover:text-gray-800 text-gray-700 border-gray-800 hover:text-gray-1400 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken/fictie?p=4" title="Pagina 4">
1772                        4                    </a>
1773                </li>
1774                                            <li>
1775                    <a class="inline-block py-3 px-5 uppercase hover:text-gray-800 text-gray-700 border-gray-800 hover:text-gray-1400 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken/fictie?p=5" title="Pagina 5">
1776                        5                    </a>
1777                </li>
1778            
1779            
1780            
1781                            <li>
1782                                        <a class="inline-block py-3 px-5 uppercase text-gray-700 border-b-2 border-transparent hover:text-gray-800 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken/fictie?p=2" title="Volgende">
1783                        <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
1784                            <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
1785                        </svg>
1786                    </a>
1787                </li>
1788                        </ul>
1789
1790            </div>
1791
1792    </div>
1793            <div class="products wrapper mode-grid products-grid">
1794        <div class="mx-auto md:grid md:grid-cols-4 gap-x-4 pt-4 pb-12 -mr-2">
1795                                        <div class="item product product-item w-full mt-8">
1796            <form method="post"
1797          action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2VuL2ZpY3RpZQ~~/product/863/"
1798          class="product_addtocart_form card card-interactive mr-2 mb-2"
1799            >
1800        <input name="form_key" type="hidden" value="Ueui8SM3dgSPd5qv" />        <input type="hidden" name="product" value="863"/>
1801                                    <div class="relative ">
1802                                <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white">
1803                        <a class="absolute w-full h-full z-30"
1804                       href="https://uitgeverijpluim.nl/boeken/fictie/red" aria-label="Louise Kennedy - Red"></a>
1805                    <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/hSoicTRemOBcBpapqpaWnkaqTzV8_s0QsXxtqsHAdoo/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL2svZS9rZW5uZWR5X2xvdWlzZV8tX3JlZC5qcGc.jpg" alt="Red" itemprop="image" loading="eager" fetchpriority="high">
1806                                            <div
1807                            class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none">
1808                            <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none"
1809                               href="https://uitgeverijpluim.nl/boeken/fictie/red" aria-label="Louise Kennedy - Red"></a>
1810
1811                            <a href="https://uitgeverijpluim.nl/boeken/fictie/red" aria-label="Louise Kennedy - Red"
1812                               class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a>
1813                        </div>
1814                                    </div>
1815                <div class="mt-6">
1816                    <div class="text-sm leading-normal uppercase mb-1px">
1817                        <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400"
1818                           href="https://uitgeverijpluim.nl/boeken/fictie/red" aria-label="Louise Kennedy - Red">
1819                            Louise Kennedy - Red                        </a>
1820                    </div>
1821                    <div class="flex justify-between items-center">
1822                        <p class="text-gray-1400 text-base leading-normal">
1823                            <ins class="no-underline">€ 26,99</ins>
1824                        </p>
1825                    </div>
1826                </div>
1827            </div>
1828                            </form>
1829</div>
1830                            <div class="item product product-item w-full mt-8">
1831            <form method="post"
1832          action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2VuL2ZpY3RpZQ~~/product/790/"
1833          class="product_addtocart_form card card-interactive mr-2 mb-2"
1834            >
1835        <input name="form_key" type="hidden" value="Ueui8SM3dgSPd5qv" />        <input type="hidden" name="product" value="790"/>
1836                                    <div class="relative ">
1837                                <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white">
1838                        <a class="absolute w-full h-full z-30"
1839                       href="https://uitgeverijpluim.nl/boeken/fictie/voor-issa" aria-label="Massih Hutak - Voor Issa"></a>
1840                    <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/p_JV7gQxAMLHB1-W1tKl4FO1vxVctw_1THjK4YknwKs/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL2gvdS9odXRha19tYXNzaWhfLV92b29yX2lzc2FfLV92b29ycGxhdC5qcGc.jpg" alt="Voor Issa" itemprop="image" loading="eager" fetchpriority="high">
1841                                            <div
1842                            class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none">
1843                            <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none"
1844                               href="https://uitgeverijpluim.nl/boeken/fictie/voor-issa" aria-label="Massih Hutak - Voor Issa"></a>
1845
1846                            <a href="https://uitgeverijpluim.nl/boeken/fictie/voor-issa" aria-label="Massih Hutak - Voor Issa"
1847                               class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a>
1848                        </div>
1849                                    </div>
1850                <div class="mt-6">
1851                    <div class="text-sm leading-normal uppercase mb-1px">
1852                        <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400"
1853                           href="https://uitgeverijpluim.nl/boeken/fictie/voor-issa" aria-label="Massih Hutak - Voor Issa">
1854                            Massih Hutak - Voor Issa                        </a>
1855                    </div>
1856                    <div class="flex justify-between items-center">
1857                        <p class="text-gray-1400 text-base leading-normal">
1858                            <ins class="no-underline">€ 23,99</ins>
1859                        </p>
1860                    </div>
1861                </div>
1862            </div>
1863                            </form>
1864</div>
1865                            <div class="item product product-item w-full mt-8">
1866            <form method="post"
1867          action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2VuL2ZpY3RpZQ~~/product/869/"
1868          class="product_addtocart_form card card-interactive mr-2 mb-2"
1869            >
1870        <input name="form_key" type="hidden" value="Ueui8SM3dgSPd5qv" />        <input type="hidden" name="product" value="869"/>
1871                                    <div class="relative ">
1872                                <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white">
1873                        <a class="absolute w-full h-full z-30"
1874                       href="https://uitgeverijpluim.nl/boeken/fictie/nvrlnd" aria-label="Valentijn de Heer - Nvrlnd"></a>
1875                    <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/sff8CgCM1r5InEgJST6DlpeHWeLnVMK4Af0q08pcm8g/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL2gvZS9oZWVyX3ZhbGVudGlqbl9kZV8tX252cmxuZF9vbXNsYWcuanBn.jpg" alt="Nvrlnd" itemprop="image" loading="eager" fetchpriority="high">
1876                                            <div
1877                            class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none">
1878                            <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none"
1879                               href="https://uitgeverijpluim.nl/boeken/fictie/nvrlnd" aria-label="Valentijn de Heer - Nvrlnd"></a>
1880
1881                            <a href="https://uitgeverijpluim.nl/boeken/fictie/nvrlnd" aria-label="Valentijn de Heer - Nvrlnd"
1882                               class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a>
1883                        </div>
1884                                    </div>
1885                <div class="mt-6">
1886                    <div class="text-sm leading-normal uppercase mb-1px">
1887                        <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400"
1888                           href="https://uitgeverijpluim.nl/boeken/fictie/nvrlnd" aria-label="Valentijn de Heer - Nvrlnd">
1889                            Valentijn de Heer - Nvrlnd                        </a>
1890                    </div>
1891                    <div class="flex justify-between items-center">
1892                        <p class="text-gray-1400 text-base leading-normal">
1893                            <ins class="no-underline">€ 23,99</ins>
1894                        </p>
1895                    </div>
1896                </div>
1897            </div>
1898                            </form>
1899</div>
1900                            <div class="item product product-item w-full mt-8">
1901            <form method="post"
1902          action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2VuL2ZpY3RpZQ~~/product/836/"
1903          class="product_addtocart_form card card-interactive mr-2 mb-2"
1904            >
1905        <input name="form_key" type="hidden" value="Ueui8SM3dgSPd5qv" />        <input type="hidden" name="product" value="836"/>
1906                                    <div class="relative ">
1907                                <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white">
1908                        <a class="absolute w-full h-full z-30"
1909                       href="https://uitgeverijpluim.nl/boeken/fictie/verloren-schaapjes" aria-label="Madeline Cash - Verloren schaapjes"></a>
1910                    <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/DfR30Qls9uSnZQUWcWKivu6aryPl5FEBgEa-qjvzFsg/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL2MvYS9jYXNoX21hZGVsaW5lXy1fdmVybG9yZW5fc2NoYWFwamVzLmpwZw.jpg" alt="Verloren schaapjes" itemprop="image" loading="eager" fetchpriority="high">
1911                                            <div
1912                            class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none">
1913                            <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none"
1914                               href="https://uitgeverijpluim.nl/boeken/fictie/verloren-schaapjes" aria-label="Madeline Cash - Verloren schaapjes"></a>
1915
1916                            <a href="https://uitgeverijpluim.nl/boeken/fictie/verloren-schaapjes" aria-label="Madeline Cash - Verloren schaapjes"
1917                               class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a>
1918                        </div>
1919                                    </div>
1920                <div class="mt-6">
1921                    <div class="text-sm leading-normal uppercase mb-1px">
1922                        <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400"
1923                           href="https://uitgeverijpluim.nl/boeken/fictie/verloren-schaapjes" aria-label="Madeline Cash - Verloren schaapjes">
1924                            Madeline Cash - Verloren schaapjes                        </a>
1925                    </div>
1926                    <div class="flex justify-between items-center">
1927                        <p class="text-gray-1400 text-base leading-normal">
1928                            <ins class="no-underline">
1928€ 26,99</ins>
1929                        </p>
1930                    </div>
1931                </div>
1932            </div>
1933                            </form>
1934</div>
1935                            <div class="item product product-item w-full mt-8">
1936            <form method="post"
1937          action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2VuL2ZpY3RpZQ~~/product/851/"
1938          class="product_addtocart_form card card-interactive mr-2 mb-2"
1939            >
1940        <input name="form_key" type="hidden" value="Ueui8SM3dgSPd5qv" />        <input type="hidden" name="product" value="851"/>
1941                                    <div class="relative ">
1942                                <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white">
1943                        <a class="absolute w-full h-full z-30"
1944                       href="https://uitgeverijpluim.nl/boeken/fictie/dedichterendeduivel" aria-label="Lieke Marsman - De dichter en de duivel"></a>
1945                    <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/mDLLmBVnybBBu7866qddywlD0S7SHjWt-z9IzX4OeKQ/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL20vYS9tYXJzbWFuX2xpZWtlXy1fZGVfZGljaHRlcl9lbl9kZV9kdWl2ZWwuanBn.jpg" alt="De dichter en de duivel" itemprop="image" loading="lazy" decoding="async" fetchpriority="low">
1946                                            <div
1947                            class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none">
1948                            <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none"
1949                               href="https://uitgeverijpluim.nl/boeken/fictie/dedichterendeduivel" aria-label="Lieke Marsman - De dichter en de duivel"></a>
1950
1951                            <a href="https://uitgeverijpluim.nl/boeken/fictie/dedichterendeduivel" aria-label="Lieke Marsman - De dichter en de duivel"
1952                               class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a>
1953                        </div>
1954                                    </div>
1955                <div class="mt-6">
1956                    <div class="text-sm leading-normal uppercase mb-1px">
1957                        <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400"
1958                           href="https://uitgeverijpluim.nl/boeken/fictie/dedichterendeduivel" aria-label="Lieke Marsman - De dichter en de duivel">
1959                            Lieke Marsman - De dichter en de duivel                        </a>
1960                    </div>
1961                    <div class="flex justify-between items-center">
1962                        <p class="text-gray-1400 text-base leading-normal">
1963                            <ins class="no-underline">€ 24,99</ins>
1964                        </p>
1965                    </div>
1966                </div>
1967            </div>
1968                            </form>
1969</div>
1970                            <div class="item product product-item w-full mt-8">
1971            <form method="post"
1972          action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2VuL2ZpY3RpZQ~~/product/782/"
1973          class="product_addtocart_form card card-interactive mr-2 mb-2"
1974            >
1975        <input name="form_key" type="hidden" value="Ueui8SM3dgSPd5qv" />        <input type="hidden" name="product" value="782"/>
1976                                    <div class="relative ">
1977                                <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white">
1978                        <a class="absolute w-full h-full z-30"
1979                       href="https://uitgeverijpluim.nl/boeken/fictie/in-verlatenheid" aria-label="L.H. Wiener - In verlatenheid"></a>
1980                    <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/k3wl8fWLo2FsuuvorAOYzEBQ2XAkpFhZ-kXxGunUJts/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL3cvaS93aWVuZXJfbC5oLl8tX2luX3ZlcmxhdGVuaGVpZC5qcGc.jpg" alt="In verlatenheid" itemprop="image" loading="lazy" decoding="async" fetchpriority="low">
1981                                            <div
1982                            class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none">
1983                            <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none"
1984                               href="https://uitgeverijpluim.nl/boeken/fictie/in-verlatenheid" aria-label="L.H. Wiener - In verlatenheid"></a>
1985
1986                            <a href="https://uitgeverijpluim.nl/boeken/fictie/in-verlatenheid" aria-label="L.H. Wiener - In verlatenheid"
1987                               class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a>
1988                        </div>
1989                                    </div>
1990                <div class="mt-6">
1991                    <div class="text-sm leading-normal uppercase mb-1px">
1992                        <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400"
1993                           href="https://uitgeverijpluim.nl/boeken/fictie/in-verlatenheid" aria-label="L.H. Wiener - In verlatenheid">
1994                            L.H. Wiener - In verlatenheid                        </a>
1995                    </div>
1996                    <div class="flex justify-between items-center">
1997                        <p class="text-gray-1400 text-base leading-normal">
1998                            <ins class="no-underline">€ 23,99</ins>
1999                        </p>
2000                    </div>
2001                </div>
2002            </div>
2003                            </form>
2004</div>
2005                            <div class="item product product-item w-full mt-8">
2006            <form method="post"
2007          action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2VuL2ZpY3RpZQ~~/product/769/"
2008          class="product_addtocart_form card card-interactive mr-2 mb-2"
2009            >
2010        <input name="form_key" type="hidden" value="Ueui8SM3dgSPd5qv" />        <input type="hidden" name="product" value="769"/>
2011                                    <div class="relative ">
2012                                <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white">
2013                        <a class="absolute w-full h-full z-30"
2014                       href="https://uitgeverijpluim.nl/boeken/fictie/de-levensavond" aria-label="Salman Rushdie - De levensavond"></a>
2015                    <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/aHIeaKKcP4NQc_owaaQWqjB3tLyZl02iXseiw2ouFW8/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL3IvdS9ydXNoZGllX3NhbG1hbl8tX2RlX2xldmVuc2F2b25kLmpwZw.jpg" alt="De levensavond" itemprop="image" loading="lazy" decoding="async" fetchpriority="low">
2016                                            <div
2017                            class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none">
2018                            <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none"
2019                               href="https://uitgeverijpluim.nl/boeken/fictie/de-levensavond" aria-label="Salman Rushdie - De levensavond"></a>
2020
2021                            <a href="https://uitgeverijpluim.nl/boeken/fictie/de-levensavond" aria-label="Salman Rushdie - De levensavond"
2022                               class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a>
2023                        </div>
2024                                    </div>
2025                <div class="mt-6">
2026                    <div class="text-sm leading-normal uppercase mb-1px">
2027                        <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400"
2028                           href="https://uitgeverijpluim.nl/boeken/fictie/de-levensavond" aria-label="Salman Rushdie - De levensavond">
2029                            Salman Rushdie - De levensavond                        </a>
2030                    </div>
2031                    <div class="flex justify-between items-center">
2032                        <p class="text-gray-1400 text-base leading-normal">
2033                            <ins class="no-underline">€ 24,99</ins>
2034                        </p>
2035                    </div>
2036                </div>
2037            </div>
2038                            </form>
2039</div>
2040                            <div class="item product product-item w-full mt-8">
2041            <form method="post"
2042          action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2VuL2ZpY3RpZQ~~/product/816/"
2043          class="product_addtocart_form card card-interactive mr-2 mb-2"
2044            >
2045        <input name="form_key" type="hidden" value="Ueui8SM3dgSPd5qv" />        <input type="hidden" name="product" value="816"/>
2046                                    <div class="relative ">
2047                                <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white">
2048                        <a class="absolute w-full h-full z-30"
2049                       href="https://uitgeverijpluim.nl/boeken/fictie/lieve-celine" aria-label="Hanna Bervoets - Lieve Céline"></a>
2050                    <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/WB_GSd6TdDkHFgInF0rT1k210odQ3lcI52LkiLbylUE/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL2IvZS9iZXJ2b2V0c19oYW5uYV8tX2xpZXZlX2NlbGluZV92b29ycGxhdC5qcGc.jpg" alt="Lieve Céline" itemprop="image" loading="lazy" decoding="async" fetchpriority="low">
2051                                            <div
2052                            class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none">
2053                            <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none"
2054                               href="https://uitgeverijpluim.nl/boeken/fictie/lieve-celine" aria-label="Hanna Bervoets - Lieve Céline"></a>
2055
2056                            <a href="https://uitgeverijpluim.nl/boeken/fictie/lieve-celine" aria-label="Hanna Bervoets - Lieve Céline"
2057                               class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a>
2058                        </div>
2059                                    </div>
2060                <div class="mt-6">
2061                    <div class="text-sm leading-normal uppercase mb-1px">
2062                        <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400"
2063                           href="https://uitgeverijpluim.nl/boeken/fictie/lieve-celine" aria-label="Hanna Bervoets - Lieve Céline">
2064                            Hanna Bervoets - Lieve Céline                        </a>
2065                    </div>
2066                    <div class="flex justify-between items-center">
2067                        <p class="text-gray-1400 text-base leading-normal">
2068                            <ins class="no-underline">€ 23,99</ins>
2069                        </p>
2070                    </div>
2071                </div>
2072            </div>
2073                            </form>
2074</div>
2075                            <div class="item product product-item w-full mt-8">
2076            <form method="post"
2077          action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2VuL2ZpY3RpZQ~~/product/757/"
2078          class="product_addtocart_form card card-interactive mr-2 mb-2"
2079            >
2080        <input name="form_key" type="hidden" value="Ueui8SM3dgSPd5qv" />        <input type="hidden" name="product" value="757"/>
2081                                    <div class="relative ">
2082                                <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white">
2083                        <a class="absolute w-full h-full z-30"
2084                       href="https://uitgeverijpluim.nl/boeken/fictie/vervloekte-dochters" aria-label="Oyinkan Braithwaite - Vervloekte dochters"></a>
2085                    <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/Vuh-GaNgjf_5exP_bpB3_HtaBdu9g1G474cQ6uESTtM/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL2Ivci9icmFpdGh3YWl0ZV9veWlua2FuXy1fdmVydmxvZWt0ZV9kb2NodGVyc18tX3Zvb3JwbGF0LmpwZw.jpg" alt="Vervloekte dochters" itemprop="image" loading="lazy" decoding="async" fetchpriority="low">
2086                                            <div
2087                            class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none">
2088                            <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none"
2089                               href="https://uitgeverijpluim.nl/boeken/fictie/vervloekte-dochters" aria-label="Oyinkan Braithwaite - Vervloekte dochters"></a>
2090
2091                            <a href="https://uitgeverijpluim.nl/boeken/fictie/vervloekte-dochters" aria-label="Oyinkan Braithwaite - Vervloekte dochters"
2092                               class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a>
2093                        </div>
2094                                    </div>
2095                <div class="mt-6">
2096                    <div class="text-sm leading-normal uppercase mb-1px">
2097                        <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400"
2098                           href="https://uitgeverijpluim.nl/boeken/fictie/vervloekte-dochters" aria-label="Oyinkan Braithwaite - Vervloekte dochters">
2099                            Oyinkan Braithwaite - Vervloekte dochters                        </a>
2100                    </div>
2101                    <div class="flex justify-between items-center">
2102                        <p class="text-gray-1400 text-base leading-normal">
2103                            <ins class="no-underline">€ 25,00</ins>
2104                        </p>
2105                    </div>
2106                </div>
2107            </div>
2108                            </form>
2109</div>
2110                            <div class="item product product-item w-full mt-8">
2111            <form method="post"
2112          action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2VuL2ZpY3RpZQ~~/product/748/"
2113          class="product_addtocart_form card card-interactive mr-2 mb-2"
2114            >
2115        <input name="form_key" type="hidden" value="Ueui8SM3dgSPd5qv" />        <input type="hidden" name="product" value="748"/>
2116                                    <div class="relative ">
2117                                <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white">
2118                        <a class="absolute w-full h-full z-30"
2119                       href="https://uitgeverijpluim.nl/boeken/fictie/kookpunt" aria-label="Nisrine Mbarki Ben Ayad - Kookpunt"></a>
2120                    <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/4ANl_O-IzvnIZBL6RenT0ysUA2H4Zfy2RNOVVBVs-SA/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL20vYi9tYmFya2lfYmVuX2F5YWRfbmlzcmluZV8tX2tvb2twdW50LmpwZw.jpg" alt="Kookpunt" itemprop="image" loading="lazy" decoding="async" fetchpriority="low">
2121                                            <div
2122                            class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none">
2123                            <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none"
2124                               href="https://uitgeverijpluim.nl/boeken/fictie/kookpunt" aria-label="Nisrine Mbarki Ben Ayad - Kookpunt"></a>
2125
2126                            <a href="https://uitgeverijpluim.nl/boeken/fictie/kookpunt" aria-label="Nisrine Mbarki Ben Ayad - Kookpunt"
2127                               class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a>
2128                        </div>
2129                                    </div>
2130                <div class="mt-6">
2131                    <div class="text-sm leading-normal uppercase mb-1px">
2132                        <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400"
2133                           href="https://uitgeverijpluim.nl/boeken/fictie/kookpunt" aria-label="Nisrine Mbarki Ben Ayad - Kookpunt">
2134                            Nisrine Mbarki Ben Ayad - Kookpunt                        </a>
2135                    </div>
2136                    <div class="flex justify-between items-center">
2137                        <p class="text-gray-1400 text-base leading-normal">
2138                            <ins class="no-underline">€ 21,99</ins>
2139                        </p>
2140                    </div>
2141                </div>
2142            </div>
2143                            </form>
2144</div>
2145                            <div class="item product product-item w-full mt-8">
2146            <form method="post"
2147          action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2VuL2ZpY3RpZQ~~/product/760/"
2148          class="product_addtocart_form card card-interactive mr-2 mb-2"
2149            >
2150        <input name="form_key" type="hidden" value="Ueui8SM3dgSPd5qv" />        <input type="hidden" name="product" value="760"/>
2151                                    <div class="relative ">
2152                                <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white">
2153                        <a class="absolute w-full h-full z-30"
2154                       href="https://uitgeverijpluim.nl/boeken/fictie/koeman" aria-label="Jan Wester - Koeman"></a>
2155                    <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/_P9zzgK5EGIy68Stwf6ie3QfhEdfEMDK8bQq7sL8bew/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL3cvZS93ZXN0ZXJfamFuXy1fa29lbWFuXy1fdm9vcnBsYXQuanBn.jpg" alt="Koeman" itemprop="image" loading="lazy" decoding="async" fetchpriority="low">
2156                                            <div
2157                            class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none">
2158                            <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none"
2159                               href="https://uitgeverijpluim.nl/boeken/fictie/koeman" aria-label="Jan Wester - Koeman"></a>
2160
2161                            <a href="https://uitgeverijpluim.nl/boeken/fictie/koeman" aria-label="Jan Wester - Koeman"
2162                               class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a>
2163                        </div>
2164                                    </div>
2165                <div class="mt-6">
2166                    <div class="text-sm leading-normal uppercase mb-1px">
2167                        <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400"
2168                           href="https://uitgeverijpluim.nl/boeken/fictie/koeman" aria-label="Jan Wester - Koeman">
2169                            Jan Wester - Koeman                        </a>
2170                    </div>
2171                    <div class="flex justify-between items-center">
2172                        <p class="text-gray-1400 text-base leading-normal">
2173                            <ins class="no-underline">€ 23,99</ins>
2174                        </p>
2175                    </div>
2176                </div>
2177            </div>
2178                            </form>
2179</div>
2180                            <div class="item product product-item w-full mt-8">
2181            <form method="post"
2182          action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2VuL2ZpY3RpZQ~~/product/751/"
2183          class="product_addtocart_form card card-interactive mr-2 mb-2"
2184            >
2185        <input name="form_key" type="hidden" value="Ueui8SM3dgSPd5qv" />        <input type="hidden" name="product" value="751"/>
2186                                    <div class="relative ">
2187                                <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white">
2188                        <a class="absolute w-full h-full z-30"
2189                       href="https://uitgeverijpluim.nl/boeken/fictie/de-erfenis" aria-label="Safae el Khannoussi - De erfenis"></a>
2190                    <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/hNlmn-8UKhMT0H9NdJcJ0n0wdD8x8hVas5leAuuDEe4/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL2svaC9raGFubm91c3NpX3NhZmFlX2VsXy1fZGVfZXJmZW5pcy5qcGc.jpg" alt="De erfenis" itemprop="image" loading="lazy" decoding="async" fetchpriority="low">
2191                                            <div
2192                            class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none">
2193                            <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none"
2194                               href="https://uitgeverijpluim.nl/boeken/fictie/de-erfenis" aria-label="Safae el Khannoussi - De erfenis"></a>
2195
2196                            <a href="https://uitgeverijpluim.nl/boeken/fictie/de-erfenis" aria-label="Safae el Khannoussi - De erfenis"
2197                               class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a>
2198                        </div>
2199                                    </div>
2200                <div class="mt-6">
2201                    <div class="text-sm leading-normal uppercase mb-1px">
2202                        <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400"
2203                           href="https://uitgeverijpluim.nl/boeken/fictie/de-erfenis" aria-label="Safae el Khannoussi - De erfenis">
2204                            Safae el Khannoussi - De erfenis                        </a>
2205                    </div>
2206                    <div class="flex justify-between items-center">
2207                        <p class="text-gray-1400 text-base leading-normal">
2208                            <ins class="no-underline">€ 14,99</ins>
2209                        </p>
2210                    </div>
2211                </div>
2212            </div>
2213                            </form>
2214</div>
2215                    </div>
2216    </div>
2217    
2217<script>
2218    function initToolbar_6ab5759bc9cd3() {
2219        return {
2220            options: {
2221                url: 'https://uitgeverijpluim.nl/boeken/fictie'
2222            },
2223            oneDirection: false,
2224            changeUrl: function (paramName, paramValue, defaultValue) {
2225                var decode = window.decodeURIComponent,
2226                    urlPaths = this.options.url.split('?'),
2227                    baseUrl = urlPaths[0],
2228                    urlParams = urlPaths[1] ? urlPaths[1].split('&') : [],
2229                    paramData = {},
2230                    parameters, i, form, params, key, input, formKey;
2231
2232                for (i = 0; i < urlParams.length; i++) {
2233                    parameters = urlParams[i].split('=');
2234                    paramData[decode(parameters[0])] = parameters[1] !== undefined ?
2235                        decode(parameters[1].replace(/\+/g, '%20')) :
2236                        '';
2237                }
2238                paramData[paramName] = paramValue;
2239
2240                if (this.options.post) {
2241                    form = document.createElement('form');
2242                    params = [this.options.mode, this.options.direction, this.options.order, this.options.limit];
2243
2244                    for (key in paramData) {
2245                        if (params.indexOf(key) !== -1) { //eslint-disable-line max-depth
2246                            input = document.createElement('input');
2247                            input.name = key;
2248                            input.value = paramData[key];
2249                            form.appendChild(input);
2250                            delete paramData[key];
2251                        }
2252                    }
2253                    formKey = document.createElement('input');
2254                    formKey.name = 'form_key';
2255                    formKey.value = this.options.formKey;
2256                    form.appendChild(formKey);
2257
2258                    paramData = $.param(paramData);
2259                    baseUrl += paramData.length ? '?' + paramData : '';
2260
2261                    form.action = baseUrl;
2262                    form.method = 'POST';
2263                    document.body.appendChild(form);
2264                    form.submit();
2265                } else {
2266                    if (paramValue === defaultValue) { //eslint-disable-line eqeqeq
2267                        delete paramData[paramName];
2268                    }
2269                    paramData = Object.keys(paramData).map(function(k) {
2270                        return encodeURIComponent(k) + '=' + encodeURIComponent(paramData[k])
2271                    }).join('&');
2272
2273                    location.href = baseUrl + (paramData.length ? '?' + paramData : '');
2274                }
2275            }
2276        }
2277    }
2278</script>
2278
2279    <div x-data="initToolbar_6ab5759bc9cd3()"
2280         class="toolbar toolbar-products h-28 grid grid-cols-3 grid-flow-row gap-2 items-center">
2281                    <div class="colspan-1">&nbsp;</div>
2282                
2283                
2284    <div class="pages order-2 col-span-2 justify-end flex">
2285                    <strong class="sr-only label pages-label" id="paging-label_684">Pagina</strong>
2286            <ul class="pagination flex ml-auto -mr-3" aria-labelledby="paging-label_684">
2287            
2288            
2289            
2290                                            <li>
2291                    <a class="inline-block py-3 px-5 uppercase hover:text-gray-800 border-b-2&#x20;text-gray-800 border-gray-800 hover:text-gray-1400 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken/fictie" title="Pagina 1">
2292                        1                    </a>
2293                </li>
2294                                            <li>
2295                    <a class="inline-block py-3 px-5 uppercase hover:text-gray-800 text-gray-700 border-gray-800 hover:text-gray-1400 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken/fictie?p=2" title="Pagina 2">
2296                        2                    </a>
2297                </li>
2298                                            <li>
2299                    <a class="inline-block py-3 px-5 uppercase hover:text-gray-800 text-gray-700 border-gray-800 hover:text-gray-1400 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken/fictie?p=3" title="Pagina 3">
2300                        3                    </a>
2301                </li>
2302                                            <li>
2303                    <a class="inline-block py-3 px-5 uppercase hover:text-gray-800 text-gray-700 border-gray-800 hover:text-gray-1400 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken/fictie?p=4" title="Pagina 4">
2304                        4                    </a>
2305                </li>
2306                                            <li>
2307                    <a class="inline-block py-3 px-5 uppercase hover:text-gray-800 text-gray-700 border-gray-800 hover:text-gray-1400 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken/fictie?p=5" title="Pagina 5">
2308                        5                    </a>
2309                </li>
2310            
2311            
2312            
2313                            <li>
2314                                        <a class="inline-block py-3 px-5 uppercase text-gray-700 border-b-2 border-transparent hover:text-gray-800 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken/fictie?p=2" title="Volgende">
2315                        <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
2316                            <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
2317                        </svg>
2318                    </a>
2319                </li>
2320                        </ul>
2321
2322            </div>
2323
2324    </div>
2325</section>
2326</div></div></div></div></div></main><footer class="page-footer"><div class="footer content"><footer class="mt-16 lg:mt-32">
2327    <div class="grid grid-cols-12 gap-4">
2328        <div class="col-span-12 sm:col-span-3">
2329            <h2 class="uppercase text-gray-1400 mb-md text-3xl">Uitgeverij Pluim</h2>
2330            <ul class="mb-5">
2331                <li><a href="/privacybeleid"
2332                       class="block uppercase text-secondary text-sm leading-normal hover:text-primary transition-colors duration-400">Privacybeleid</a></li>
2333                <li class="mt-4 lg:mt-0"><a href="/over-ons"
2334                                            class="block uppercase text-secondary text-sm leading-normal hover:text-primary transition-colors duration-400">Over
2335                        ons</a></li>
2336                <li class="mt-4 lg:mt-0"><a href="/contact"
2337                                            class="block uppercase text-secondary text-sm leading-normal hover:text-primary transition-colors duration-400">Contact</a>
2338                </li>
2339            </ul>
2340        </div>
2341        <div class="col-span-12 sm:col-span-5 lg:col-span-4 xl:col-span-3">
2342            <div class="lg:w-1/4 md:w-1/2 w-full lg:px-4">
2343     <form
2344        class="form subscribe mb-2md"
2345        action="https://uitgeverijpluim.nl/newsletter/subscriber/new/"
2346        method="post"
2347        x-data="initNewsletterForm"
2348        @submit.prevent="submitForm"
2349        id="newsletter-validate-detail"
2350        aria-label="Abonneren&#x20;op&#x20;de&#x20;nieuwsbrief"
2351    >
2352        <h2 class="uppercase text-gray-1400 mb-md text-3xl">Nieuwsbrief</h2>
2353        <div class="flex justify-center gap-2 md:justify-start">
2354            <label for="newsletter-subscribe" class="sr-only">
2355                E-mailadres            </label>
2356            <input
2357                name="email"
2358                type="email"
2359                required
2360                id="newsletter-subscribe"
2361                class="text-gray-1000 h-2md flex-grow border-2 border-gray-300 px-3 mr-1"
2362                placeholder="E-mailadres"
2363                autocomplete="email"
2364                aria-describedby="footer-newsletter-heading"
2365            >
2366            <input name="form_key" type="hidden" value="Ueui8SM3dgSPd5qv" />                        <button type="submit" class="bg-primary h-2md block px-3 text-gray-900 uppercase">
2367                OK            </button>
2368        </div>
2369        <div>
2370            <template x-if="displayErrorMessage">
2371                <p class="flex items-center text-red">
2372                    <span class="inline-block w-8 h-8 mr-3">
2373                        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" width="24" height="24" aria-hidden="true">
2374  <path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
2375</svg>
2376                    </span>
2377                    <template x-for="errorMessage in errorMessages">
2378                        <span x-html="errorMessage"></span>
2379                    </template>
2380                </p>
2381            </template>
2382        </div>
2383    </form>
2384    <div class="w-full">
2385            </div>
2386    
2386<script>
2387        function initNewsletterForm() {
2388            return {
2389                errors: 0,
2390                hasCaptchaToken: 0,
2391                displayErrorMessage: false,
2392                errorMessages: [],
2393                setErrorMessages(messages) {
2394                    this.errorMessages = [messages]
2395                    this.displayErrorMessage = this.errorMessages.length
2396                },
2397                submitForm() {
2398                    // Do not rename $form, the variable is expected to be declared in the recaptcha output
2399                    const $form = document.querySelector('#newsletter-validate-detail');
2400                    
2401                    if (this.errors === 0) {
2402                        $form.submit();
2403                    }
2404                }
2405            }
2406        }
2407
2408        window.addEventListener('alpine:init', () => Alpine.data('initNewsletterForm', initNewsletterForm), {once: true})
2409    </script>
2409
2410    </div>
2411            <ul class="flex md:hidden">
2412                <li class="pr-4">
2413                    <a class="text-gray-700 text-xl leading-normal" href="https://www.instagram.com/uitgeverijpluim/"
2414                       target="_blank" title="Instagram" rel="noreferrer">
2415                        <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5">
2416                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
2417                                  d="M224.1 141c-63.6 0-114.9 51.3-114.9 114.9s51.3 114.9 114.9 114.9S339 319.5 339 255.9 287.7 141 224.1 141zm0 189.6c-41.1 0-74.7-33.5-74.7-74.7s33.5-74.7 74.7-74.7 74.7 33.5 74.7 74.7-33.6 74.7-74.7 74.7zm146.4-194.3c0 14.9-12 26.8-26.8 26.8-14.9 0-26.8-12-26.8-26.8s12-26.8 26.8-26.8 26.8 12 26.8 26.8zm76.1 27.2c-1.7-35.9-9.9-67.7-36.2-93.9-26.2-26.2-58-34.4-93.9-36.2-37-2.1-147.9-2.1-184.9 0-35.8 1.7-67.6 9.9-93.9 36.1s-34.4 58-36.2 93.9c-2.1 37-2.1 147.9 0 184.9 1.7 35.9 9.9 67.7 36.2 93.9s58 34.4 93.9 36.2c37 2.1 147.9 2.1 184.9 0 35.9-1.7 67.7-9.9 93.9-36.2 26.2-26.2 34.4-58 36.2-93.9 2.1-37 2.1-147.8 0-184.8zM398.8 388c-7.8 19.6-22.9 34.7-42.6 42.6-29.5 11.7-99.5 9-132.1 9s-102.7 2.6-132.1-9c-19.6-7.8-34.7-22.9-42.6-42.6-11.7-29.5-9-99.5-9-132.1s-2.6-102.7 9-132.1c7.8-19.6 22.9-34.7 42.6-42.6 29.5-11.7 99.5-9 132.1-9s102.7-2.6 132.1 9c19.6 7.8 34.7 22.9 42.6 42.6 11.7 29.5 9 99.5 9 132.1s2.7 102.7-9 132.1z"></path>
2418                        </svg>
2419                    </a>
2420                </li>
2421                <li class="pr-4">
2422                    <a class="text-gray-700 text-xl leading-normal" href="https://www.facebook.com/uitgeverijpluim"
2423                       target="_blank" title="Facebook" rel="noreferrer">
2424                        <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5">
2425                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
2426                                  d="M279.14 288l14.22-92.66h-88.91v-60.13c0-25.35 12.42-50.06 52.24-50.06h40.42V6.26S260.43 0 225.36 0c-73.22 0-121.08 44.38-121.08 124.72v70.62H22.89V288h81.39v224h100.17V288z"></path>
2427                        </svg>
2428                    </a>
2429                </li>
2430                <li class="pr-4">
2431                    <a class="text-gray-700 text-xl leading-normal"
2432                       href="https://www.youtube.com/channel/UCRr07oGz4RdkdjrntFZ76nw" target="_blank" title="YouTube"
2433                       rel="noreferrer">
2434                        <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5">
2435                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
2436                                  d="M549.655 124.083c-6.281-23.65-24.787-42.276-48.284-48.597C458.781 64 288 64 288 64S117.22 64 74.629 75.486c-23.497 6.322-42.003 24.947-48.284 48.597-11.412 42.867-11.412 132.305-11.412 132.305s0 89.438 11.412 132.305c6.281 23.65 24.787 41.5 48.284 47.821C117.22 448 288 448 288 448s170.78 0 213.371-11.486c23.497-6.321 42.003-24.171 48.284-47.821 11.412-42.867 11.412-132.305 11.412-132.305s0-89.438-11.412-132.305zm-317.51 213.508V175.185l142.739 81.205-142.739 81.201z"></path>
2437                        </svg>
2438                    </a>
2439                </li>
2440                <li class="pr-4">
2441                    <a class="text-gray-700 text-xl leading-normal" href="https://twitter.com/uitgeverijpluim"
2442                       target="_blank" title="Twitter" rel="noreferrer">
2443                        <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5">
2444                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
2445                                  d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"></path>
2446                        </svg>
2447                    </a>
2448                </li>
2449                <li class="pr-4">
2450                    <a class="text-gray-700 text-xl leading-normal" href="https://soundcloud.com/uitgeverijpluim"
2451                       target="_blank" title="Soundcloud" rel="noreferrer">
2452                        <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5">
2453                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
2454                                  d="M256 32C114.52 32 0 146.496 0 288v48a32 32 0 0 0 17.689 28.622l14.383 7.191C34.083 431.903 83.421 480 144 480h24c13.255 0 24-10.745 24-24V280c0-13.255-10.745-24-24-24h-24c-31.342 0-59.671 12.879-80 33.627V288c0-105.869 86.131-192 192-192s192 86.131 192 192v1.627C427.671 268.879 399.342 256 368 256h-24c-13.255 0-24 10.745-24 24v176c0 13.255 10.745 24 24 24h24c60.579 0 109.917-48.098 111.928-108.187l14.382
2454-7.191A32 32 0 0 0 512 336v-48c0-141.479-114.496-256-256-256z"
2455                            "></path>
2456                        </svg>
2457                    </a>
2458                </li>
2459            </ul>
2460        </div>
2461    </div>
2462    <p class="text-xs leading-normal mt-12 mb-5 lg:text-right">&copy; Uitgeverij Pluim</p>
2463</footer>
2464</div>
2465</div>
2466</div>
2467</div>
2468</main>
2469</body>
2470</html>
2471</div></footer>
2471<script type="speculationrules">{"prefetch":[{"source":"document","where":{"and":[{"href_matches":"\/*"},{"not":{"href_matches":"\/*(customer|login|logout|auth|cart|checkout|search|download|redirect|rewrite|store|productalert)\/*"}},{"not":{"href_matches":"*.pdf"}},{"not":{"href_matches":"*.zip"}},{"not":{"selector_matches":".do-not-prerender"}},{"not":{"selector_matches":"[rel=nofollow]"}},{"not":{"selector_matches":"[target=_blank]"}},{"not":{"selector_matches":"[target=_parent]"}},{"not":{"selector_matches":"[target=_top]"}}]},"eagerness":"moderate"}]}</script>
vendor: 1 bytes, line 2471
2471
2472<script>
2473(() => {
2474    class BFCacheHandler {
2475        constructor() {
2476            this.options = {
2477                isCustomerLoggedIn: false,
2478                enableUserInteractionRefreshMiniCart: true,
2479                autoCloseMenuMobile: true            };
2480
2481            this.userInteractionEvents = ['touchstart', 'mouseover', 'wheel', 'scroll', 'keydown'];
2482            this._reloadTriggered = false;
2483            this.dom = {};
2484
2485            // Centralized selectors for reuse
2486            this.selectors = {
2487                cartDrawer: "[x-data^='initCartDrawer']",
2488                mobileMenu: "[x-data^='initMenuMobile']",
2489                desktopMenu: "[x-data^='initMenuDesktop']"
2490            };
2491        }
2492
2493                getElement(key) {
2494            const cached = this.dom[key];
2495            if (cached && document.contains(cached)) return cached;
2496            const el = document.querySelector(this.selectors[key]);
2497            if (el) this.dom[key] = el;
2498            return el;
2499        }
2500
2501        init() {
2502            this.refreshMiniCart();
2503            this.reloadCustomerLoginPage();
2504            this.actionAutoCloseMenu(this.options.autoCloseMenuMobile);
2505        }
2506
2507                refreshMiniCart() {
2508            if (this.options.enableUserInteractionRefreshMiniCart) {
2509                this.refreshMiniCartOnUserInteraction();
2510            } else {
2511                this.actionRefreshMiniCart();
2512            }
2513        }
2514
2515        refreshMiniCartOnUserInteraction() {
2516            const refreshMiniCart = () => {
2517                this.userInteractionEvents.forEach(eventType =>
2518                    window.removeEventListener(eventType, refreshMiniCart)
2519                );
2520                this.actionRefreshMiniCart();
2521            };
2522            const opts = { once: true, passive: true };
2523            this.userInteractionEvents.forEach(eventType =>
2524                window.addEventListener(eventType, refreshMiniCart, opts)
2525            );
2526        }
2527
2528                reloadCustomerLoginPage() {
2529            const backendLoggedInState = this.options.isCustomerLoggedIn;
2530
2531            let customerData = null;
2532            try {
2533                const cacheStorage = localStorage.getItem('mage-cache-storage');
2534                if (cacheStorage) {
2535                    customerData = JSON.parse(cacheStorage).customer || null;
2536                }
2537            } catch {
2538                // Ignore parse errors silently
2539            }
2540
2541            const frontendLoggedInState = Boolean(customerData?.firstname);
2542
2543            if (frontendLoggedInState !== backendLoggedInState && !this._reloadTriggered) {
2544                this._reloadTriggered = true;
2545                window.location.reload();
2546            }
2547        }
2548
2549                actionRefreshMiniCart() {
2550            window.dispatchEvent(new CustomEvent('reload-customer-section-data'));
2551        }
2552
2553                actionAutoCloseMenu(autoCloseMenuMobile) {
2554            window.dispatchEvent(new Event('clear-messages'));
2555
2556            const cartDrawer = this.getElement('cartDrawer');
2557            if (cartDrawer && typeof Alpine !== 'undefined') {
2558                requestAnimationFrame(() => {
2559                    const cartDrawerData = Alpine.$data(cartDrawer);
2560                    if (cartDrawerData?.open !== undefined) {
2561                        cartDrawerData.open = false;
2562                        document.body.style.overflow = '';
2563                    }
2564                });
2565            }
2566
2567            if (autoCloseMenuMobile) {
2568                this.handleMenuClosure();
2569            }
2570        }
2571
2572                handleMenuClosure() {
2573            if (typeof Alpine === 'undefined' || !Alpine.$data) return;
2574
2575            const mobileMenu = this.getElement('mobileMenu');
2576            if (mobileMenu) {
2577                const mobileMenuData = Alpine.$data(mobileMenu);
2578                if (mobileMenuData?.open !== undefined) {
2579                    mobileMenuData.open = false;
2580                }
2581            }
2582
2583            const desktopMenu = this.getElement('desktopMenu');
2584            if (desktopMenu) {
2585                const desktopMenuData = Alpine.$data(desktopMenu);
2586                if (desktopMenuData?.hoverPanelActiveId !== undefined) {
2587                    desktopMenuData.hoverPanelActiveId = 0;
2588                }
2589            }
2590        }
2591    }
2592
2593        window.addEventListener('pageshow', (event) => {
2594        if (event.persisted) {
2595            (window._bfcacheHandler ??= new BFCacheHandler()).init();
2596        }
2597    }, { passive: true });
2598})();
2599</script>
2599
2600
vendor: 2,386 bytes, lines 2601-2661
2601<script>
2602    (() => {
2603        function src_default(Alpine) {
2604            Alpine.directive("intersect", Alpine.skipDuringClone((el, { value, expression, modifiers }, { evaluateLater, cleanup }) => {
2605                let evaluate = evaluateLater(expression);
2606                let options = {
2607                    rootMargin: getRootMargin(modifiers),
2608                    threshold: getThreshold(modifiers)
2609                };
2610                let observer = new IntersectionObserver((entries) => {
2611                    entries.forEach((entry) => {
2612                        if (entry.isIntersecting === (value === "leave"))
2613                            return;
2614                        evaluate();
2615                        modifiers.includes("once") && observer.disconnect();
2616                    });
2617                }, options);
2618                observer.observe(el);
2619                cleanup(() => {
2620                    observer.disconnect();
2621                });
2622            }));
2623        }
2624        function getThreshold(modifiers) {
2625            if (modifiers.includes("full"))
2626                return 0.99;
2627            if (modifiers.includes("half"))
2628                return 0.5;
2629            if (!modifiers.includes("threshold"))
2630                return 0;
2631            let threshold = modifiers[modifiers.indexOf("threshold") + 1];
2632            if (threshold === "100")
2633                return 1;
2634            if (threshold === "0")
2635                return 0;
2636            return Number(`.${threshold}`);
2637        }
2638        function getLengthValue(rawValue) {
2639            let match = rawValue.match(/^(-?[0-9]+)(px|%)?$/);
2640            return match ? match[1] + (match[2] || "px") : void 0;
2641        }
2642        function getRootMargin(modifiers) {
2643            const key = "margin";
2644            const fallback = "0px 0px 0px 0px";
2645            const index = modifiers.indexOf(key);
2646            if (index === -1)
2647                return fallback;
2648            let values = [];
2649            for (let i = 1; i < 5; i++) {
2650                values.push(getLengthValue(modifiers[index + i] || ""));
2651            }
2652            values = values.filter((v) => v !== void 0);
2653            return values.length ? values.join(" ").trim() : fallback;
2654        }
2655
2656        document.addEventListener("alpine:init", () => {
2657            window.Alpine.plugin(src_default);
2658        });
2659    })();
2660    //# sourceURL=alpine-intersect.js
2661</script>
2661
2662
2663<script>
2664    let SnapSlider = class {
2665        constructor(el, {
2666            labelSepparator = 'van',
2667            autoPager,
2668            groupPager
2669        } = {}) {
2670            this.el = el;
2671            this.track = this.el.querySelector("[data-track]");
2672            if (!this.track) {
2673                console.warn(
2674                    "No Slider track defined, reverting back to CSS slider.\nPlease create a wrapper for your slides with the attribute data-track",
2675                    this.el
2676                );
2677                return;
2678            }
2679            this.initialLoad = true;
2680            this.slides = [];
2681            this.inViewObserver;
2682            this.mutationObserver;
2683            this.resizeObserver;
2684            this.pager = this.el.querySelector("[data-pager]");
2685            this.navBtns = Array.from(
2686                this.el.querySelectorAll("[data-next], [data-prev]")
2687            );
2688            this.slideLabelSepparator = this.el.dataset.slideLabelSepparator || labelSepparator;
2689            this.useAutoPager = autoPager || this.el.hasAttribute("data-auto-pager") || false;
2690            this.useGroupPager = groupPager || this.el.hasAttribute("data-group-pager") || false;
2691            this.sliderLabel = this.el.hasAttribute("aria-label") && this.el.getAttribute("aria-label").toLowerCase().trim().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
2692            this.sliderId = this.el.id || this.sliderLabel || "slider";
2693            this.markerIdName = "data-target-id";
2694            this.pagerClasses = this.el.dataset.pagerClass || "snap-pager";
2695            this.markerClasses = this.el.dataset.markerClass || "snap-marker";
2696            this.init();
2697        }
2698        init() {
2699            this.setupSlides();
2700            this.setupNav();
2701            if (this.useAutoPager) {
2702                this.createPager();
2703            } else {
2704                this.setupPager();
2705            }
2706            this.setupObservers();
2707            this.setupMutationObserver();
2708            this.setupResizeObserver();
2709            this.el.addEventListener("click", this.eventHandler.bind(this));
2710            this.el.addEventListener("keydown", this.eventHandler.bind(this));
2711        }
2712        destroy() {
2713            this.inViewObserver?.disconnect();
2714            this.mutationObserver?.disconnect();
2715            this.resizeObserver?.disconnect();
2716            this.el.removeEventListener("click", this.eventHandler.bind(this));
2717            this.el.removeEventListener("keydown", this.eventHandler.bind(this));
2718        }
2719        roundUpIfGreaterThan(number, min = 8) {
2720            const decimalPart = number - Math.floor(number);
2721            return decimalPart >= min ? Math.ceil(number) : Math.floor(number);
2722        }
2723        getInViewItems() {
2724            const inViewSlides = this.track.querySelectorAll("[data-in-view]");
2725            const firstInViewSlide = inViewSlides[0] || null;
2726            const lastInViewSlide = inViewSlides[inViewSlides.length - 1] || null;
2727            const isAtStart = firstInViewSlide === this.slides[0];
2728            const isAtEnd = lastInViewSlide === this.slides[this.slides.length - 1];
2729            return {
2730                inViewSlides,
2731                totalInViewSlides: inViewSlides.length,
2732                firstInViewSlide,
2733                lastInViewSlide,
2734                isAtStart,
2735                isAtEnd,
2736                hasNoOverflow: isAtStart && isAtEnd
2737            };
2738        }
2739        groupPagerMarkers() {
2740            if (this.slides.length === 0 || !this.pager || !this.useGroupPager) return;
2741            const totalVisibleSlides = this.roundUpIfGreaterThan(
2742                this.track.offsetWidth / this.slides[0].offsetWidth
2743            );
2744            const markers = Array.from(this.pager.querySelectorAll("a, button"));
2745            markers.forEach((marker, index) => {
2746                marker.style.display = index % totalVisibleSlides === 0 ? null : "none";
2747            });
2748        }
2749        handleInView(entries) {
2750            entries.forEach((entry) => {
2751                const marker = this.pager && (this.pager.querySelector(`[href="#${entry.target.id}"]`) || this.pager.querySelector(
2752                    `[${this.markerIdName}="${entry.target.id}"]`
2753                ));
2754                entry.target.toggleAttribute("data-in-view", entry.isIntersecting);
2755                entry.target.toggleAttribute("inert", !entry.isIntersecting);
2756                marker?.setAttribute("aria-current", entry.isIntersecting);
2757                marker?.setAttribute("tabindex", entry.isIntersecting ? "0" : "-1");
2758            });
2759            const {
2760                isAtStart,
2761                isAtEnd,
2762                hasNoOverflow
2763            } = this.getInViewItems();
2764            this.navBtns.forEach((btn) => {
2765                if (btn.hasAttribute("data-next")) {
2766                    btn.style.visibility = hasNoOverflow ? "hidden" : null;
2767                    isAtEnd ? btn.setAttribute("disabled", "") : btn.removeAttribute("disabled");
2768                }
2769                if (btn.hasAttribute("data-prev")) {
2770                    btn.style.visibility = hasNoOverflow ? "hidden" : null;
2771                    isAtStart ? btn.setAttribute("disabled", "") : btn.removeAttribute("disabled");
2772                }
2773            });
2774            if (this.pager) {
2775                this.pager.style.visibility = hasNoOverflow ? "hidden" : null;
2776            }
2777            if (!this.initialLoad) {
2778                this.el.dispatchEvent(
2779                    new CustomEvent("slideChange", {
2780                        detail: this.getInViewItems()
2781                    })
2782                );
2783            } else {
2784                this.initialLoad = false;
2785            }
2786            if (document.activeElement.parentElement.hasAttribute("data-pager")) {
2787                const activeItems = this.pager.querySelectorAll('[tabindex="0"]');
2788                if (activeItems.length) {
2789                    activeItems[0].focus({ preventScroll: true });
2790                }
2791            }
2792        }
2793        getSlides() {
2794            if (!this.track) return [];
2795            return Array.from(this.track.children).filter(
2796                (child) => child.tagName.toLowerCase() !== "template" && child.tagName.toLowerCase() !== "style" && child.tagName.toLowerCase() !== "script"
2797            );
2798        }
2799        refreshSlides() {
2800            this.initialLoad = true;
2801            this.slides = this.getSlides();
2802            this.setupSlides();
2803            if (this.useAutoPager) {
2804                this.createPager();
2805            } else {
2806                this.setupPager();
2807            }
2808            this.groupPagerMarkers();
2809            this.setupObservers();
2810        }
2811        setupSlides() {
2812            this.slides = this.getSlides();
2813            this.slides.forEach((slide, index) => {
2814                const totalSlides = this.slides.length;
2815                const currentSlide = index + 1;
2816                const existingLabel = slide.getAttribute("aria-label") || "";
2817                const isImage = slide.tagName.toLowerCase() === "img" || slide.tagName.toLowerCase() === "picture";
2818                const slideLabelTag = isImage ? "alt" : "aria-label";
2819                const hasAutoLabel = existingLabel.startsWith(
2820                    `${currentSlide} ${this.slideLabelSepparator} `
2821                );
2822                if (!slide.hasAttribute("id")) {
2823                    slide.setAttribute(
2824                        "id",
2825                        `${this.sliderId}-item-${currentSlide}`
2826                    );
2827                }
2828                if (!slide.hasAttribute(slideLabelTag) || slide.getAttribute(slideLabelTag) === "" || hasAutoLabel) {
2829                    slide.setAttribute(
2830                        slideLabelTag,
2831                        `${currentSlide} ${this.slideLabelSepparator} ${totalSlides}`
2832                    );
2833                }
2834                if (slide.tagName.toLowerCase() === "div" && !slide.hasAttribute("role")) {
2835                    slide.setAttribute("role", "group");
2836                }
2837                if (this.pager && !isImage) {
2838                    slide.setAttribute("role", "tabpanel");
2839                }
2840                if (slide.getAttribute("role") === "group") {
2841                    slide.setAttribute("aria-roledescription", "item");
2842                }
2843            });
2844            if (!this.el.hasAttribute("role") && this.el.tagName !== "SECTION") {
2845                this.el.setAttribute("role", "region");
2846            }
2847            if (!this.el.hasAttribute("aria-roledescription")) {
2848                this.el.setAttribute("aria-roledescription", "carousel");
2849            }
2850            this.track.setAttribute("tabindex", 0);
2851            this.track.setAttribute("aria-live", "polite");
2852        }
2853        setupNav() {
2854            this.navBtns.forEach((btn) => {
2855                btn.setAttribute("disabled", "");
2856                btn.removeAttribute("hidden");
2857                btn.classList.remove("invisible");
2858                btn.style.visibility = null;
2859            });
2860        }
2861        createPager() {
2862            const newPager = document.createElement("nav");
2863            newPager.setAttribute("data-pager", "");
2864            newPager.setAttribute("role", "tablist");
2865            newPager.classList.add(...this.pagerClasses.split(" "));
2866            this.slides.forEach((slide, index) => {
2867                const marker = document.createElement("button");
2868                const slideId = slide.id || `${this.sliderId}-item-${index + 1}`;
2869                marker.setAttribute(this.markerIdName, slideId);
2870                marker.classList.add(...this.markerClasses.split(" "));
2871                this.setupPagerMarker(marker, index);
2872                newPager.appendChild(marker);
2873                if (slide.tagName.toLowerCase() !== "img" && slide.tagName.toLowerCase() !== "picture") {
2874                    slide.removeAttribute("aria-roledescription");
2875                    slide.setAttribute("role", "tabpanel");
2876                }
2877            });
2878            if (this.pager) {
2879                this.pager.setAttribute("role", "tablist");
2880                this.pager.replaceChildren(...newPager.children);
2881            } else {
2882                this.track.after(newPager);
2883                this.pager = this.el.querySelector("[data-pager]");
2884            }
2885        }
2886        setupPager() {
2887            if (!this.pager) return;
2888            this.pager.setAttribute("role", "tablist");
2889            const items = Array.from(this.pager.querySelectorAll("a, button"));
2890            items.forEach((marker, index) => this.setupPagerMarker(marker, index));
2891        }
2892        setupPagerMarker(marker, index) {
2893            const markerId = marker.getAttribute("href")?.slice(1) || marker.getAttribute(this.markerIdName);
2894            const slideId = markerId || `${this.sliderId}-item-${index + 1}`;
2895            if (!markerId) {
2896                marker.setAttribute(this.markerIdName, slideId);
2897            }
2898            marker.setAttribute("role", "tab");
2899            marker.setAttribute("aria-controls", slideId);
2900            marker.setAttribute("aria-posinset", index + 1);
2901            marker.setAttribute("aria-setsize", this.slides.length);
2902            marker.setAttribute("tabindex", "-1");
2903            if (!marker.hasAttribute("aria-label")) {
2904                marker.setAttribute("aria-label", `Slide ${index + 1}`);
2905            }
2906        }
2907        setupObservers() {
2908            if (this.inViewObserver) {
2909                this.inViewObserver.disconnect();
2910            }
2911            this.inViewObserver = new IntersectionObserver(
2912                this.handleInView.bind(this), {
2913                    root: this.track,
2914                    threshold: 0.8
2915                }
2916            );
2917            this.slides.forEach((slide) => this.inViewObserver.observe(slide));
2918        }
2919        setupMutationObserver() {
2920            this.mutationObserver = new MutationObserver(
2921                this.refreshSlides.bind(this)
2922            );
2923            this.mutationObserver.observe(this.track, {
2924                childList: true,
2925                subtree: false
2926            });
2927        }
2928        setupResizeObserver() {
2929            this.resizeObserver = new ResizeObserver(
2930                this.groupPagerMarkers.bind(this)
2931            );
2932            this.resizeObserver.observe(this.el);
2933        }
2934        goToSlideDir(dir = "next") {
2935            const {
2936                firstInViewSlide,
2937                lastInViewSlide
2938            } = this.getInViewItems();
2939            const isPrev = dir === "prev";
2940            let targetSlide = isPrev ? firstInViewSlide?.previousElementSibling : lastInViewSlide?.nextElementSibling;
2941            if (!targetSlide) return;
2942            if (targetSlide.tagName.toLowerCase() === "template") {
2943                targetSlide = isPrev ? targetSlide?.previousElementSibling : targetSlide?.nextElementSibling;
2944            }
2945            targetSlide.scrollIntoView({ block: "nearest", inline: isPrev ? "end" : "start" });
2946        }
2947        pagerToSlide(event) {
2948            if (!event.target.closest("[data-pager]")) return;
2949            const marker = event.target.closest("a, button");
2950            if (!marker) return;
2951            event.preventDefault();
2952            const slideId = marker.getAttribute("href")?.slice(1) || marker.getAttribute(this.markerIdName);
2953            const targetSlide = this.track.querySelector(`#${slideId}`);
2954            targetSlide?.scrollIntoView({ block: "nearest", inline: "start" });
2955        }
2956        eventHandler(event) {
2957            const target = event.target.closest(
2958                "[data-next], [data-prev], [data-pager]"
2959            );
2960            if (!target) return;
2961            if (event.type === "click") {
2962                if (target.hasAttribute("data-next")) {
2963                    this.goToSlideDir("next");
2964                } else if (target.hasAttribute("data-prev")) {
2965                    this.goToSlideDir("prev");
2966                } else if (target.closest("[data-pager]")) {
2967                    this.pagerToSlide(event);
2968                }
2969            }
2970            if (event.type === "keydown" && target.closest("[data-pager]")) {
2971                if (event.key === "ArrowRight") {
2972                    this.goToSlideDir("next");
2973                } else if (event.key === "ArrowLeft") {
2974                    this.goToSlideDir("prev");
2975                }
2976            }
2977        }
2978    };
2979
2980    (() => {
2981        function alpineSnapSlider(Alpine) {
2982            Alpine.directive("snap-slider", (el, {
2983                modifiers
2984            }, {
2985                cleanup
2986            }) => {
2987                const autoPager = modifiers.includes("auto-pager");
2988                const groupPager = modifiers.includes("group-pager");
2989                const slider = new SnapSlider(el, {
2990                    autoPager,
2991                    groupPager
2992                });
2993                cleanup(() => {
2994                    slider.destroy();
2995                });
2996            });
2997        }
2998
2999        document.addEventListener("alpine:init", () => {
3000            window.Alpine.plugin(alpineSnapSlider);
3001        });
3002    })();
3003</script>
3003
3004
3005<script type="module"
3006        src="https://uitgeverijpluim.nl/static/version1790248052/frontend/Pluim/Hyva/nl_NL/Hyva_Theme/js/alpine3.min.js"
3007        defer
3008        crossorigin
3009></script>
vendor: 1 bytes, line 3009
3009
3010<script>
3011    function dispatchMessages(messages, hideAfter) {
3012        const messagesEvent = new CustomEvent("messages-loaded", {
3013            detail: {
3014                messages: messages,
3015                hideAfter: hideAfter
3016            }
3017        });
3018        window.dispatchEvent(messagesEvent);
3019    }
3020
3021        if (typeof hyva === 'undefined' || (!hyva.getBrowserStorage || !hyva.getCookie || !hyva.setCookie)) {
3022        console.warn("Hyvä helpers are not loaded yet. Make sure they are included before this script");
3023    }
3024
3025    (function( hyva, undefined ) {
3026
3027        hyva.initFormKey = () => {
3028            if (typeof hyva.getFormKey !== 'function') return;
3029
3030            const formKey = hyva.getFormKey();
3031            if (!formKey) return;
3032
3033            document.querySelectorAll('input[name="form_key"]').forEach((input) => {
3034                input.value = formKey;
3035            });
3036        }
3037
3038        hyva.initMessages = () => {
3039            try {
3040                const messages = hyva.getCookie('mage-messages');
3041                window.mageMessages = messages ? JSON.parse(decodeURIComponent(messages).replace(/\+/g, ' ')) : [];
3042
3043                dispatchMessages(window.mageMessages);
3044
3045                                // empty `mage-messages` cookie
3046                const skipSetDomain = true;
3047                hyva.setCookie('mage-messages','', -1, skipSetDomain);
3048
3049            } catch (error) {
3050                console.warn('Error parsing Cookie Messages:', error);
3051            }
3052        }
3053
3054        window.addEventListener('DOMContentLoaded', hyva.initFormKey);
3055
3056        window.addEventListener('pageshow', (event) => {
3057            if (event.persisted) {
3058                hyva.initFormKey();
3059            }
3060        });
3061
3062        hyva.alpineInitialized(hyva.initMessages)
3063    }( window.hyva = window.hyva || {} ));
3064    //# sourceURL=hyva-cookies.js
3065</script>
vendor: 1 bytes, line 3065
3065
3066<script>
3067    (() => {
3068        const private_content_key = 'mage-cache-storage';
3069        const private_content_expire_key = 'mage-cache-timeout';
3070        const private_content_version_key = 'private_content_version';
3071        const section_data_ids_key = 'section_data_ids';
3072        const mage_cache_session_id_key = 'mage-cache-sessid';
3073        const last_visited_store_key = 'last_visited_store';
3074
3075        const ttl = 3600;
3076
3077                if (typeof hyva === 'undefined' || (!hyva.getBrowserStorage || !hyva.getCookie || !hyva.setCookie)) {
3078            console.warn("Hyvä helpers are not loaded yet. Make sure they are included before this script");
3079        }
3080
3081        function loadSectionData () {
3082            const browserStorage = hyva.getBrowserStorage();
3083            if (!browserStorage) {
3084                typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
3085                    [{
3086                        type: "warning",
3087                        text: "Schakel LocalStorage in in je browser."
3088                    }]
3089                );
3090                return;
3091            }
3092            try {
3093                let isInvalid = false;
3094
3095                                if (hyva.getCookie(last_visited_store_key) !== CURRENT_STORE_CODE) {
3096                    isInvalid = true;
3097                }
3098                hyva.setCookie(last_visited_store_key, CURRENT_STORE_CODE, false, false);
3099
3100                                if (!hyva.getCookie(mage_cache_session_id_key)) {
3101                    isInvalid = true;
3102                    browserStorage.removeItem(private_content_key);
3103                    const skipSetDomain = true;
3104                    const days = false;
3105                    hyva.setCookie(mage_cache_session_id_key, true, days, skipSetDomain)
3106                }
3107
3108                const cookieVersion = hyva.getCookie(private_content_version_key);
3109                const storageVersion = browserStorage.getItem(private_content_version_key);
3110
3111                if (cookieVersion && !storageVersion || cookieVersion !== storageVersion) {
3112                    isInvalid = true;
3113                }
3114
3115                const privateContentExpires = browserStorage.getItem(private_content_expire_key);
3116                if (privateContentExpires && new Date(privateContentExpires) < new Date()) {
3117                    browserStorage.removeItem(private_content_key);
3118                }
3119
3120                if (isInvalid && cookieVersion) {
3121                    fetchPrivateContent([]);
3122                } else if (cookieVersion && storageVersion && cookieVersion === storageVersion) {
3123                    const privateContent = JSON.parse(browserStorage.getItem(private_content_key));
3124                    if (
3125                        privateContent &&
3126                        privateContentExpires &&
3127                        privateContent.cart &&
3128                        privateContent.customer
3129                    ) {
3130                        dispatchPrivateContent(privateContent);
3131                    } else {
3132                        fetchPrivateContent([]);
3133                    }
3134                } else {
3135                    if (document.getElementById('default-section-data')) {
3136                        const privateContent = JSON.parse(document.getElementById('default-section-data').textContent.trim());
3137                        dispatchPrivateContent(privateContent);
3138                    } else {
3139                        dispatchPrivateContent({});
3140                    }
3141                }
3142
3143            } catch (error) {
3144                console.warn('Error retrieving Private Content:', error);
3145            }
3146        }
3147
3148        hyva.alpineInitialized(loadSectionData)
3149        window.addEventListener('reload-customer-section-data', loadSectionData);
3150
3151                window.processSectionDataBeforeDispatch = window.processSectionDataBeforeDispatch || (data => data);
3152
3153        function dispatchPrivateContent(data) {
3154
3155            const privateContentEvent = new CustomEvent("private-content-loaded", {
3156                detail: {
3157                    data: window.processSectionDataBeforeDispatch(data)
3158                }
3159            });
3160            window.dispatchEvent(privateContentEvent);
3161        }
3162
3163                function fetchPrivateContent(sections) {
3164            fetch(`${BASE_URL}customer/section/load/?sections=${encodeURIComponent(sections.join(','))}`, {
3165                method: 'GET',
3166                headers: {
3167                    'Content-Type': 'application/json',
3168                    'X-Requested-With': 'XMLHttpRequest'
3169                }
3170            })
3171                .then(response => response.ok && response.json())
3172                .then(
3173                    data => {
3174                        if (data) {
3175                            try {
3176                                const browserStorage = hyva.getBrowserStorage();
3177
3178                                // merge new data preserving non-invalidated sections
3179                                const oldSectionData = JSON.parse(browserStorage.getItem(private_content_key) || '{}') || {};
3180
3181                                                                if ((! data.cart || ! data.cart.cartId) && oldSectionData['checkout-data']) {
3182                                    delete oldSectionData['checkout-data'];
3183                                }
3184                                const newSectionData = Object.assign(oldSectionData, data);
3185
3186                                dispatchPrivateContent(newSectionData);
3187
3188                                // don't persist messages, they've been dispatched already
3189                                if (newSectionData.messages && newSectionData.messages.messages ) {
3190                                    newSectionData.messages.messages = [];
3191                                }
3192
3193                                browserStorage.setItem(private_content_key, JSON.stringify(newSectionData));
3194
3195                                const expiresAt = new Date(Date.now() + (ttl * 1000)).toISOString();
3196                                browserStorage.setItem(private_content_expire_key, expiresAt);
3197
3198                                const newCookieVersion = hyva.getCookie(private_content_version_key);
3199                                browserStorage.setItem(private_content_version_key, newCookieVersion);
3200
3201                                // We don't need the section_data_ids in Hyvä, but we store them for compatibility
3202                                // with Luma Fallback. Otherwise, not all sections are loaded in Luma Checkout
3203                                hyva.setCookie(
3204                                    section_data_ids_key,
3205                                    JSON.stringify(
3206                                        Object.keys(data).reduce((sectionDataIds, sectionKey) => {
3207                                            sectionDataIds[sectionKey] = data[sectionKey]['data_id'];
3208                                            return sectionDataIds;
3209                                        }, {})
3210                                    ),
3211                                    false,
3212                                    true
3213                                );
3214                            } catch (error) {
3215                                console.warn("Couldn't store privateContent", error);
3216                            }
3217                        }
3218                    }
3219                )
3220                .catch(error => {
3221                    console.warn("Couldn't fetch privateContent", error);
3222                });
3223        }
3224    })();
3225    //# sourceURL=hyva-private-content.js
3226</script>
vendor: 1 bytes, line 3226
3226
3227<script>
3228    (() => {
3229        document.addEventListener('submit', event => event.target.action = event.target.action.replace('%25uenc%25', hyva.getUenc()));
3230    })()
3231</script>
vendor: 1 bytes, line 3231
3231
3232<script>
3233    (events => {
3234        const dispatchUserInteractionEvent = () => {
3235            events.forEach(type => window.removeEventListener(type, dispatchUserInteractionEvent))
3236            window.dispatchEvent(new Event('init-external-scripts'))
3237        };
3238        events.forEach(type => window.addEventListener(type, dispatchUserInteractionEvent, {once: true, passive: true}))
3239    })(['touchstart', 'mousemove', 'wheel', 'scroll', 'keydown']);
3240    //# sourceURL=hyva-init-external-scripts.js
3241</script>
vendor: 1 bytes, line 3241
3241
3242<script>
3243    document.addEventListener("alpine:init", () => {
3244        Alpine.data('hyvaCmsToggle', () => ({
3245            open: false,
3246            notOpen() {
3247                return !this.open;
3248            },
3249            show() {
3250                this.open = true
3251            },
3252            hide() {
3253                this.open = false
3254            },
3255            toggle() {
3256                this.open = !this.open
3257            }
3258        }))
3259    });
3260</script>
vendor: 1 bytes, line 3260
3260
3261<script>
3262    function initSmileAttribute(options) {
3263        "use strict";
3264
3265        return {
3266            options             : options,
3267            searchLabel         : 'Zoeken (%s)',
3268            showMoreLabel       : 'Toon meer',
3269            showLessLabel       : 'Toon minder',
3270            noSearchResultLabel : 'Geen waarde die overeenkomt met de zoekopdracht <b>%s</b>.',
3271            expanded: false,
3272            items: [],
3273            lastSelectIndex: 0,
3274            maxSize: 0,
3275            displaySearch: false,
3276            displayedItems: [],
3277            fulltextSearch: null,
3278            searchPlaceholder: '',
3279            result: 0,
3280
3281            /**
3282             * Component initialization
3283             */
3284            initialize: function () {
3285
3286                this.items = this.options.items.map(this.addItemId.bind(this));
3287
3288                var lastSelectedIndex = Math.max.apply(null, (this.items.map(
3289                    function (v, k) {return v['is_selected'] ? k : 0;}))
3290                );
3291                this.maxSize = Math.max(this.options.maxSize, lastSelectedIndex + 1);
3292
3293                this.$nextTick(() => {
3294                    this.initSearchPlaceholder();
3295                    this.onShowLess();
3296                    this.displaySearch = this.displayShowMore();
3297                    this.displayedItems = this.getDisplayedItems();
3298                });
3299            },
3300
3301            /**
3302             * Init the place holder
3303             */
3304            initSearchPlaceholder: function () {
3305                var examples = this.items.slice(0, 2).map(function (item) {return item.label});
3306
3307                if (this.items.length > 2) {
3308                    examples.push('...');
3309                }
3310
3311                // Tip to make html chars interpretable by the browser when the value is get from JS.
3312                let placeholder = document.createElement('div');
3313                placeholder.innerHTML = this.searchLabel.replace('%s', examples.join(', '));
3314
3315                this.searchPlaceholder = placeholder.textContent;
3316            },
3317
3318            /**
3319             * Triggered when typing on the search input
3320             */
3321            onSearchChange: function (event) {
3322                var text = event.target.value;
3323                if (text.trim() === "") {
3324                    this.fulltextSearch = null;
3325                    this.onShowLess();
3326                } else {
3327                    this.fulltextSearch = text;
3328                    this.onShowMore();
3329                }
3330
3331                return true;
3332            },
3333
3334            /**
3335             * Triggered when leaving the search field.
3336             */
3337            onSearchFocusOut: function(event) {
3338                var text = event.target.value;
3339                if (text.trim() === "") {
3340                    this.fulltextSearch = null;
3341                }
3342            },
3343
3344            /**
3345             * Retrieve additional Results
3346             *
3347             * @param callback
3348             */
3349            loadAdditionalItems: function (callback) {
3350                fetch(this.options.ajaxLoadUrl, {
3351                    method: 'GET',
3352                    headers: {
3353                        'X-Requested-With': 'XMLHttpRequest',
3354                    },
3355                })
3356                .then((response) =>  response.json())
3357                .then((data) => {
3358                        this.items = data.map(this.addItemId.bind(this));
3359                        this.options.hasMoreItems = false;
3360
3361                        if (callback) {
3362                            return callback();
3363                        }
3364                    }
3365                );
3366            },
3367
3368            /**
3369             * Retrieve items to display
3370             *
3371             * @returns {*}
3372             */
3373            getDisplayedItems: function () {
3374                var items = this.items;
3375
3376                if (this.expanded === false) {
3377                    items = this.items.slice(0, this.maxSize);
3378                }
3379
3380                if (this.getFulltextSearch()) {
3381                    var searchTokens    = this.slugify(this.getFulltextSearch()).split('-');
3382                    var lastSearchToken = searchTokens.splice(-1, 1)[0];
3383
3384                    items = items.filter(function(item) {
3385                        var isValidItem = true;
3386                        var itemTokens = this.slugify(item.label).split('-');
3387                        searchTokens.forEach(function(currentToken) {
3388                            if (itemTokens.indexOf(currentToken) === -1) {
3389                                isValidItem = false;
3390                            }
3391                        })
3392                        if (isValidItem && lastSearchToken) {
3393                            var ngrams = itemTokens.map(function(token) {return token.substring(0, lastSearchToken.length)});
3394                            isValidItem = ngrams.indexOf(lastSearchToken) !== -1;
3395                        }
3396                        return isValidItem;
3397                    }.bind(this))
3398                }
3399                this.result = items.length;
3400                return items;
3401            },
3402
3403            /**
3404             * Does the search have a result
3405             */
3406            hasSearchResult: function () {
3407                return this.getDisplayedItems().length > 0
3408            },
3409
3410            /**
3411             * Search result message
3412             */
3413            getSearchResultMessage : function() {
3414                return this.noSearchResultLabel.replace("%s", '"' + this.getFulltextSearch() + '"')
3415            },
3416
3417            getFulltextSearch : function() {
3418                return this.fulltextSearch;
3419            },
3420
3421            /**
3422             * Callback for the "Show more" button
3423             */
3424            onShowMore: function () {
3425                if (this.options.hasMoreItems) {
3426                    this.loadAdditionalItems(this.onShowMore.bind(this));
3427                } else {
3428                    this.expanded = true;
3429                }
3430
3431                this.displayedItems = this.getDisplayedItems();
3432            },
3433
3434            /**
3435             * Index the text to be searched.
3436             */
3437            slugify: function(text) {
3438                return text.toString().toLowerCase()
3439                    .replace(/\s+/g, '-')                                              // Replace spaces with -
3440                    .replace(/[^\w\u0400-\u052F\u2DE0-\u2DFF\uA640-\uA69F'\-]+/g, '')  // Remove all non-word chars
3441                    .replace(/\-\-+/g, '-')                                            // Replace multiple - with single -
3442                    .replace(/^-+/, '')                                                // Trim - from start of text
3443            },
3444
3445            /**
3446             * Callback for the "Show less" button
3447             */
3448            onShowLess: function () {
3449                this.expanded = false;
3450                this.displayedItems = this.getDisplayedItems();
3451            },
3452
3453            /**
3454             * Check if the filter can be expanded
3455             *
3456             * @returns {boolean}
3457             */
3458            enableExpansion : function () {
3459                return this.options.hasMoreItems || this.items.length > this.maxSize;
3460            },
3461
3462            /**
3463             * Displays the "Show More" link
3464             *
3465             * @returns {*|boolean}
3466             */
3467            displayShowMore: function () {
3468                return this.enableExpansion() && this.expanded === false && !this.getFulltextSearch();
3469            },
3470
3471            /**
3472             * Displays the "Show Less" link
3473             *
3474             * @returns {*|boolean}
3475             */
3476            displayShowLess: function () {
3477                return this.enableExpansion() && this.expanded === true && !this.getFulltextSearch();
3478            },
3479
3480            /**
3481             * Add an id to items.
3482             */
3483            addItemId: function (item) {
3484                item.id = Math.floor(Math.random() * 1000000) + "_option_";
3485                item.displayProductCount = this.options.displayProductCount && (item.count >= 1);
3486
3487                return item;
3488            },
3489        };
3490    };
3491</script>
vendor: 1 bytes, line 3491
3491
3492<script>
3493    function rangeSlider(options, isPriceSlider) {
3494        "use strict";
3495
3496        return {
3497            options: options,
3498            priceSlider: isPriceSlider,
3499            lastThumbUpdatedMax: true,
3500            minfocused: false,
3501            maxfocused: false,
3502            sliderZindex: 20,
3503            messageTemplates: {
3504                "displayOne": '<span class="msg">1 product</span>',
3505                "displayCount": '<span class="msg"><%- count %> producten</span>',
3506                "displayEmpty": '<span class="msg-error">No items in the current range.</span>'
3507            },
3508            minprice: 0,
3509            maxprice: 0,
3510            fromLabel: '',
3511            toLabel: '',
3512            min: 0,
3513            max: 0,
3514            minZindex: 0,
3515            maxZindex: 0,
3516            minthumb: 0,
3517            maxthumb: 0,
3518            message: '',
3519            url: '',
3520            showAdaptiveSlider: false,
3521            intervals: [],
3522
3523            initialize: function () {
3524                this.options = Object.assign({}, this.options, options);
3525                this.url = this.options.urlTemplate;
3526
3527                if (this.options.sliderZindex !== undefined) {
3528                    this.sliderZindex = this.options.sliderZindex;
3529                }
3530
3531                this.showAdaptiveSlider =  false;
3532                if (this.options.showAdaptiveSlider !== undefined) {
3533                    this.showAdaptiveSlider = this.options.showAdaptiveSlider;
3534                }
3535
3536                if (this.options.maxLabelOffset === undefined) {
3537                    this.options.maxLabelOffset = 0.01;
3538                }
3539
3540                if (this.options.rate === undefined) {
3541                    this.options.rate = 1.0000;
3542                }
3543
3544                if (this.showAdaptiveSlider) {
3545                    this._initAdaptiveSliderValues();
3546                } else {
3547                    this._initSliderValues();
3548                }
3549
3550                this.mintrigger();
3551                this.maxtrigger();
3552            },
3553            _initSliderValues: function () {
3554                this.rate = parseFloat(this.options.rate);
3555                this.minprice = Math.floor(this.options.currentValue.from * this.rate);
3556                this.maxprice = Math.round(this.options.currentValue.to * this.rate);
3557                this.intervals    = this.options.intervals.map(
3558                    function(item) { item.value = Math.round(item.value * this.rate); return item}.bind(this)
3559                );
3560                this.min = Math.floor(this.options.minValue * this.rate);
3561                this.max = Math.round(this.options.maxValue * this.rate);
3562            },
3563
3564            _initAdaptiveSliderValues: function () {
3565                this.intervals = this.options.adaptiveIntervals;
3566                this.options.adaptiveIntervals[0].value = Math.floor(this.options.adaptiveIntervals[0].value);
3567                this.minprice = this._getAdaptiveValue(Number(this.options.currentValue.from));
3568                this.maxprice = this._getAdaptiveValue(Number(this.options.currentValue.to));
3569                this.rate = parseFloat(this.options.rate);
3570                this.intervals = this.intervals.map(
3571                    function(item) { item.originalValue = Math.ceil(item.originalValue * this.rate); return item}.bind(this)
3572                );
3573                this.min = this.intervals[0].value;
3574                this.max = this.intervals[this.intervals.length - 1].value;
3575            },
3576            mintrigger: function () {
3577                this.lastThumbUpdatedMax = false;
3578                this.validation();
3579                this.minprice = this._getClosestAdaptiveValue(Math.min(this.minprice, this.maxprice));
3580                this.minthumb = ((this.minprice - this.min) / (this.max - this.min)) * 100;
3581                this._onChangeAfter();
3582            },
3583            maxtrigger: function () {
3584                this.lastThumbUpdatedMax = true;
3585                this.validation();
3586                this.maxprice = this._getClosestAdaptiveValue(Math.max(this.maxprice, this.minprice));
3587                this.maxthumb = ((this.maxprice - this.min) / (this.max - this.min)) * 100;
3588                this._onChangeAfter();
3589            },
3590            updateFocusState: function (targetName, stateValue) {
3591                this[`${targetName}focused`] = stateValue
3592            },
3593            _onChangeAfter: function () {
3594                this._updateSliderZindexes();
3595                this._applyRange();
3596                this._refreshDisplay();
3597            },
3598            validation: function () {
3599                if (/^[\d.]*$/.test(this.minprice)) {
3600                    if (this.minprice > this.max) {
3601                        this.minprice = this._getAdaptiveValue(Number(this.options.currentValue.from));
3602                    }
3603                    if (this.minprice < this.min) {
3604                        this.minprice = this.min;
3605                    }
3606                } else {
3607                    this.minprice = this._getAdaptiveValue(Number(this.options.currentValue.from));
3608                }
3609                if (/^[\d.]*$/.test(this.maxprice)) {
3610                    if (this.maxprice > this.max) {
3611                        this.maxprice = this.max;
3612                    }
3613                    if (this.maxprice < this.min) {
3614                        this.maxprice = this._getAdaptiveValue(Number(this.options.currentValue.to));
3615                    }
3616                } else {
3617                    this.maxprice = this._getAdaptiveValue(Number(this.options.currentValue.to))
3618                }
3619            },
3620            getItemCount: function () {
3621                var from = this.minprice, to = this.maxprice, intervals = this.intervals;
3622                return intervals.map(function (item) {
3623                    return item.value >= from && item.value < to ? item.count : 0;
3624                })
3625                    .reduce(function (a, b) {
3626                        return a + b;
3627                    });
3628            },
3629            _refreshDisplay: function () {
3630                this.count = this.getItemCount();
3631                let minprice = this._getOriginalValue(this.minprice);
3632                if (minprice >= this._getOriginalValue(this.max) ) {
3633                    minprice = this._getOriginalValue(this.max) - this.options.maxLabelOffset;
3634                }
3635                this.fromLabel = this._formatLabel(minprice);
3636
3637                let maxprice = this._getOriginalValue(this.maxprice) - this.options.maxLabelOffset;
3638                if (maxprice < this._getOriginalValue(this.min)) {
3639                    maxprice = this._getOriginalValue(this.maxprice);
3640                }
3641                this.toLabel = this._formatLabel(maxprice);
3642
3643                var messageTemplate = this.messageTemplates[this.count > 0 ? (this.count > 1 ? 'displayCount' : 'displayOne') : 'displayEmpty'];
3644                this.message = this._unescape(messageTemplate.replace('<%- count %>', this.count));
3645            },
3646            _formatLabel: function (value) {
3647                var formattedValue = value;
3648
3649                if (this.priceSlider && this.options.fieldFormat) {
3650                    formattedValue = this._formatPrice(value);
3651                } else if (this.options.fieldFormat) {
3652                    var pattern = this.options.fieldFormat.pattern || '%s';
3653                    formattedValue = pattern.replace(
3654                        '%s',
3655                        Number.parseFloat(formattedValue).toFixed(this.options.fieldFormat.precision)
3656                    );
3657                }
3658
3659                return formattedValue;
3660            },
3661            _formatPrice: function (value) {
3662                var formatter = new Intl.NumberFormat(
3663                    document.documentElement.lang,
3664                    {
3665                        style: 'currency',
3666                        currency: 'EUR',
3667                    }
3668                );
3669
3670                return formatter.format(value);
3671            },
3672            _applyRange: function () {
3673                var range = {
3674                    from : this._getOriginalValue(this.minprice) * (1 / this.rate),
3675                    to   : this._getOriginalValue(this.maxprice) * (1 / this.rate),
3676                };
3677
3678                this.url = this._unescape(this.options.urlTemplate.replace('<%- from %>', range.from).replace('<%- to %>', range.to));
3679            },
3680            _updateSliderZindexes: function () {
3681                this.minZindex = this.sliderZindex;
3682                this.maxZindex = this.sliderZindex;
3683                if (this.minprice == this.maxprice) {
3684                    if (this.minprice === this.min || (this.minprice !== this.min && this.lastThumbUpdatedMax)) {
3685                        this.maxZindex = this.sliderZindex + 1;
3686                    }
3687
3688                    if (this.maxprice === this.max || (this.maxprice !== this.max && !this.lastThumbUpdatedMax)) {
3689                        this.minZindex = this.sliderZindex + 1;
3690                    }
3691
3692                }
3693            },
3694            _getAdaptiveValue : function (value) {
3695                if (!this.showAdaptiveSlider) {
3696                    return value;
3697                }
3698
3699                var adaptiveValue = this.intervals[0].value;
3700                var found = false;
3701                this.intervals.forEach(function (item) {
3702                    if (found === false && item.originalValue === value) {
3703                        adaptiveValue = item.value;
3704                        found = true;
3705                    }
3706
3707                    if (found === false && item.originalValue < value) {
3708                        adaptiveValue = item.value;
3709                    }
3710                });
3711
3712                return adaptiveValue;
3713            },
3714
3715            _getClosestAdaptiveValue : function (value) {
3716                if (!this.showAdaptiveSlider) {
3717                    return value;
3718                }
3719
3720                var closestValue = this.intervals[0].value;
3721                var found = false;
3722                this.intervals.forEach(function (item) {
3723                    if (item.value === value) {
3724                        closestValue = value;
3725                        found = true;
3726                    }
3727
3728                    if (found === false && item.value < value) {
3729                        closestValue = item.value;
3730                    }
3731                });
3732
3733                return closestValue;
3734            },
3735
3736            _getOriginalValue : function (value) {
3737                if (!this.showAdaptiveSlider) {
3738                    return value;
3739                }
3740
3741                var originalValue = null;
3742                this.intervals.forEach(function (item) {
3743                    if (item.value === value) {
3744                        originalValue = item.originalValue;
3745                    }
3746                });
3747
3748                return originalValue;
3749            },
3750
3751            _unescape: function (str) {
3752                return str.replace(/&lt;%|%3C%/g, '<%').replace(/%&gt;|%%3E/g, '%>');
3753            },
3754        }
3755    }
3756</script>
vendor: 1 bytes, line 3756
3756
3757<script type="text/json" id="default-section-data">
3758    {"messages":[],"customer":[],"compare-products":[],"last-ordered-items":[],"cart":[],"directory-data":{"BE":{"name":"Belgi\u00eb","regions":{"609":{"code":"VAN","name":"Antwerpen"},"610":{"code":"WBR","name":"Brabant wallon"},"611":{"code":"BRU","name":"Brussels-Capital Region"},"612":{"code":"WHT","name":"Hainaut"},"614":{"code":"WLG","name":"Li\u00e8ge"},"613":{"code":"VLI","name":"Limburg"},"615":{"code":"WLX","name":"Luxembourg"},"616":{"code":"WNA","name":"Namur"},"617":{"code":"VOV","name":"Oost-Vlaanderen"},"618":{"code":"VBR","name":"Vlaams-Brabant"},"619":{"code":"VWV","name":"West-Vlaanderen"}}},"NL":{"name":"Nederland"},"data_id":1790277019},"instant-purchase":[],"loggedAsCustomer":[],"captcha":[],"persistent":[],"review":[],"wishlist":{"items":[]},"hyva_commerce_messages":[],"hyva_checkout":[],"gtm-checkout":[],"recently_viewed_product":[],"recently_compared_product":[],"product_data_storage":[],"paypal-billing-agreement":[],"paypal-buyer-country":[]}</script>
vendor: 1 bytes, line 3758
3758
3759<script>
3760    window.processSectionDataBeforeDispatch && (window.processSectionDataBeforeDispatch = new Proxy(window.processSectionDataBeforeDispatch, {
3761        apply(target, thisArg, [data]) {
3762            data.hyva_commerce_messages = data.hyva_commerce_messages || {};
3763            return target.call(thisArg, data);
3764        }
3765    }));
3766</script>
vendor: 1 bytes, line 3766
3766
3767<script>if (!window.__rumvisionSkip) {
3768    window.rumv = window.rumv || function() { (window.rumv.q = window.rumv.q || []).push(arguments) };
3769    (function(rum, vi,si,on) {
3770        var s = JSON.parse( sessionStorage.getItem('rumv') || '{"pageviews":0}' ); s.pageviews++;
3771        if ( s.urls && s.regex && ( s.page = eval('('+s.regex+')')( s.urls, vi.location.pathname ) ) && !s.page.type ) {
3772            return sessionStorage.setItem('rumv', JSON.stringify( s ) );
3773        }
3774
3775        vi.rumv.storage = s;
3776        var head = si.querySelector('head'), js = si.createElement('script');
3777        js.src = 'https://d5yoctgpv4cpx.cloudfront.net/'+rum+'/v5-'+vi.location.hostname+'.js';
3778        head.appendChild(js);
3779    })( 'RUM\u002D409605D6E0', window, document, 'uitgeverijpluim.nl' );
3780}</script>
3780</div>
3780<script nonce="eHFmYXJhOTZ4MG52czd4OGRxa2s3b2M4Z2RvdTVydGI=">
3781    yireoGoogleTagManager2Pusher({"version":"3.10.7","page_type":"category","page_path":"https:\/\/uitgeverijpluim.nl\/boeken\/fictie","page_title":"Fictie  | Uitgeverij Pluim","virtual_page":"\/boeken\/fictie","breadcrumb":["Boeken","Fictie"],"user":{"has_transacted":false},"category_size":12,"products":[{"item_id":"Red","item_sku":"Red","magento_sku":"Red","magento_id":"863","item_name":"Red","item_list_id":"3","item_list_name":"Boeken","price":14.99,"item_category":"Boeken","item_category2":"Fictie","index":1,"quantity":1},{"item_id":"Voor Issa","item_sku":"Voor Issa","magento_sku":"Voor Issa","magento_id":"790","item_name":"Voor Issa","item_list_id":"3","item_list_name":"Boeken","price":12.99,"item_category":"Boeken","item_category2":"Fictie","index":2,"quantity":1},{"item_id":"Nvrlnd","item_sku":"Nvrlnd","magento_sku":"Nvrlnd","magento_id":"869","item_name":"Nvrlnd","item_list_id":"3","item_list_name":"Boeken","price":12.99,"item_category":"Boeken","item_category2":"Fictie","index":3,"quantity":1}],"category_id":"4","category_name":"Fictie"}, 'push (initial page) [data-layer.phtml]');
3782        yireoGoogleTagManager2Pusher({"event":"view_item_list","ecommerce":{"items":[{"item_id":"Red","item_sku":"Red","magento_sku":"Red","magento_id":"863","item_name":"Red","item_list_id":"3","item_list_name":"Boeken","price":14.99,"item_category":"Boeken","item_category2":"Fictie","index":1,"quantity":1},{"item_id":"Voor Issa","item_sku":"Voor Issa","magento_sku":"Voor Issa","magento_id":"790","item_name":"Voor Issa","item_list_id":"3","item_list_name":"Boeken","price":12.99,"item_category":"Boeken","item_category2":"Fictie","index":2,"quantity":1},{"item_id":"Nvrlnd","item_sku":"Nvrlnd","magento_sku":"Nvrlnd","magento_id":"869","item_name":"Nvrlnd","item_list_id":"3","item_list_name":"Boeken","price":12.99,"item_category":"Boeken","item_category2":"Fictie","index":3,"quantity":1}]}}, 'push (page-based event) [data-layer.phtml]');
3783    </script>
vendor: 1 bytes, line 3783
3783
3784<script nonce="eHFmYXJhOTZ4MG52czd4OGRxa2s3b2M4Z2RvdTVydGI=">
3785    function yireoGoogleTagManager2FindParentElementWithName(element, parentTagName) {
3786        if (!element.parentElement) {
3787            return null;
3788        }
3789
3790        if (element.parentElement.tagName.toLowerCase() === parentTagName) {
3791            return element.parentElement;
3792        }
3793
3794        return yireoGoogleTagManager2FindParentElementWithName(element.parentElement, parentTagName);
3795    }
3796
3797    const products = document.querySelectorAll('.products\u0020a.product,\u0020.products\u0020a.product\u002Ditem\u002Dlink,\u0020.card\u0020a.product\u002Ditem\u002Dlink,\u0020.card\u0020a.product\u002Ditem\u002Dphoto,\u0020.card\u0020a.btn\u002Dprimary');
3798    if (products && products.length > 0) {
3799        products.forEach(function(product) {
3800            product.addEventListener('click', function(event, s) {
3801                let parentElement = yireoGoogleTagManager2FindParentElementWithName(event.target, 'form');
3802                if (!parentElement) {
3803                    return;
3804                }
3805
3806                const inputElement = parentElement.querySelector('input[name="product"]');
3807                const productId = inputElement.value;
3808                const productData = window['YIREO_GOOGLETAGMANAGER2_PRODUCT_DATA_ID_' + productId] || {};
3809                productData.item_id = productId;
3810
3811                const eventData = {
3812                    'event': 'select_item',
3813                    'ecommerce': {
3814                        'items': [productData]
3815                    }
3816                }
3817
3818                yireoGoogleTagManager2Pusher(eventData, 'push (page event "select_item") [script-product-clicks.phtml]');
3819
3820                if (window['YIREO_GOOGLETAGMANAGER2_DEBUG_CLICKS'] && confirm("Press to continue with redirect") === false) {
3821                    event.preventDefault();
3822                }
3823            });
3824        })
3825    }
3826</script>
vendor: 1 bytes, line 3826
3826
3827<script nonce="eHFmYXJhOTZ4MG52czd4OGRxa2s3b2M4Z2RvdTVydGI=">
3828    window.addEventListener('private-content-loaded', function(event) {
3829        window.dataLayer = window.dataLayer || [];
3830
3831        var isEmpty = function (variable) {
3832            if (typeof variable === 'undefined') {
3833                return true;
3834            }
3835
3836            if (Array.isArray(variable) && variable.length === 0) {
3837                return true;
3838            }
3839
3840            return typeof variable === 'object' && Object.keys(variable).length === 0;
3841        }
3842
3843        var getSectionNames = function () {
3844            return ['cart', 'customer'];
3845        }
3846
3847        getSectionNames().forEach(function (sectionName) {
3848            if (!event.detail.data[sectionName].gtm) {
3849                return;
3850            }
3851
3852            const attributes = event.detail.data[sectionName].gtm;
3853            if (!Object.keys(attributes).length > 0) {
3854                return;
3855            }
3856
3857            yireoGoogleTagManager2Pusher(attributes, 'push (attributes) [script-additions.phtml]');
3858        });
3859
3860        let attributes = {};
3861        getSectionNames().forEach(function (sectionName) {
3862            if (!event.detail.data[sectionName].gtm_events) {
3863                return;
3864            }
3865
3866            const gtmEvents = event.detail.data[sectionName].gtm_events;
3867            for (const [eventId, eventData] of Object.entries(gtmEvents)) {
3868                yireoGoogleTagManager2Logger('found event in section "' + sectionName + '"', eventData);
3869
3870                const metaData = eventData.meta;
3871                yireoGoogleTagManager2Pusher(eventData, 'push (customerData "' + sectionName + '"  event "' + eventId + '")');
3872
3873                if (!metaData || metaData.cacheable !== true) {
3874                    yireoGoogleTagManager2Logger('removing event "' + eventId + '" from section "' + sectionName + '"')
3875
3876                    let mageCacheStorage = event.detail.data;
3877
3878                    if (mageCacheStorage !== null
3879                        && typeof mageCacheStorage[sectionName] !== 'undefined'
3880                        && typeof mageCacheStorage[sectionName].gtm_events !== 'undefined'
3881                        && eventId in mageCacheStorage[sectionName].gtm_events) {
3882                        delete mageCacheStorage[sectionName].gtm_events[eventId];
3883                    }
3884
3885                    window.localStorage.setItem('mage-cache-storage', JSON.stringify(mageCacheStorage));
3886                }
3887            }
3888        });
3889
3890        if (false === isEmpty(attributes)) {
3891            yireoGoogleTagManager2Pusher(attributes, 'dataLayer (initial js) [script-additions.phtml]');
3892        }
3893    });
3894
3895    window.addEventListener('toggle-cart', (event) => {
3896        if (!event.detail || !event.detail.isOpen) {
3897            return;
3898        }
3899
3900        window.dispatchEvent(new CustomEvent('minicart_collapse'));
3901    });
3902</script>
3902
3903</body>
3904</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.