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 Pluim" 1227 aria-label="store logo"> 1228 <img src="https://uitgeverijpluim.nl/media/logo/websites/1/logo_sxfncc_1.jpg" 1229 title="Uitgeverij Pluim" 1230 alt="Uitgeverij 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 de hele 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 laag naar hoog 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 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"> </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 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 op de 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">© 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(/<%|%3C%/g, '<%').replace(/%>|%%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.