1<!doctype html> 2<html lang="en"> 3<head > 4 <meta charset="utf-8"/> 5<meta name="title" content="Banham | Experts in Security Services Since 1926"/> 6<meta name="description" content="Welcome to Banham, market-leaders in the security industry since 1926, and the perfect people to help secure your property"/> 7<meta name="robots" content="INDEX,FOLLOW"/> 8<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/> 9<title>Banham | Experts in Security Services Since 1926</title> 10 <link rel="stylesheet" type="text/css" media="all" href="https://www.banham.co.uk/static/version1790667712/frontend/BraveBison/default/en_GB/css/styles.min.css" /> 11<link rel="stylesheet" type="text/css" media="all" href="https://www.banham.co.uk/static/version1790667712/frontend/BraveBison/default/en_GB/Amasty_Storelocator/vendor/chosen/chosen.min.css" /> 12<link rel="stylesheet" type="text/css" media="all" href="https://www.banham.co.uk/static/version1790667712/frontend/BraveBison/default/en_GB/Trustpilot_Reviews/css/trustpilot.min.css" /> 13 14<link rel="icon" type="image/x-icon" href="https://www.banham.co.uk/media/favicon/stores/1/Banham_Logo_FA-04.png" /> 15<link rel="shortcut icon" type="image/x-icon" href="https://www.banham.co.uk/media/favicon/stores/1/Banham_Logo_FA-04.png" /> 16<!-- [HEAD Scripts and Style Sheets (store)] --> 17<link rel="preload" as="image" href="https://www.banham.co.uk/media/wysiwyg/Stronger_Together_-_Desktop_Compressed_.gif"/> 18<link rel="preload" as="image" href="https://www.banham.co.uk/media/wysiwyg/Stronger_Together_-_Mobile_4.gif"/> 19 20<meta name="google-site-verification" content="tlRCJ39nLLEuusvkrLQgs0hMLKjni_c_coG3u7dexYs" /> 21 22<meta name="p:domain_verify" content="FXieQwTo44FvqeK7UsLQWQazWnZ4AsMS"/> 23 24<meta name="trustpilot-one-time-domain-verification-id" content="uzIvKWRHCcxSIDidTTo2LpYtOOFSJrSEaWynJNnM"/> 25<link rel="dns-prefetch" href="//consent.cookiebot.com" /> 26 27<style type="text/css"> 28 /* to hide slide out form on these 2 pages */ 29.catalog-category-view.categorypath-safes .enquiries-slide , 30.catalog-category-view.category-gates-grilles-shutters .enquiries-slide { 31 display: none !important; 32 } 33.product-social-links__items a.action.mailto.friend {display:none; } 34 35 36.product-add-form .wishlist { 37 margin-top: 13px; 38 display: block; 39} 40.product-add-form .wishlist svg { 41 width: 20px; 42} 43 44.payment-method input#sagepaysuitepi + label { 45display: inline-block !important; 46} 47 48 49 50 51</style> 52 53<meta name="google-site-verification" content="jaCDjXkWDNHwKHL7TPsqP_5XnPTLp5HMop_7H2Cu5SE" /> 54 55 56<style> 57/*style for task BAN-2451*/ 58.nav-sections .navigation li.level0.mofx-mmn-our-difference { 59 order: -1; 60} 61 @media only screen and (max-width: 991px) { 62 .navigation ul { 63 display: flex; 64 flex-direction: column; 65 } 66} 67/* end style for task BAN-2451*/ 68.enquiries-slide-open .pca .pcaautocomplete { 69 max-width: 260px; 70 } 71.enquiries-slide-open .pca .pcaitem { 72 white-space: normal; 73 } 74.pca .pcaautocomplete { 75 max-width: 80vw; 76 } 77 78 .pca .pcaitem { 79 white-space: normal!important; 80 } 81/* M60 hotfix */ 82.product-item-info.empty-carousel-item { visibility:hidden; } 83.admin__data-grid-loading-mask {display:none;} 84.catalogsearch-result-index #instant-search-results-container .ais-Hits-item>[class^=col-] { 85 width: 100% !important; 86} 87 88 89 90</style> 91 92<!-- [/HEAD Scripts and Style Sheets (store)] --> 93<meta name="p:domain_verify" content="sYFFWHIheNKJDGZA1eZkGvMNvgwmO745"/> 94 95 96<!-- AB experiment code for doors page --> 97 98 99 100<meta name="facebook-domain-verification" content="v18ynkkipzgbfnaq7fxpdsfu3en2s9" /> 101 102 103<!-- Start VWO Async SmartCode --> 104<link rel="preconnect" href="https://dev.visualwebsiteoptimizer.com" /> 105 106<!-- End VWO Async SmartCode --> 107 108 109<meta name="og:title" content="Banham | Experts in Security Services Since 1926"> 110<meta name="og:description" content="Welcome to Banham, market-leaders in the security industry since 1926, and the perfect people to help secure your property"> 111<meta name="og:image" content="https://a.storyblok.com/f/286600090739628/1200x630/18282bfc13/homepage-og.png"> 112<meta name="twitter:title" content="Banham | Experts in Security Services Since 1926"> 113<meta name="twitter:description" content="Welcome to Banham, market-leaders in the security industry since 1926, and the perfect people to help secure your property"> 114<meta name="twitter:image" content="https://a.storyblok.com/f/286600090739628/1200x630/18282bfc13/homepage-og.png"> 115<link rel="canonical" href="https://www.banham.co.uk/"> 116 </head> 117<body class="store-default logged-out storyblok-home storyblok-page-view page-layout-1column" id="html-body">
118 <!-- [BODY Miscellaneous HTML/ Scripts (store)] --> 119 120<!-- [/BODY Miscellaneous HTML/ Scripts (store)] --> 121<style> 122 #webform-slide-out-yk8I5y { 123 background: #ffffff; 124 border-color: #ffffff; 125 width: 100%; 126 } 127 128 @media (min-width: 768px) { 129 #webform-slide-out-yk8I5y { 130 width: 720px; 131 } 132 } 133 134 /* #webform-slide-out-button-yk8I5y { 135 background: #3c4a54; 136 color: #ffffff; 137 width: 25px; 138 height: 160px; 139 } 140 .slide-out-button-icon{ 141 fill:#ffffff; 142 width: 1.25rem; 143 height: 1.25rem; 144 margin-right: .5rem; 145 } */ 146</style> 147 148 <style>.webforms .actions-toolbar{border:none}.webforms .actions-toolbar.submit-center .primary{margin:0 auto}.webforms .actions-toolbar.submit-left{flex-direction:row}.webforms .actions-toolbar.submit-left .primary{float:left}.webforms .actions-toolbar.submit-right{flex-direction:row-reverse}.webforms .actions-toolbar.submit-right .primary{float:right}.webforms .actions-toolbar button.sbtn-l{line-height:2rem;font-size:1.5rem}.webforms .actions-toolbar button.sbtn-xl{line-height:2.3rem;font-size:1.8rem}.webforms .field-error input,.webforms .field-error textarea,.webforms .field-error .drop-zone{border-color:red}.webforms .inline-elements .choice{display:inline-flex}@media screen and (min-width:640px){.webforms .wf-sm-row{clear:left}}@media screen and (min-width:768px){.webforms .wf-md-row{clear:left}.webforms .wf-sm-row{clear:unset}}@media screen and (min-width:1024px){.webforms .wf-lg-row{clear:left !important}.webforms .wf-md-row,.webforms .wf-sm-row{clear:unset}}.webforms figcaption{padding:5px 0;margin:5px}.webforms .image_picker_image{height:auto}.webforms .mm-map{height:400px;width:100%}.webforms .mm-map-search{margin:2px;width:40%}.webforms .mm-logic-hidden{display:none !important}</style> 149<div x-data="{ isOpen: false }" 150 151 x-init="$watch('isOpen', function(val) { if (val && window.resetSlideOutForm) window.resetSlideOutForm('yk8I5y'); })"> 152 <!-- Slide-out form --> 153 <div id="webform-slide-out-yk8I5y" 154 x-cloak 155 class="webform-content fixed top-0 right-0 md:max-w-[80%] md:max-w-auto md:w-1/3 lg:w-1/4 h-full shadow-2xl transform transition-transform duration-300 ease-in-out z-[9999]" 156 :class="{ 'translate-x-full': !isOpen }" 157 @click="isOpen && (document.documentElement.style.overflow = isOpen ? 'hidden' : 'auto')" 158 x-effect="document.documentElement.style.overflow = isOpen ? 'hidden' : 'auto'"> 159 <div class="md:px-36 md:pt-[60px] md:pb-10 px-5 py-10 h-full overflow-y-auto relative max-h-svh flex flex-col"> 160 <!-- Close button --> 161 <button @click="isOpen = false" class="close-btn absolute top-5 right-5 text-black hover:text-gray-700"> 162 <i class="ti ti-x text-xl"></i> 163 </button> 164 165 <div id="webform_yk8I5y_heading"> 166 167 </div> 168 169 <div id="webform_yk8I5y_success_text" class="form-success-message flex flex-col h-full hyva-success-msg" 170 style="display:none"></div> 171 172 173 174 <div id="webform_yk8I5y_form" 175 class="webforms webform-slide-out-form flex-shrink-0 flex-grow"> 176 177 <div id="yk8I5yform_description" class="webforms-description-text text-center"> 178 <p class="absolute top-0 left-0 right-0 bg-primary-darkGray mb-6 !text-sm" style="padding: 10px;">Prefer to call? Reach us on <a style="text-decoration:none" class="!text-sm" href="tel:+442078193730">020 7819 3730 </a></p> 179 </div> 180 181 182 183<form method="post" 184 name="webform_yk8I5y" 185 id="webform_yk8I5y" enctype="multipart/form-data" 186 class="form h-full enquiry-form survey-booking-form" 187 style="" 188 x-cloak 189 x-data="bbPCAForm({ loqateFindUrl: window.loqateFindUrl, loqateRetrieveUrl: window.loqateRetrieveUrl })" 190 @alpine:initialized="init()" 191> 192 193 <input name="form_key" type="hidden" value=""/> 194 <input type="hidden" name="submitForm_2" value="1"/> 195 <input type="hidden" name="form_id" value="2"/> 196 197 198 <input name='field[1035]' 199 id="enquiry_topic_product" 200 type='hidden' 201 value='' 202 /><input name='field[762]' 203 id="utm_source" 204 type='hidden' 205 value='' 206 /><input name='field[768]' 207 id="utm_campaign" 208 type='hidden' 209 value='' 210 /><input name='field[765]' 211 id="utm_medium" 212 type='hidden' 213 value='' 214 /> 215 216 <div id="wizardyk8I5y" class="h-full"> 217 218 <div class="step-app h-full flex flex-col pt-6" > 219 <div x-data="stepProgress()" x-init="init()" class=""> 220 <!-- Header: Question X of N --> 221 <div class="text-black text-sm font-normal leading-tight mb-5"> 222 Question <span x-text="currentStep"></span> of <span x-text="totalSteps"></span> 223 </div> 224 225 <!-- Progress bar --> 226 <div class="w-full h-1 bg-[#EDEDED] rounded"> 227 <div class="h-1 bg-primary rounded transition-all duration-300" 228 :style="`width: ${percent()}%`"></div> 229 </div> 230 231 <!-- Keep original steps for wizard logic, but hide them --> 232 <ul class="step-steps hidden" x-ref="stepList"> 233 <li id="title_fieldset_yk8I5y
23356" 234 class=""> 235 <a href="#stepyk8I5y56"> 236 Which Banham products are you interested in? </a> 237 </li> 238 <li id="title_fieldset_yk8I5y59" 239 class=""> 240 <a href="#stepyk8I5y59"> 241 About Your Enquiry </a> 242 </li> 243 <li id="title_fieldset_yk8I5y62" 244 class=""> 245 <a href="#stepyk8I5y62"> 246 Weâll need a little information about you </a> 247 </li> 248 <li id="title_fieldset_yk8I5y65" 249 class=""> 250 <a href="#stepyk8I5y65"> 251 Search for the property youâd like surveyed </a> 252 </li> 253 <li id="title_fieldset_yk8I5y68" 254 class=""> 255 <a href="#stepyk8I5y68"> 256 Choose your preferred survey date </a> 257 </li> 258 </ul> 259 </div> 260 <div class="step-content"> 261 <div class="step-tab-panel form-step active [&_h3]:my-10" id="stepyk8I5y56"> 262 <fieldset id="fieldset_yk8I5y56" class="fieldset-56 hyva-fieldset lg:w-full md:w-full w-full " style="" role="group" aria-labelledby="legend_yk8I5y56"> <h3 class="hyva-fieldset-label" id="legend_yk8I5y56"><span> Which Banham products are you interested in?</span></h3> <div id="field_yk8I5y626" role="group" class="field hyva-field lg:w-full md:w-full w-full type-text webforms-fields-626 webforms-fields-enquiry-topic " > <div class="control" role="group" ><input id='enquiry-topic' 263 name='field[626]' 264 type='text' 265 class='input-text hyva-text' 266 style='' 267 value="" 268 placeholder="" 269 aria-label="Enquiry Topic" 270 /> 271 </div></div> <div id="field_yk8I5y1778" role="group" class="field hyva-field lg:w-full md:w-full w-full type-swatches webforms-fields-1778 webforms-fields-also_interested_in " > <div class="control" role="group" > <style></style> 272<div class="mm-swatches"> 273 274 <div class='field choice option-0 mr-3 overflow-hidden text-sm leading-5 w-full self-stretch rounded inline-flex flex-col justify-start items-start gap-2' 275 style='' 276 > 277 <label id="fieldyk8I5y17780_label" class="w-full "> 278 <input id='fieldyk8I5y17780' 279 name='field[1778][]' 280 type='checkbox' 281 value="Locks" 282 class=' text-sm leading-5 w-full' 283 data-uid="yk8I5y1778" 284 aria-labelledby="fieldyk8I5y17780_label" 285 /> 286 <span style='' class="text-sm leading-5 w-full ">Locks</span> 287 </label> 288 289 </div> 290 291 292 <div class='field choice option-1 mr-3 overflow-hidden text-sm leading-5 w-full self-stretch rounded inline-flex flex-col justify-start items-start gap-2' 293 style='' 294 > 295 <label id="fieldyk8I5y17781_label" class="w-full "> 296 <input id='fieldyk8I5y17781' 297 name='field[1778][]' 298 type='checkbox' 299 value="Intruder Alarm" 300 class=' text-sm leading-5 w-full' 301 data-uid="yk8I5y1778" 302 aria-labelledby="fieldyk8I5y17781_label" 303 />
304 <span style='' class="text-sm leading-5 w-full ">Alarms</span> 305 </label> 306 307 </div> 308 309 310 <div class='field choice option-2 mr-3 overflow-hidden text-sm leading-5 w-full self-stretch rounded inline-flex flex-col justify-start items-start gap-2' 311 style='' 312 > 313 <label id="fieldyk8I5y17782_label" class="w-full "> 314 <input id='fieldyk8I5y17782' 315 name='field[1778][]' 316 type='checkbox' 317 value="Doors" 318 class=' text-sm leading-5 w-full' 319 data-uid="yk8I5y1778" 320 aria-labelledby="fieldyk8I5y17782_label" 321 /> 322 <span style='' class="text-sm leading-5 w-full ">Doors</span> 323 </label> 324 325 </div> 326 327 328 <div class='field choice option-3 mr-3 overflow-hidden text-sm leading-5 w-full self-stretch rounded inline-flex flex-col justify-start items-start gap-2' 329 style='' 330 > 331 <label id="fieldyk8I5y17783_label" class="w-full "> 332 <input id='fieldyk8I5y17783' 333 name='field[1778][]' 334 type='checkbox' 335 value="Keyholding" 336 class=' text-sm leading-5 w-full' 337 data-uid="yk8I5y1778" 338 aria-labelledby="fieldyk8I5y17783_label" 339 /> 340 <span style='' class="text-sm leading-5 w-full ">Keyholding</span> 341 </label> 342 343 </div> 344 345 346 <div class='field choice option-4 mr-3 overflow-hidden text-sm leading-5 w-full self-stretch rounded inline-flex flex-col justify-start items-start gap-2' 347 style='' 348 > 349 <label id="fieldyk8I5y17784_label" class="w-full "> 350 <input id='fieldyk8I5y17784' 351 name='field[1778][]' 352 type='checkbox' 353 value="Safes" 354 class=' text-sm leading-5 w-full' 355 data-uid="yk8I5y1778" 356 aria-labelledby="fieldyk8I5y17784_label" 357 /> 358 <span style='' class="text-sm leading-5 w-full ">Safes</span> 359 </label> 360 361 </div> 362 363 364 <div class='field choice option-5 mr-3 overflow-hidden text-sm leading-5 w-full self-stretch rounded inline-flex flex-col justify-start items-start gap-2' 365 style='' 366 > 367 <label id="fieldyk8I5y17785_label" class="w-full "> 368 <input id='fieldyk8I5y17785' 369 name='field[1778][]' 370 type='checkbox' 371 value="Metalwork" 372 class=' text-sm leading-5 w-full' 373 data-uid="yk8I5y1778" 374 aria-labelledby="fieldyk8I5y17785_label" 375 /> 376 <span style='' class="text-sm leading-5 w-full ">Gates / Grilles & Shutters</span> 377 </label> 378 379 </div> 380 381 </div> 382 383 384 </div></div></fieldset> 385 <div class="actions-toolbar w-full"> 386 </div> 387 </div> 388 <div class="step-tab-panel form-step [&_h3]:my-10" id="stepyk8I5y59">
389 <fieldset id="fieldset_yk8I5y59" class="fieldset-59 hyva-fieldset lg:w-full md:w-full w-full " style="" role="group" aria-labelledby="legend_yk8I5y59"> <h3 class="hyva-fieldset-label" id="legend_yk8I5y59"><span>About Your Enquiry</span></h3> <div id="field_yk8I5y8" role="group" class="field hyva-field lg:w-full md:w-full w-full type-textarea webforms-fields-8 webforms-fields-enquiry required" > <div class="control" role="group" > <textarea id='fieldyk8I5y8' 390 name='field[8]' 391 class=' hyva-textarea' 392 rows='5' 393 style='' 394 placeholder="Enter your enquiry here" 395 data-validate="{'required-entry':true}" 396 aria-label="Your Enquiry" 397 aria-required="true" ></textarea> </div></div></fieldset> 398 <div class="actions-toolbar w-full"> 399 </div> 400 </div> 401 <div class="step-tab-panel form-step [&_h3]:my-10" id="stepyk8I5y62"> 402 <fieldset id="fieldset_yk8I5y62" class="fieldset-62 hyva-fieldset lg:w-full md:w-full w-full " style="" role="group" aria-labelledby="legend_yk8I5y62"> <h3 class="hyva-fieldset-label" id="legend_yk8I5y62"><span>Weâll need a little information about you</span></h3> <div id="field_yk8I5y5" role="group" class="field hyva-field lg:w-full md:w-full w-full type-select webforms-fields-5 webforms-fields-enquiry-type required" > <div class="control" role="group" > <select id='fieldyk8I5y5' name='field[5]' class=' hyva-select' style='' data-validate="{'validate-select':true}" aria-label="Enquiry Type" aria-required="true" > <option value="Residential" >Residential</option> <option value="Business" >Business</option></select> </div></div> <div id="field_yk8I5y23" role="group" class="field hyva-field lg:w-full md:w-full w-full type-text mm-logic-hidden webforms-fields-23 webforms-fields-company_name required" > <div class="control" role="group" ><input id='company_name' 403 name='field[23]' 404 type='text' 405 class='input-text hyva-text' 406 style='' 407 value="" 408 placeholder="Company name" 409 data-validate="{'required-entry':true, 'validate-length-max':'60'}" 410 aria-label="Company Name" 411 aria-required="true" /> 412 </div></div> <div id="field_yk8I5y11" role="group" class="field hyva-field lg:w-full md:w-full w-full type-text webforms-fields-11 webforms-fields-firstname required" > <div class="control" role="group" ><input id='firstname' 413 name='field[11]' 414 type='text' 415 class='input-text hyva-text' 416 style='' 417 value="" 418 placeholder="Please enter your first name here" 419 data-validate="{'required-entry':true, 'validate-length-max':'30'}" 420 aria-label="First name" 421 aria-required="true" x-data="MageMe.WebForms.customerData()" 422 @private-content-loaded.window="receiveCustomerData($event.detail.data)" 423 x-model="customer.customer_firstname" 424 /> 425 </div></div> <div id="field_yk8I5y14" role="group" class="field hyva-field lg:w-full md:w-full w-full type-text webforms-fields-14 webforms-fields-lastname required" > <div class="control" role="group" ><input id='lastname' 426 name='field[14]' 427 type='text' 428 class='input-text hyva-text' 429 style='' 430 value="" 431 placeholder="Please enter your last name here" 432 data-validate="{'required-entry':true, 'validate-length-max':'30'}" 433 aria-label="Last name" 434 aria-required="true" x-data="MageMe.WebForms.customerData()" 435 @private-content-loaded.window="receiveCustomerData($event.detail.data)" 436 x-model="customer.customer_lastname" 437 /> 438 </div></div> <div id="field_yk8I5y2" role="group" class="field hyva-field lg:w-full md:w-full w-full type-email webforms-fields-2 webforms-fields-email required" > <div class="control" role="group" > <input id='fieldyk8I5y2' name='field[2]' type='email' class='input-text hyva-text' style='' value="" placeholder="[email protected]" data-validate="{'required-entry':true, 'validate-email':true}" aria-label="Email" aria-required="true" x-data="mmWebformsEmailDatafieldyk8I5y2" @input="trimSpace" @private-content-loaded.window="setCustomerData" /> </div></div> <div id="field_yk8I5y17" role="group" class="field hyva-field lg:w-full md:w-full w-full type-text webforms-fields-17 webforms-fields-contact-number required" > <div class="control" role="group" ><input id='contact-number' 439 name='field[17]' 440 type='text' 441 class='input-text hyva-text' 442 style='' 443 value="" 444 placeholder="Contact number" 445 data-validate="{'required-entry':true, 'mm-pattern':'^((\\d[\\-. ]?)?((\\(\\{d3}\\))|\\d{3}))?[\\-. ]?\\d{3,4}[\\-. ]?\\d{4}$'}" 446 data-msg-required-entry="Please enter a valid phone number."data-msg-mm-pattern="Please enter a valid phone number. For example (123) 456-7890 or 123-456-7890." aria-label="Contact number" 447 aria-required="true" /> 448 <div style="display:none" class="hyva-comment-label" id="advice-required-entry-fieldyk8I5y17">Please enter a valid phone number.</div> </div></div></fieldset> 449 <div class="actions-toolbar w-full"> 450 </div> 451 </div> 452 <div class="step-tab-panel form-step [&_h3]:my-10" id="stepyk8I5y65"> 453 <fieldset id="fieldset_yk8I5y65" class="fieldset-65 hyva-fieldset address-search lg:w-full md:w-full w-full " style="" role="group" aria-labelledby="legend_yk8I5y65"> <h3 class="hyva-fieldset-label" id="legend_yk8I5y65"><span>Search for the property youâd like surveyed</span></h3>
453 <div id="field_yk8I5y1781" role="group" class="field hyva-field search-address lg:w-full md:w-full w-full type-text webforms-fields-1781 " > <div class="control" role="group" ><input id='' 454 name='field[1781]' 455 type='text' 456 class='input-text hyva-text' 457 style='' 458 value="" 459 placeholder="Search Address" 460 aria-label="Search Address" 461 /> 462 </div></div> <div id="field_yk8I5y20" role="group" class="field hyva-field lg:w-full md:w-full w-full type-text webforms-fields-20 webforms-fields-street required" > <div class="control" role="group" ><input id='street' 463 name='field[20]' 464 type='text' 465 class='input-text hyva-text' 466 style='' 467 value="" 468 placeholder="Address Line 1" 469 data-validate="{'required-entry':true, 'validate-length-max':'40'}" 470 aria-label="Street & Number" 471 aria-required="true" /> 472 </div></div> <div id="field_yk8I5y629" role="group" class="field hyva-field lg:w-full md:w-full w-full type-text webforms-fields-629 webforms-fields-street1 " > <div class="control" role="group" ><input id='street1' 473 name='field[629]' 474 type='text' 475 class='input-text hyva-text' 476 style='' 477 value="" 478 placeholder="Address Line 2" 479 data-validate="{'validate-length-max':'40'}" 480 aria-label="Street 1" 481 /> 482 </div></div> <div id="field_yk8I5y632" role="group" class="field hyva-field lg:w-full md:w-full w-full type-text webforms-fields-632 webforms-fields-street2 " > <div class="control" role="group" ><input id='street2' 483 name='field[632]' 484 type='text' 485 class='input-text hyva-text' 486 style='' 487 value="" 488 placeholder="Address Line 3" 489 data-validate="{'validate-length-max':'40'}" 490 aria-label="Street 2" 491 /> 492 </div></div> <div id="field_yk8I5y26" role="group" class="field hyva-field lg:w-full md:w-full w-full type-text webforms-fields-26 webforms-fields-city required" > <div class="control" role="group" ><input id='city' 493 name='field[26]' 494 type='text' 495 class='input-text hyva-text' 496 style='' 497 value="" 498 placeholder="Town / City" 499 data-validate="{'required-entry':true, 'validate-length-max':'30'}" 500 aria-label="Town / City" 501 aria-required="true" /> 502 </div></div> <div id="field_yk8I5y29" role="group" class="field hyva-field lg:w-full md:w-full w-full type-text webforms-fields-29 webforms-fields-country required" > <div class="control" role="group" ><input id='country' 503 name='field[29]' 504 type='text' 505 class='input-text hyva-text' 506 style='' 507 value="" 508 placeholder="Country" 509 data-validate="{'required-entry':true, 'validate-length-max':'30'}" 510 aria-label="Country" 511 aria-required="true" /> 512 </div></div> <div id="field_yk8I5y32" role="group" class="field hyva-field lg:w-full md:w-full w-full type-text webforms-fields-32 webforms-fields-postcode required" > <div class="control" role="group" ><input id='postcode' 513 name='field[32]' 514 type='text' 515 class='input-text hyva-text' 516 style='' 517 value="" 518 placeholder="Postcode" 519 data-validate="{'required-entry':true, 'validate-length-max':'10'}" 520 aria-label="Postcode" 521 aria-required="true" /> 522 </div></div></fieldset> 523 <div class="actions-toolbar w-full"> 524 </div> 525 </div> 526 <div class="step-tab-panel form-step [&_h3]:my-10" id="stepyk8I5y68"> 527 <fieldset id="fieldset_yk8I5y68" class="fieldset-68 hyva-fieldset lg:w-full md:w-full w-full " style="" role="group" aria-labelledby="legend_yk8I5y68"> <h3 class="hyva-fieldset-label" id="legend_yk8I5y68"><span>Choose your preferred survey date</span></h3>
527 <div id="field_yk8I5y1032" role="group" class="field hyva-field lg:w-full md:w-full w-full type-date webforms-fields-1032 webforms-fields-survey-date required" > <div class="control" role="group" ><div class='webforms-datepicker ddd'> 528 <div id='fieldyk8I5y1032_calendar' class='webforms-calendar'> 529 <div class="relative"> 530 <input id='fieldyk8I5y1032' 531 name='field[1032]' 532 type='text' 533 class='input-text _has-datepicker hyva-text pr-10' 534 style='' 535 value='' 536 placeholder="" 537 data-validate="{'required-entry':true}" 538 aria-label="Preferred Survey Date" 539 aria-required="true" /> 540 541 <svg class="text-gray-400 pointer-events-none" style="position: absolute; top:20px; right:20px; width:20px;"viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> 542 <path d="M3.33337 5.83329C3.33337 5.39127 3.50897 4.96734 3.82153 4.65478C4.13409 4.34222 4.55801 4.16663 5.00004 4.16663H15C15.4421 4.16663 15.866 4.34222 16.1786 4.65478C16.4911 4.96734 16.6667 5.39127 16.6667 5.83329V15.8333C16.6667 16.2753 16.4911 16.6992 16.1786 17.0118C15.866 17.3244 15.4421 17.5 15 17.5H5.00004C4.55801 17.5 4.13409 17.3244 3.82153 17.0118C3.50897 16.6992 3.33337 16.2753 3.33337 15.8333V5.83329Z" stroke="black" stroke-width="0.833333" stroke-linecap="round" stroke-linejoin="round"/> 543 <path d="M13.3334 2.5V5.83333" stroke="black" stroke-width="0.833333" stroke-linecap="round" stroke-linejoin="round"/> 544 <path d="M6.66663 2.5V5.83333" stroke="black" stroke-width="0.833333" stroke-linecap="round" stroke-linejoin="round"/> 545 <path d="M3.33337 9.16663H16.6667" stroke="black" stroke-width="0.833333" stroke-linecap="round" stroke-linejoin="round"/> 546 <path d="M5.83337 11.6666H5.84421" stroke="black" stroke-width="0.833333" stroke-linecap="round" stroke-linejoin="round"/> 547 <path d="M8.34167 11.6666H8.34667" stroke="black" stroke-width="0.833333" stroke-linecap="round" stroke-linejoin="round"/> 548 <path d="M10.8417 11.6666H10.8467" stroke="black" stroke-width="0.833333" stroke-linecap="round" stroke-linejoin="round"/> 549 <path d="M13.3458 11.6666H13.3508" stroke="black" stroke-width="0.833333" stroke-linecap="round" stroke-linejoin="round"/> 550 <path d="M10.8458 14.1666H10.8508" stroke="black" stroke-width="0.833333" stroke-linecap="round" stroke-linejoin="round"/> 551 <path d="M5.84167 14.1666H5.84667" stroke="black" stroke-width="0.833333" stroke-linecap="round" stroke-linejoin="round"/> 552 <path d="M8.34167 14.1666H8.34667" stroke="black" stroke-width="0.833333" stroke-linecap="round" stroke-linejoin="round"/> 553 </svg> 554 </div> 555 </div> 556</div> 557 </div></div> <div id="field_yk8I5y38" role="group" class="field hyva-field lg:w-full md:w-full w-full type-subscribe choice webforms-fields-38 webforms-fields-subscribe " aria-describedby="comment_fieldyk8I5y38"> <div class="control" role="group" > <div class="field choice option-0"><input id='fieldyk8I5y380' name='field[38]' type='checkbox' class='hyva-checkbox' style='float:left' value='1' /><label for='fieldyk8I5y380' class=' hyva-checkbox-label' style='' >Subscribe</label></div> <p class="hyva-comment-label" id="comment_fieldyk8I5y38" tabindex="0">I consent to Banham sending me email updates on special offers, products, and services. If you would like further information on how we use your personal data, please check our <a href="/privacy-promise" target="_blank">Privacy Policy</a>.</p></div></div></fieldset> 558 <div class="actions-toolbar w-full"> 559 <div id="captcha-container-yk8I5y"></div> 560 </div> 561 </div> 562 </div> 563 564 <div class="step-footer mt-auto" 565 x-data="{ 566 handleKey(e) { 567 const btnNext = this.$root.querySelector('button[data-direction=\'next\']'); 568 const btnPrev = this.$root.querySelector('button[data-direction=\'prev\']'); 569 const btnFinish = this.$root.querySelector('button[data-direction=\'finish\']'); 570 if (e.key === 'ArrowRight') { 571 if (btnNext && btnNext.offsetParent !== null) btnNext.focus(); 572 else if (btnFinish && btnFinish.offsetParent !== null) btnFinish.focus(); 573 } 574 if (e.key === 'ArrowLeft') { 575 if (btnPrev && btnPrev.offsetParent !== null) btnPrev.focus(); 576 } 577 } 578 }" 579 @keydown.window="handleKey($event)" 580 > 581 582 <div class="please-wait text-center" id="webform_yk8I5y_sending_data" 583 style="display:none;"> 584 <div class="loading-indicator"></div>
585 <span id="webform_yk8I5y_progress_text">Sending...</span> 586 </div> 587 <button type="button" tabindex="0" 588 data-direction="next" 589 @click="handleNextStep" 590 class="btn btn-primary step-btn action primary w-full">Next</button> 591 <button type="submit" tabindex="0" 592 data-direction="finish" 593 id="webform_yk8I5y_submit_button" 594 style="display:none" 595 class="btn btn-primary step-btn action submit primary w-full ">Next</button> 596 <button type="button" tabindex="0" 597 data-direction="prev" 598 style="display:none" 599 class=" leading-5 text-sm m-auto mt-5 border-b border-primary-darker">Back</button> 600 <div class="w-full text-center grecaptcha-legal mt-5"> 601 <p class="my-4 text-sm"> 602 <em>This site is protected by reCAPTCHA.</em> 603</p> </div> 604 605 </div> 606 </div> 607 </div> <!--wizard--> 608 </form> 609 610 611 612 </div> 613 614 <style> .recaptcha-position-inline{ clear: both; box-sizing: border-box; padding: 0 2rem 2rem 0; width: 100%; float: left; }</style> 615 </div> 616 </div> 617 618 619 <div id="webform-slide-out-button-yk8I5y" 620 x-data="{ isScrolled: false }" 621 @scroll.window="isScrolled = window.scrollY > 0" 622 @click=" 623 isOpen = true; 624 window.surveyOpening = window.surveyOpeningSource === 'Opened from CTA' 625 ? 'Opened from CTA' 626 : 'Opened from sidebar'; 627 if (!window.surveyStartedPushed) { 628 var openingSource = window.surveyOpening; 629 var enquiryTopicEl = document.getElementById('enquiry-topic'); 630 window.surveyTypeVal = ''; 631 window.logged_in_status = false; 632 window.dataLayer = window.dataLayer || []; 633 window.dataLayer.push({ 634 event: 'survey_started', 635 site_type: window.site_type, 636 logged_in_status: false, 637 journey_type: 'product', 638 survey_opening: openingSource, 639 }); 640 window.surveyStartedPushed = true; 641 } 642 window.surveyOpeningSource = null; 643 " 644 class="open-slide-form-button fixed z-30 py-4 pl-6 pr-4 right-5 md:right-10 bottom-5 md:bottom-16 lg:bottom-10 group flex items-center bg-primary text-white rounded-full overflow-hidden transition-all duration-300 ease-in-out hover:!w-[350px] justify-end cursor-pointer text-base shadow-md" 645 :class="isScrolled ? 'w-14' : 'w-[350px]'" 646 > 647 <span 648 class="whitespace-nowrap mr-auto transition-all duration-300 group-hover:!opacity-100 font-heading" 649 :class="isScrolled ? 'opacity-0' : 'opacity-100'" 650 > 651 Book a Free Survey </span> 652 <i class="ti ti-calendar-check text-2xl flex items-center justify-center"> 653 </i> 654 </div> 655 656 <div id="webform-slide-out-overlayyk8I5y" 657 x-cloak 658 class="fixed inset-0 bg-black opacity-50 z-[999]" 659 x-show="isOpen" 660 @click="isOpen = false"></div> 661 </div> 662 663 664<input name="form_key" type="hidden" value="9ScZqxAtVshasw9c" /> 665 <noscript> 666 <section 667 class="message global noscript border-b-2 border-blue-500 bg-blue-50 shadow-none m-0 px-0 rounded-none font-normal" 668 aria-labelledby="messageGlobalNoScriptMessage" 669 > 670 <div class="container text-center"> 671 <p id="messageGlobalNoScriptMessage"> 672 <strong>JavaScript seems to be disabled in your browser.</strong>
673 <span> 674 For the best experience on our site, be sure to turn on Javascript in your browser. </span> 675 </p> 676 </div> 677 </section> 678 </noscript> 679 680 681 682 <div class="page-wrapper"><header class="page-header"><a class="action skip sr-only focus:not-sr-only focus:absolute focus:z-40 focus:bg-white 683 contentarea" 684 href="#contentarea"> 685 <span> 686 Skip to Content </span> 687</a><!-- Action Name: /home --> 688<div id="header" role="banner" 689 x-data="{ 690 ...initHeader(), 691 isSticky: false 692 }" 693 694 x-init=" 695 const updateHeaderHeight = () => { 696 const headerEl = document.querySelector('#header'); 697 const bannerEl = document.querySelector('.announcement-banner'); 698 const headerHeight = headerEl ? headerEl.offsetHeight : 0; 699 const bannerHeight = bannerEl ? bannerEl.offsetHeight : 0; 700 document.documentElement.style.setProperty('--header-height', (headerHeight + bannerHeight) + 'px'); 701 }; 702 window.addEventListener('scroll', () => { 703 isSticky = window.scrollY > 50 704 }); 705 window.addEventListener('resize', updateHeaderHeight); 706 updateHeaderHeight(); 707 " 708 :class="isSticky 709 ? 'fixed top-0 left-0 z-100 w-full bg-white' 710 : 'relative z-30 w-full bg-white header-sticky [&_.mini-count]:!bg-white [&_.header-search]:bg-transparent [&_.header-search_.form-input]:bg-transparent [&_.header-search_.form-input]:text-white [&_.header-search_.form-input]:border-white [&_.header-search_.form-input]:placeholder:text-white'" 711 @private-content-loaded.window="getData(event.detail.data)" 712> 713 <div class="relative container flex flex-wrap lg:flex-nowrap items-center justify-between w-full min-h-[60px] px-5 py-5 mx-auto mt-0 md:px-10 lg:min-h-[84px]"> 714 715 <!--Main Navigation--> 716 <div class="flex items-center gap-4 lg:gap-7"> 717 <nav 718 x-data="initMenuMobile_6abf56c059c8f()" 719 @keydown.window.escape="closeMenu()" 720 @private-content-loaded.window="setCustomerState($event.detail.data)" 721 class="z-20 navigation leading-none" 722 aria-label="Site navigation" 723 role="navigation" 724> 725 <button 726 x-ref="mobileMenuTrigger" 727 @click="openMenu()" 728 type="button" 729 aria-label="Open menu" 730 aria-haspopup="menu" 731 :aria-expanded="open" 732 class="h-5 focus:border-primary-lighter focus:outline-none focus:ring-0 leading-none" 733 title="Navigation" 734 > 735 <i class="ti ti-menu text-xl"></i> 736 </button> 737 <div 738 x-ref="mobileMenuNavLinks" 739 class=" 740 fixed top-0 left-0 w-full h-full flex 741 flex-col 742 -translate-x-full 743 z-40 744 transition-transform duration-300 ease-in-out 745 overflow-y-auto overflow-x-hidden max-w-[350px] md:max-w-[384px] lg:max-w-[100%] focus:border-primary-lighter focus:outline-none focus:ring-0" 746 :class="{ 747 'translate-x-0': open, 748 '-translate-x-full': !open, 749 }" 750 :aria-hidden="open ? 'false' : 'true'" 751 role="dialog" 752 aria-modal="true" 753 > 754 <div 755 class="fixed inset-0 z-[-1]" 756 @click="closeMenu()" 757 ></div> 758 <div 759 class="lg:max-w-[480px] h-full bg-white shadow " 760 :class="{ 761 'max-lg:-translate-x-full': mobilePanelActiveId, 762 }" 763 > 764 <div class="w-full h-full pb-[60px] overflow-y-auto overflow-x-hidden" 765 :class="{ 'submenu-open': mobilePanelActiveId !== null && mobilePanelActiveId !== 0 }" 766 > 767 <button 768 @click="closeMenu()" 769 class="flex justify-start w-full self-start py-5 px-5 md:px-10 focus:border-primary-lighter focus:outline-none focus:ring-0" 770 aria-label="Close menu" 771 type="button" 772 > 773 <i class="ti ti-x text-xl"></i> 774 </button> 775 <ul 776 777 class="flex flex-col" 778 aria-label="Site navigation links" 779 > 780 781 <li x-show="open" 782 x-transition:enter="transition ease-out duration-400" 783 x-transition:enter-start="opacity-0 -translate-y-2" 784 x-transition:enter-end="opacity-100 translate-y-0" 785 style="transition-delay: 90ms;" 786 class="md:hidden level-0 px-5 py-[6px] md:px-10"> 787 <a class="relative flex items-center cursor-pointer level-0 focus:border-primary-lighter focus:outline-none focus:ring-0 hover:text-primary-darker" href="/book-a-survey/" title="Book a Survey"><span>Book a Survey</span></a> 788 </li> 789 <li 790 x-show="open" 791 data-child-id="category-node-469-main" 792 x-transition:enter="transition ease-out duration-700" 793 x-transition:enter-start="opacity-0 -translate-y-2" 794 x-transition:enter-end="opacity-100 translate-y-0" 795 style="transition-delay: 0ms;" 796 class="level-0 px-5 py-[6px] md:px-10" 797 :class="{ 'active': mobilePanelActiveId === 'category-node-469' }" 798 > 799 <div class="flex items-center transition-transform duration-150 ease-in-out transform"> 800 <a 801 class="relative flex items-center justify-between cursor-pointer level-0 w-full focus:border-primary-lighter focus:outline-none focus:ring-0 hover:text-primary-darker" 802 title="Locks" 803 @click="openSubcategory('category-node-469')" 804 aria-label="Open Locks subcategories" 805 aria-haspopup="true" 806 :aria-expanded="mobilePanelActiveId === 'category-node-469'" 807 > 808 Locks <i class="ti ti-chevron-right text-base"></i> 809 </a> 810 </div> 811 <div 812 data-child-id="category-node-469" 813 class="absolute top-[0] left-[0] flex flex-col w-full h-full bg-white border-l border-primary pt-16 pb-[60px] overflow-y-auto overflow-x-hidden lg:left-[480px] lg:max-w-[480px] lg:translate-x-0 max-lg:translate-x-full transition-transform duration-150 ease-in-out z-[-1] invisible pointer-events-none" 814 :class="{ 815 '!-translate-x-full invisible pointer-events-none': mobilePanelActiveId !== 'category-node-469', 816 'translate-x-0 visible pointer-events-auto': mobilePanelActiveId === 'category-node-469', 817 818 }" 819 > 820 <button 821 type="button" 822 class="absolute top-5 left-5 flex justify-start w-auto self-start cursor-pointer w-full md:left-10 lg:hidden focu
822s:border-primary-lighter focus:outline-none focus:ring-0" 823 @click="backToMainCategories('category-node-469-main')" 824 aria-label="Back to main categories" 825 > 826 <i class="ti ti-arrow-left text-xl"></i> 827 </button> 828 <ul aria-label="Subcategories"> 829 <li x-show="mobilePanelActiveId === 'category-node-469'" 830 x-transition:enter="transition ease-out duration-700" 831 x-transition:enter-start="opacity-0 -translate-y-2" 832 x-transition:enter-end="opacity-100 translate-y-0" 833 style="transition-delay: 0ms;" class="px-5 pb-2 md:px-10"> 834 <span class="relative text-[10px] text-primary font-grotesk uppercase tracking-[0.8px]"> 835 Locks </span> 836 </li> 837 <li x-show="mobilePanelActiveId === 'category-node-469'" 838 x-transition:enter="transition ease-out duration-700" 839 x-transition:enter-start="opacity-0 -translate-y-2" 840 x-transition:enter-end="opacity-100 translate-y-0" 841 style="transition-delay: 40ms;" class="px-5 pb-2 md:px-10"> 842 <a 843 href="https://www.banham.co.uk/locks/" 844 title="Locks" 845 > 846 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 847 All Locks </span> 848 </a> 849 </li> 850 <li 851 x-show="mobilePanelActiveId === 'category-node-469'" 852 x-transition:enter="transition ease-out duration-700" 853 x-transition:enter-start="opacity-0 -translate-y-2" 854 x-transition:enter-end="opacity-100 translate-y-0" 855 style="transition-delay: 80ms;" 856 class="px-5 pb-2 md:px-10"> 857 <a 858 href="https://www.banham.co.uk/locks/door-locks/" 859 title="Door Locks" 860 > 861 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 862 Door Locks </span> 863 </a> 864 </li> 865 <li 866 x-show="mobilePanelActiveId === 'category-node-469'" 867 x-transition:enter="transition ease-out duration-700" 868 x-transition:enter-start="opacity-0 -translate-y-2" 869 x-transition:enter-end="opacity-100 translate-y-0" 870 style="transition-delay: 120ms;" 871 class="px-5 pb-2 md:px-10"> 872 <a 873 href="https://www.banham.co.uk/locks/locks-additional-security/" 874 title="Window Locks & Door Bolts" 875 >
876 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 877 Window Locks & Door Bolts </span> 878 </a> 879 </li> 880 <li 881 x-show="mobilePanelActiveId === 'category-node-469'" 882 x-transition:enter="transition ease-out duration-700" 883 x-transition:enter-start="opacity-0 -translate-y-2" 884 x-transition:enter-end="opacity-100 translate-y-0" 885 style="transition-delay: 160ms;" 886 class="px-5 pb-2 md:px-10"> 887 <a 888 href="https://www.banham.co.uk/locks/keys-and-accessories/" 889 title="Keys, Fobs & Accessories" 890 > 891 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 892 Keys, Fobs & Accessories </span> 893 </a> 894 </li> 895 </ul> 896 <ul class="submenu-custom-link"> 897 <li x-show="mobilePanelActiveId === 'category-node-469'" 898 x-transition:enter="transition ease-out duration-700" 899 x-transition:enter-start="opacity-0 -translate-y-2" 900 x-transition:enter-end="opacity-100 translate-y-0" 901 style="transition-delay: 200ms;" 902 class="px-5 pt-10 pb-2 md:px-10"> 903 <span class="relative text-[10px] text-primary font-grotesk uppercase tracking-[0.8px]">SERVICES</span> 904 </li> 905 <li x-show="mobilePanelActiveId === 'category-node-469'" 906 x-transition:enter="transition ease-out duration-700" 907 x-transition:enter-start="opacity-0 -translate-y-2" 908 x-transition:enter-end="opacity-100 translate-y-0" 909 style="transition-delay: 240ms;" 910 class="px-5 pb-2 md:px-10"> 911 <a href="/locks/key-cutting-key-registration/" title="Keycutting and Key Registration"> 912 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Keycutting and Key Registration</span> 913 </a> 914 </li> 915 <li x-show="mobilePanelActiveId === 'category-node-469'" 916 x-transition:enter="transition ease-out duration-700" 917 x-transition:enter-start="opacity-0 -translate-y-2" 918 x-transition:enter-end="opacity-100 translate-y-0" 919 style="transition-delay: 280ms;" 920 class="px-5 pb-2 md:px-10"> 921 <a href="/locksmith-service/" title="Emergency Locksmiths">
922 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Emergency Locksmiths</span> 923 </a> 924 </li> 925 </ul> 926 <div x-show="mobilePanelActiveId === 'category-node-469'" 927 x-transition:enter="transition ease-out duration-700" 928 x-transition:enter-start="opacity-0 -translate-y-2" 929 x-transition:enter-end="opacity-100 translate-y-0" 930 style="transition-delay: 320ms;" 931 class="menu-cutom-block px-5 mt-[60px] md:px-10"> 932 <img src="https://a.storyblok.com/f/286600090739628/400x400/b01178a975/banham-locks-menu.png" alt="Banham door lock with a reflective surface, mounted on a dark blue door next to a white frame." class="w-full"/> 933 <div class="inner-detials flex flex-col gap-4 bg-primary px-7 pt-7 pb-[60px]"> 934 <h4 class="text-white mb-0">Trusted Since 1926</h4> 935 <p class="text-white mb-0"> Elegant, strong and secure: our locks come in 936a range of quality finishes.</p> 937 <a href="/locks" class="underline-link"><span class="text-white">Learn More</span></a> 938 </div> 939 </div> 940 </div> 941 </li> 942 <li 943 x-show="open" 944 data-child-id="category-node-472-main" 945 x-transition:enter="transition ease-out duration-700" 946 x-transition:enter-start="opacity-0 -translate-y-2" 947 x-transition:enter-end="opacity-100 translate-y-0" 948 style="transition-delay: 40ms;" 949 class="level-0 px-5 py-[6px] md:px-10" 950 :class="{ 'active': mobilePanelActiveId === 'category-node-472' }" 951 > 952 <div class="flex items-center transition-transform duration-150 ease-in-out transform"> 953 <a 954 class="relative flex items-center justify-between cursor-pointer level-0 w-full focus:border-primary-lighter focus:outline-none focus:ring-0 hover:text-primary-darker" 955 title="Doors" 956 @click="openSubcategory('category-node-472')" 957 aria-label="Open Doors subcategories" 958 aria-haspopup="true" 959 :aria-expanded="mobilePanelActiveId === 'category-node-472'" 960 > 961 Doors <i class="ti ti-chevron-right text-base"></i> 962 </a> 963 </div> 964 <div 965 data-child-id="category-node-472" 966 class="absolute top-[0] left-[0] flex flex-col w-full h-full bg-white border-l border-primary pt-16 pb-[60px] overflow-y-auto overflow-x-hidden lg:left-[480px] lg:max-w-[480px] lg:translate-x-0 max-lg:translate-x-full transition-transform duration-150 ease-in-out z-[-1] invisible pointer-events-none" 967 :class="{ 968 '!-translate-x-full invisible pointer-events-none': mobilePanelActiveId !== 'category-node-472', 969 'translate-x-0 visible pointer-events-auto': mobilePanelActiveId === 'category-node-472', 970 971 }" 972 > 973 <button 974 type="button" 975 class="absolute top-5 left-5 flex justify-start w-auto self-start cursor-pointer w-full md:left-10 lg:hidden focu
975s:border-primary-lighter focus:outline-none focus:ring-0" 976 @click="backToMainCategories('category-node-472-main')" 977 aria-label="Back to main categories" 978 > 979 <i class="ti ti-arrow-left text-xl"></i> 980 </button> 981 <ul aria-label="Subcategories"> 982 <li x-show="mobilePanelActiveId === 'category-node-472'" 983 x-transition:enter="transition ease-out duration-700" 984 x-transition:enter-start="opacity-0 -translate-y-2" 985 x-transition:enter-end="opacity-100 translate-y-0" 986 style="transition-delay: 0ms;" class="px-5 pb-2 md:px-10"> 987 <span class="relative text-[10px] text-primary font-grotesk uppercase tracking-[0.8px]"> 988 Doors </span> 989 </li> 990 <li x-show="mobilePanelActiveId === 'category-node-472'" 991 x-transition:enter="transition ease-out duration-700" 992 x-transition:enter-start="opacity-0 -translate-y-2" 993 x-transition:enter-end="opacity-100 translate-y-0" 994 style="transition-delay: 40ms;" class="px-5 pb-2 md:px-10"> 995 <a 996 href="https://www.banham.co.uk/doors/" 997 title="Doors" 998 > 999 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 1000 All Doors </span> 1001 </a> 1002 </li> 1003 <li 1004 x-show="mobilePanelActiveId === 'category-node-472'" 1005 x-transition:enter="transition ease-out duration-700" 1006 x-transition:enter-start="opacity-0 -translate-y-2" 1007 x-transition:enter-end="opacity-100 translate-y-0" 1008 style="transition-delay: 80ms;" 1009 class="px-5 pb-2 md:px-10"> 1010 <a 1011 href="https://www.banham.co.uk/doors/security-doors/" 1012 title="Front Doors" 1013 > 1014 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 1015 Front Doors </span> 1016 </a> 1017 </li> 1018 <li 1019 x-show="mobilePanelActiveId === 'category-node-472'" 1020 x-transition:enter="transition ease-out duration-700" 1021 x-transition:enter-start="opacity-0 -translate-y-2" 1022 x-transition:enter-end="opacity-100 translate-y-0" 1023 style="transition-delay: 120ms;" 1024 class="px-5 pb-2 md:px-10"> 1025 <a 1026 href="https://www.banham.co.uk/doors/fire-doors/" 1027 title="Fire Doors" 1028 >
1029 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 1030 Fire Doors </span> 1031 </a> 1032 </li> 1033 <li 1034 x-show="mobilePanelActiveId === 'category-node-472'" 1035 x-transition:enter="transition ease-out duration-700" 1036 x-transition:enter-start="opacity-0 -translate-y-2" 1037 x-transition:enter-end="opacity-100 translate-y-0" 1038 style="transition-delay: 160ms;" 1039 class="px-5 pb-2 md:px-10"> 1040 <a 1041 href="https://www.banham.co.uk/doors/door-furniture/" 1042 title="Door Furniture" 1043 > 1044 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 1045 Door Furniture </span> 1046 </a> 1047 </li> 1048 </ul> 1049 <ul class="submenu-custom-link"> 1050 <li x-show="mobilePanelActiveId === 'category-node-472'" 1051 x-transition:enter="transition ease-out duration-700" 1052 x-transition:enter-start="opacity-0 -translate-y-2" 1053 x-transition:enter-end="opacity-100 translate-y-0" 1054 style="transition-delay: 200ms;" 1055 class="px-5 pt-10 pb-2 md:px-10"> 1056 <span class="relative text-[10px] text-primary font-grotesk uppercase tracking-[0.8px]">Services</span> 1057 </li> 1058 <li x-show="mobilePanelActiveId === 'category-node-472'" 1059 x-transition:enter="transition ease-out duration-700" 1060 x-transition:enter-start="opacity-0 -translate-y-2" 1061 x-transition:enter-end="opacity-100 translate-y-0" 1062 style="transition-delay: 240ms;" 1063 class="px-5 pb-2 md:px-10"> 1064 <a href="/locks/key-cutting-key-registration/" title="Keycutting & Key Registration"> 1065 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Keycutting & Key Registration</span> 1066 </a> 1067 </li> 1068 <li x-show="mobilePanelActiveId === 'category-node-472'" 1069 x-transition:enter="transition ease-out duration-700" 1070 x-transition:enter-start="opacity-0 -translate-y-2" 1071 x-transition:enter-end="opacity-100 translate-y-0" 1072 style="transition-delay: 280ms;" 1073 class="px-5 pb-2 md:px-10"> 1074 <a href="/locksmith-service/" title="Emergency Locksmiths">
1075 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Emergency Locksmiths</span> 1076 </a> 1077 </li> 1078 </ul> 1079 <div x-show="mobilePanelActiveId === 'category-node-472'" 1080 x-transition:enter="transition ease-out duration-700" 1081 x-transition:enter-start="opacity-0 -translate-y-2" 1082 x-transition:enter-end="opacity-100 translate-y-0" 1083 style="transition-delay: 320ms;" 1084 class="menu-cutom-block px-5 mt-[60px] md:px-10"> 1085 <img src="https://a.storyblok.com/f/286600090739628/400x400/4dcd04b053/banham-doors-menu.png" alt="Bespoke Banham Blue door with stained glass panels featuring geometric patterns. Gold hardware and mail slot. Greenery visible on the side." class="w-full"/> 1086 <div class="inner-detials flex flex-col gap-4 bg-primary px-7 pt-7 pb-[60px]"> 1087 <h4 class="text-white mb-0">Doors</h4> 1088 <p class="text-white mb-0"> Security doors with bespoke design.</p> 1089 <a href="/doors" class="underline-link"><span class="text-white">Learn More</span></a> 1090 </div> 1091 </div> 1092 </div> 1093 </li> 1094 <li 1095 x-show="open" 1096 data-child-id="category-node-15-main" 1097 x-transition:enter="transition ease-out duration-700" 1098 x-transition:enter-start="opacity-0 -translate-y-2" 1099 x-transition:enter-end="opacity-100 translate-y-0" 1100 style="transition-delay: 80ms;" 1101 class="level-0 px-5 py-[6px] md:px-10" 1102 :class="{ 'active': mobilePanelActiveId === 'category-node-15' }" 1103 > 1104 <div class="flex items-center transition-transform duration-150 ease-in-out transform"> 1105 <a 1106 class="relative flex items-center justify-between cursor-pointer level-0 w-full focus:border-primary-lighter focus:outline-none focus:ring-0 hover:text-primary-darker" 1107 title="Burglar Alarms" 1108 @click="openSubcategory('category-node-15')" 1109 aria-label="Open Burglar Alarms subcategories" 1110 aria-haspopup="true" 1111 :aria-expanded="mobilePanelActiveId === 'category-node-15'" 1112 > 1113 Burglar Alarms <i class="ti ti-chevron-right text-base"></i> 1114 </a> 1115 </div> 1116 <div 1117 data-child-id="category-node-15" 1118 class="absolute top-[0] left-[0] flex flex-col w-full h-full bg-white border-l border-primary pt-16 pb-[60px] overflow-y-auto overflow-x-hidden lg:left-[480px] lg:max-w-[480px] lg:translate-x-0 max-lg:translate-x-full transition-transform duration-150 ease-in-out z-[-1] invisible pointer-events-none" 1119 :class="{ 1120 '!-translate-x-full invisible pointer-events-none': mobilePanelActiveId !== 'category-node-15', 1121 'translate-x-0 visible pointer-events-auto': mobilePanelActiveId === 'category-node-15', 1122 1123 }" 1124 > 1125 <button 1126 type="button" 1127 class="absolute top-5 left-5 flex justify-start w-auto self-start cursor-pointer w-full md:left-10 lg:hidden focu
1127s:border-primary-lighter focus:outline-none focus:ring-0" 1128 @click="backToMainCategories('category-node-15-main')" 1129 aria-label="Back to main categories" 1130 > 1131 <i class="ti ti-arrow-left text-xl"></i> 1132 </button> 1133 <ul aria-label="Subcategories"> 1134 <li x-show="mobilePanelActiveId === 'category-node-15'" 1135 x-transition:enter="transition ease-out duration-700" 1136 x-transition:enter-start="opacity-0 -translate-y-2" 1137 x-transition:enter-end="opacity-100 translate-y-0" 1138 style="transition-delay: 0ms;" class="px-5 pb-2 md:px-10"> 1139 <span class="relative text-[10px] text-primary font-grotesk uppercase tracking-[0.8px]"> 1140 Burglar Alarms </span> 1141 </li> 1142 <li x-show="mobilePanelActiveId === 'category-node-15'" 1143 x-transition:enter="transition ease-out duration-700" 1144 x-transition:enter-start="opacity-0 -translate-y-2" 1145 x-transition:enter-end="opacity-100 translate-y-0" 1146 style="transition-delay: 40ms;" class="px-5 pb-2 md:px-10"> 1147 <a 1148 href="https://www.banham.co.uk/burglar-alarms/" 1149 title="Burglar Alarms" 1150 > 1151 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 1152 All Burglar Alarms </span> 1153 </a> 1154 </li> 1155 <li 1156 x-show="mobilePanelActiveId === 'category-node-15'" 1157 x-transition:enter="transition ease-out duration-700" 1158 x-transition:enter-start="opacity-0 -translate-y-2" 1159 x-transition:enter-end="opacity-100 translate-y-0" 1160 style="transition-delay: 80ms;" 1161 class="px-5 pb-2 md:px-10"> 1162 <a 1163 href="https://www.banham.co.uk/burglar-alarms/alarm-consultation/" 1164 title="Alarm Consultation" 1165 > 1166 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 1167 Alarm Consultation </span> 1168 </a> 1169 </li> 1170 <li 1171 x-show="mobilePanelActiveId === 'category-node-15'" 1172 x-transition:enter="transition ease-out duration-700" 1173 x-transition:enter-start="opacity-0 -translate-y-2" 1174 x-transition:enter-end="opacity-100 translate-y-0" 1175 style="transition-delay: 120ms;" 1176 class="px-5 pb-2 md:px-10"> 1177 <a 1178 href="https://www.banham.co.uk/burglar-alarms/alarm-installation/" 1179 title="Alarm Installation" 1180 >
1181 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 1182 Alarm Installation </span> 1183 </a> 1184 </li> 1185 <li 1186 x-show="mobilePanelActiveId === 'category-node-15'" 1187 x-transition:enter="transition ease-out duration-700" 1188 x-transition:enter-start="opacity-0 -translate-y-2" 1189 x-transition:enter-end="opacity-100 translate-y-0" 1190 style="transition-delay: 160ms;" 1191 class="px-5 pb-2 md:px-10"> 1192 <a 1193 href="https://www.banham.co.uk/burglar-alarms/car-shield/" 1194 title="Car Shield" 1195 > 1196 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 1197 Car Shield </span> 1198 </a> 1199 </li> 1200 <li 1201 x-show="mobilePanelActiveId === 'category-node-15'" 1202 x-transition:enter="transition ease-out duration-700" 1203 x-transition:enter-start="opacity-0 -translate-y-2" 1204 x-transition:enter-end="opacity-100 translate-y-0" 1205 style="transition-delay: 200ms;" 1206 class="px-5 pb-2 md:px-10"> 1207 <a 1208 href="https://www.banham.co.uk/burglar-alarms/alarm-system-upgrade/" 1209 title="Upgrade Your Alarm System" 1210 > 1211 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 1212 Upgrade Your Alarm System </span> 1213 </a> 1214 </li> 1215 </ul> 1216 <ul class="submenu-custom-link"> 1217 <li x-show="mobilePanelActiveId === 'category-node-15'" 1218 x-transition:enter="transition ease-out duration-700" 1219 x-transition:enter-start="opacity-0 -translate-y-2" 1220 x-transition:enter-end="opacity-100 translate-y-0" 1221 style="transition-delay: 240ms;" 1222 class="px-5 pt-10 pb-2 md:px-10"> 1223 <span class="relative text-[10px] text-primary font-grotesk uppercase tracking-[0.8px]">Services</span> 1224 </li> 1225 <li x-show="mobilePanelActiveId === 'category-node-15'" 1226 x-transition:enter="transition ease-out duration-700" 1227 x-transition:enter-start="opacity-0 -translate-y-2" 1228 x-transition:enter-end="opacity-100 translate-y-0" 1229 style="transition-delay: 280ms;" 1230 class="px-5 pb-2 md:px-10"> 1231 <a href="/burglar-alarms/alarm-monitoring/" title="Alarm Monitoring">
1232 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Alarm Monitoring</span> 1233 </a> 1234 </li> 1235 <li x-show="mobilePanelActiveId === 'category-node-15'" 1236 x-transition:enter="transition ease-out duration-700" 1237 x-transition:enter-start="opacity-0 -translate-y-2" 1238 x-transition:enter-end="opacity-100 translate-y-0" 1239 style="transition-delay: 320ms;" 1240 class="px-5 pb-2 md:px-10"> 1241 <a href="/burglar-alarms/banham-smart-security-app/" title="Smart Security App"> 1242 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Smart Security App</span> 1243 </a> 1244 </li> 1245 <li x-show="mobilePanelActiveId === 'category-node-15'" 1246 x-transition:enter="transition ease-out duration-700" 1247 x-transition:enter-start="opacity-0 -translate-y-2" 1248 x-transition:enter-end="opacity-100 translate-y-0" 1249 style="transition-delay: 360ms;" 1250 class="px-5 pb-2 md:px-10"> 1251 <a href="/keyholding/" title="Keyholding and Alarm Response"> 1252 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Keyholding and Alarm Response</span> 1253 </a> 1254 </li> 1255 <li x-show="mobilePanelActiveId === 'category-node-15'" 1256 x-transition:enter="transition ease-out duration-700" 1257 x-transition:enter-start="opacity-0 -translate-y-2" 1258 x-transition:enter-end="opacity-100 translate-y-0" 1259 style="transition-delay: 400ms;" 1260 class="px-5 pb-2 md:px-10"> 1261 <a href="/commercial" title="Banham Commercial"> 1262 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Banham Commercial</span> 1263 </a> 1264 </li> 1265 <li x-show="mobilePanelActiveId === 'category-node-15'" 1266 x-transition:enter="transition ease-out duration-700" 1267 x-transition:enter-start="opacity-0 -translate-y-2" 1268 x-transition:enter-end="opacity-100 translate-y-0" 1269 style="transition-delay: 440ms;" 1270 class="px-5 pb-2 md:px-10"> 1271 <a href="/pstn/" title="Digital Phone Switchover">
1272 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Digital Phone Switchover</span> 1273 </a> 1274 </li> 1275 </ul> 1276 <div x-show="mobilePanelActiveId === 'category-node-15'" 1277 x-transition:enter="transition ease-out duration-700" 1278 x-transition:enter-start="opacity-0 -translate-y-2" 1279 x-transition:enter-end="opacity-100 translate-y-0" 1280 style="transition-delay: 480ms;" 1281 class="menu-cutom-block px-5 mt-[60px] md:px-10"> 1282 <img src="https://a.storyblok.com/f/286600090739628/359x261/e65714dc7f/alarms-q3-megamenu.jpg" alt="" class="w-full"/> 1283 <div class="inner-detials flex flex-col gap-4 bg-primary px-7 pt-7 pb-[60px]"> 1284 <h4 class="text-white mb-0">Alarms</h4> 1285 <p class="text-white mb-0"> Bespoke-designed alarm systems installed and maintained by experts.</p> 1286 <a href="/burglar-alarms" class="underline-link"><span class="text-white">Learn More</span></a> 1287 </div> 1288 </div> 1289 </div> 1290 </li> 1291 <li 1292 x-show="open" 1293 data-child-id="category-node-4-main" 1294 x-transition:enter="transition ease-out duration-700" 1295 x-transition:enter-start="opacity-0 -translate-y-2" 1296 x-transition:enter-end="opacity-100 translate-y-0" 1297 style="transition-delay: 120ms;" 1298 class="level-0 px-5 py-[6px] md:px-10" 1299 :class="{ 'active': mobilePanelActiveId === 'category-node-4' }" 1300 > 1301 <div class="flex items-center transition-transform duration-150 ease-in-out transform"> 1302 <a 1303 class="relative flex items-center justify-between cursor-pointer level-0 w-full focus:border-primary-lighter focus:outline-none focus:ring-0 hover:text-primary-darker" 1304 title="CCTV" 1305 @click="openSubcategory('category-node-4')" 1306 aria-label="Open CCTV subcategories" 1307 aria-haspopup="true" 1308 :aria-expanded="mobilePanelActiveId === 'category-node-4'" 1309 > 1310 CCTV <i class="ti ti-chevron-right text-base"></i> 1311 </a> 1312 </div> 1313 <div 1314 data-child-id="category-node-4" 1315 class="absolute top-[0] left-[0] flex flex-col w-full h-full bg-white border-l border-primary pt-16 pb-[60px] overflow-y-auto overflow-x-hidden lg:left-[480px] lg:max-w-[480px] lg:translate-x-0 max-lg:translate-x-full transition-transform duration-150 ease-in-out z-[-1] invisible pointer-events-none" 1316 :class="{ 1317 '!-translate-x-full invisible pointer-events-none': mobilePanelActiveId !== 'category-node-4', 1318 'translate-x-0 visible pointer-events-auto': mobilePanelActiveId === 'category-node-4', 1319 1320 }" 1321 > 1322 <button 1323 type="button" 1324 class="absolute top-5 left-5 flex justify-start w-auto self-start cursor-pointer w-full md:left-10 lg:hidden focu
1324s:border-primary-lighter focus:outline-none focus:ring-0" 1325 @click="backToMainCategories('category-node-4-main')" 1326 aria-label="Back to main categories" 1327 > 1328 <i class="ti ti-arrow-left text-xl"></i> 1329 </button> 1330 <ul aria-label="Subcategories"> 1331 <li x-show="mobilePanelActiveId === 'category-node-4'" 1332 x-transition:enter="transition ease-out duration-700" 1333 x-transition:enter-start="opacity-0 -translate-y-2" 1334 x-transition:enter-end="opacity-100 translate-y-0" 1335 style="transition-delay: 0ms;" class="px-5 pb-2 md:px-10"> 1336 <span class="relative text-[10px] text-primary font-grotesk uppercase tracking-[0.8px]"> 1337 CCTV </span> 1338 </li> 1339 <li x-show="mobilePanelActiveId === 'category-node-4'" 1340 x-transition:enter="transition ease-out duration-700" 1341 x-transition:enter-start="opacity-0 -translate-y-2" 1342 x-transition:enter-end="opacity-100 translate-y-0" 1343 style="transition-delay: 40ms;" class="px-5 pb-2 md:px-10"> 1344 <a 1345 href="https://www.banham.co.uk/cctv-entry/" 1346 title="CCTV" 1347 > 1348 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 1349 All CCTV </span> 1350 </a> 1351 </li> 1352 <li 1353 x-show="mobilePanelActiveId === 'category-node-4'" 1354 x-transition:enter="transition ease-out duration-700" 1355 x-transition:enter-start="opacity-0 -translate-y-2" 1356 x-transition:enter-end="opacity-100 translate-y-0" 1357 style="transition-delay: 80ms;" 1358 class="px-5 pb-2 md:px-10"> 1359 <a 1360 href="https://www.banham.co.uk/cctv-entry/cctv/" 1361 title="CCTV Systems" 1362 > 1363 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 1364 CCTV Systems </span> 1365 </a> 1366 </li> 1367 <li 1368 x-show="mobilePanelActiveId === 'category-node-4'" 1369 x-transition:enter="transition ease-out duration-700" 1370 x-transition:enter-start="opacity-0 -translate-y-2" 1371 x-transition:enter-end="opacity-100 translate-y-0" 1372 style="transition-delay: 120ms;" 1373 class="px-5 pb-2 md:px-10"> 1374 <a 1375 href="https://www.banham.co.uk/cctv-entry/entry-access-control-systems/" 1376 title="Entry & Access Control Systems" 1377 >
1378 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 1379 Entry & Access Control Systems </span> 1380 </a> 1381 </li> 1382 </ul> 1383 <ul class="submenu-custom-link"> 1384 <li x-show="mobilePanelActiveId === 'category-node-4'" 1385 x-transition:enter="transition ease-out duration-700" 1386 x-transition:enter-start="opacity-0 -translate-y-2" 1387 x-transition:enter-end="opacity-100 translate-y-0" 1388 style="transition-delay: 160ms;" 1389 class="px-5 pt-10 pb-2 md:px-10"> 1390 <span class="relative text-[10px] text-primary font-grotesk uppercase tracking-[0.8px]">Services</span> 1391 </li> 1392 <li x-show="mobilePanelActiveId === 'category-node-4'" 1393 x-transition:enter="transition ease-out duration-700" 1394 x-transition:enter-start="opacity-0 -translate-y-2" 1395 x-transition:enter-end="opacity-100 translate-y-0" 1396 style="transition-delay: 200ms;" 1397 class="px-5 pb-2 md:px-10"> 1398 <a href="/cctv-entry/cctv/cctv-monitoring-services/" title="CCTV Monitoring"> 1399 <span class="relative text-sm text-primary-darker hover:text-primary-darker">CCTV Monitoring</span> 1400 </a> 1401 </li> 1402 <li x-show="mobilePanelActiveId === 'category-node-4'" 1403 x-transition:enter="transition ease-out duration-700" 1404 x-transition:enter-start="opacity-0 -translate-y-2" 1405 x-transition:enter-end="opacity-100 translate-y-0" 1406 style="transition-delay: 240ms;" 1407 class="px-5 pb-2 md:px-10"> 1408 <a href="/keyholding/" title="Keyholding and Alarm Response"> 1409 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Keyholding and Alarm Response</span> 1410 </a> 1411 </li> 1412 <li x-show="mobilePanelActiveId === 'category-node-4'" 1413 x-transition:enter="transition ease-out duration-700" 1414 x-transition:enter-start="opacity-0 -translate-y-2" 1415 x-transition:enter-end="opacity-100 translate-y-0" 1416 style="transition-delay: 280ms;" 1417 class="px-5 pb-2 md:px-10"> 1418 <a href="/cctv-entry/cctv/routine-cctv-maintenance-24-hour-service/" title="Routine CCTV Maintenance and 24-Hour Service">
1419 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Routine CCTV Maintenance and 24-Hour Service</span> 1420 </a> 1421 </li> 1422 <li x-show="mobilePanelActiveId === 'category-node-4'" 1423 x-transition:enter="transition ease-out duration-700" 1424 x-transition:enter-start="opacity-0 -translate-y-2" 1425 x-transition:enter-end="opacity-100 translate-y-0" 1426 style="transition-delay: 320ms;" 1427 class="px-5 pb-2 md:px-10"> 1428 <a href="/commercial" title="Banham Commercial"> 1429 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Banham Commercial</span> 1430 </a> 1431 </li> 1432 <li x-show="mobilePanelActiveId === 'category-node-4'" 1433 x-transition:enter="transition ease-out duration-700" 1434 x-transition:enter-start="opacity-0 -translate-y-2" 1435 x-transition:enter-end="opacity-100 translate-y-0" 1436 style="transition-delay: 360ms;" 1437 class="px-5 pb-2 md:px-10"> 1438 <a href="/pstn/" title="Digital Phone Switchover"> 1439 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Digital Phone Switchover</span> 1440 </a> 1441 </li> 1442 <li x-show="mobilePanelActiveId === 'category-node-4'" 1443 x-transition:enter="transition ease-out duration-700" 1444 x-transition:enter-start="opacity-0 -translate-y-2" 1445 x-transition:enter-end="opacity-100 translate-y-0" 1446 style="transition-delay: 400ms;" 1447 class="px-5 pb-2 md:px-10"> 1448 <a href="/direct-debits/" title="Direct Debit Options"> 1449 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Direct Debit Options</span> 1450 </a> 1451 </li> 1452 </ul> 1453 <div x-show="mobilePanelActiveId === 'category-node-4'" 1454 x-transition:enter="transition ease-out duration-700" 1455 x-transition:enter-start="opacity-0 -translate-y-2" 1456 x-transition:enter-end="opacity-100 translate-y-0" 1457 style="transition-delay: 440ms;" 1458 class="menu-cutom-block px-5 mt-[60px] md:px-10"> 1459 <img src="https://a.storyblok.com/f/286600090739628/359x261/5e9a2dae8f/meganav_cctv_entry2.jpg" alt="" class="w-full"/> 1460 <div class="inner-detials flex flex-col gap-4 bg-primary px-7 pt-7 pb-[60px]"> 1461 <h4 class="text-white mb-0">CCTV & Entry</h4> 1462 <p class="text-white mb-0"> Intelligent CCTV and entry systems tailored to your property.</p> 1463 <a href="/cctv-entry" class="underline-link"><span class="text-white">Learn More</span></a> 1464 </div> 1465 </div> 1466 </div> 1467 </li> 1468 <li 1469 x-show="open" 1470 data-child-id="category-node-30-main" 1471 x-transition:enter="transition ease-out duration-700" 1472 x-transition:enter-start="opacity-0 -translate-y-2" 1473 x-transition:enter-end="opacity-100 translate-y-0" 1474 style="transition-delay: 160ms;" 1475 class="level-0 px-5 py-[6px] md:px-10" 1476 :class="{ 'active': mobilePanelActiveId === 'category-node-30' }" 1477 > 1478 <div class="flex items-center transition-transform duration-150 ease-in-out transform"> 1479 <a 1480 class="relative flex items-center justify-between cursor-pointer level-0 w-full focus:border-primary-lighter focus:outline-none focus:ring-0 hover:text-primary-darker" 1481 title="Fire Alarms" 1482 @click="openSubcategory('category-node-30')" 1483 aria-label="Open Fire Alarms subcategories" 1484 aria-haspopup="true" 1485 :aria-expanded="mobilePanelActiveId === 'category-node-30'" 1486 > 1487 Fire Alarms <i class="ti ti-chevron-right text-base"></i> 1488 </a> 1489 </div> 1490 <div 1491 data-child-id="category-node-30" 1492 class="absolute top-[0] left-[0] flex flex-col w-full h-full bg-white border-l border-primary pt-16 pb-[60px] overflow-y-auto overflow-x-hidden lg:left-[480px] lg:max-w-[480px] lg:translate-x-0 max-lg:translate-x-full transition-transform duration-150 ease-in-out z-[-1] invisible pointer-events-none" 1493 :class="{ 1494 '!-translate-x-full invisible pointer-events-none': mobilePanelActiveId !== 'category-node-30', 1495 'translate-x-0 visible pointer-events-auto': mobilePanelActiveId === 'category-node-30', 1496 1497 }" 1498 > 1499 <button 1500 type="button" 1501 class="absolute top-5 left-5 flex justify-start w-auto self-start cursor-pointer w-full md:left-10 lg:hidden focu
1501s:border-primary-lighter focus:outline-none focus:ring-0" 1502 @click="backToMainCategories('category-node-30-main')" 1503 aria-label="Back to main categories" 1504 > 1505 <i class="ti ti-arrow-left text-xl"></i> 1506 </button> 1507 <ul aria-label="Category content"> 1508 <li x-show="mobilePanelActiveId === 'category-node-30'" 1509 x-transition:enter="transition ease-out duration-700" 1510 x-transition:enter-start="opacity-0 -translate-y-2" 1511 x-transition:enter-end="opacity-100 translate-y-0" 1512 style="transition-delay: 0ms;" class="px-5 pb-2 md:px-10"> 1513 <span class="relative text-[10px] text-primary font-grotesk uppercase tracking-[0.8px]"> 1514 Fire Alarms </span> 1515 </li> 1516 <li x-show="mobilePanelActiveId === 'category-node-30'" 1517 x-transition:enter="transition ease-out duration-700" 1518 x-transition:enter-start="opacity-0 -translate-y-2" 1519 x-transition:enter-end="opacity-100 translate-y-0" 1520 style="transition-delay: 40ms;" class="px-5 pb-2 md:px-10"> 1521 <a 1522 href="https://www.banham.co.uk/fire-alarms/" 1523 title="Fire Alarms" 1524 > 1525 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 1526 All Fire Alarms </span> 1527 </a> 1528 </li> 1529 </ul> 1530 <ul class="submenu-custom-link"> 1531 <li x-show="mobilePanelActiveId === 'category-node-30'" 1532 x-transition:enter="transition ease-out duration-700" 1533 x-transition:enter-start="opacity-0 -translate-y-2" 1534 x-transition:enter-end="opacity-100 translate-y-0" 1535 style="transition-delay: 80ms;" 1536 class="px-5 pt-10 pb-2 md:px-10"> 1537 <span class="relative text-[10px] text-primary font-grotesk uppercase tracking-[0.8px]">Related products and services</span> 1538 </li> 1539 <li x-show="mobilePanelActiveId === 'category-node-30'" 1540 x-transition:enter="transition ease-out duration-700" 1541 x-transition:enter-start="opacity-0 -translate-y-2" 1542 x-transition:enter-end="opacity-100 translate-y-0" 1543 style="transition-delay: 120ms;" 1544 class="px-5 pb-2 md:px-10"> 1545 <a href="/fire-extinguishers/" title="Fire Extinguishers">
1546 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Fire Extinguishers</span> 1547 </a> 1548 </li> 1549 <li x-show="mobilePanelActiveId === 'category-node-30'" 1550 x-transition:enter="transition ease-out duration-700" 1551 x-transition:enter-start="opacity-0 -translate-y-2" 1552 x-transition:enter-end="opacity-100 translate-y-0" 1553 style="transition-delay: 160ms;" 1554 class="px-5 pb-2 md:px-10"> 1555 <a href="/doors/fire-doors/" title="Fire Doors"> 1556 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Fire Doors</span> 1557 </a> 1558 </li> 1559 <li x-show="mobilePanelActiveId === 'category-node-30'" 1560 x-transition:enter="transition ease-out duration-700" 1561 x-transition:enter-start="opacity-0 -translate-y-2" 1562 x-transition:enter-end="opacity-100 translate-y-0" 1563 style="transition-delay: 200ms;" 1564 class="px-5 pb-2 md:px-10"> 1565 <a href="/burglar-alarms/alarm-monitoring/" title="Alarm Monitoring"> 1566 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Alarm Monitoring</span> 1567 </a> 1568 </li> 1569 <li x-show="mobilePanelActiveId === 'category-node-30'" 1570 x-transition:enter="transition ease-out duration-700" 1571 x-transition:enter-start="opacity-0 -translate-y-2" 1572 x-transition:enter-end="opacity-100 translate-y-0" 1573 style="transition-delay: 240ms;" 1574 class="px-5 pb-2 md:px-10"> 1575 <a href="/keyholding/" title="Keyholding and Alarm Response"> 1576 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Keyholding and Alarm Response</span> 1577 </a> 1578 </li> 1579 <li x-show="mobilePanelActiveId === 'category-node-30'" 1580 x-transition:enter="transition ease-out duration-700" 1581 x-transition:enter-start="opacity-0 -translate-y-2" 1582 x-transition:enter-end="opacity-100 translate-y-0" 1583 style="transition-delay: 280ms;" 1584 class="px-5 pb-2 md:px-10"> 1585 <a href="/burglar-alarms/routine-alarm-maintenance-24-hour-service/" title="Alarm Maintenance 24/7">
1586 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Alarm Maintenance 24/7</span> 1587 </a> 1588 </li> 1589 <li x-show="mobilePanelActiveId === 'category-node-30'" 1590 x-transition:enter="transition ease-out duration-700" 1591 x-transition:enter-start="opacity-0 -translate-y-2" 1592 x-transition:enter-end="opacity-100 translate-y-0" 1593 style="transition-delay: 320ms;" 1594 class="px-5 pb-2 md:px-10"> 1595 <a href="/commercial" title="Banham Commercial"> 1596 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Banham Commercial</span> 1597 </a> 1598 </li> 1599 </ul> 1600 <div x-show="mobilePanelActiveId === 'category-node-30'" 1601 x-transition:enter="transition ease-out duration-700" 1602 x-transition:enter-start="opacity-0 -translate-y-2" 1603 x-transition:enter-end="opacity-100 translate-y-0" 1604 style="transition-delay: 360ms;" 1605 class="menu-cutom-block px-5 mt-[60px] md:px-10"> 1606 <img src="https://a.storyblok.com/f/286600090739628/359x261/acdca88f26/burglar_alarms_and_fire.jpg" alt="" class="w-full"/> 1607 <div class="inner-detials flex flex-col gap-4 bg-primary px-7 pt-7 pb-[60px]"> 1608 <h4 class="text-white mb-0">Alarms & CCTV</h4> 1609 <p class="text-white mb-0"> Bespoke-designed alarm systems installed and maintained by experts.</p> 1610 <a href="/fire-alarms" class="underline-link"><span class="text-white">Learn More</span></a> 1611 </div> 1612 </div> 1613 </div> 1614 </li> 1615 <li 1616 x-show="open" 1617 data-child-id="category-node-6-main" 1618 x-transition:enter="transition ease-out duration-700" 1619 x-transition:enter-start="opacity-0 -translate-y-2" 1620 x-transition:enter-end="opacity-100 translate-y-0" 1621 style="transition-delay: 200ms;" 1622 class="level-0 px-5 py-[6px] md:px-10" 1623 :class="{ 'active': mobilePanelActiveId === 'category-node-6' }" 1624 > 1625 <div class="flex items-center transition-transform duration-150 ease-in-out transform"> 1626 <a 1627 class="relative flex items-center justify-between cursor-pointer level-0 w-full focus:border-primary-lighter focus:outline-none focus:ring-0 hover:text-primary-darker" 1628 title="Safes" 1629 @click="openSubcategory('category-node-6')" 1630 aria-label="Open Safes subcategories" 1631 aria-haspopup="true" 1632 :aria-expanded="mobilePanelActiveId === 'category-node-6'" 1633 > 1634 Safes <i class="ti ti-chevron-right text-base"></i> 1635 </a> 1636 </div> 1637 <div 1638 data-child-id="category-node-6" 1639 class="absolute top-[0] left-[0] flex flex-col w-full h-full bg-white border-l border-primary pt-16 pb-[60px] overflow-y-auto overflow-x-hidden lg:left-[480px] lg:max-w-[480px] lg:translate-x-0 max-lg:translate-x-full transition-transform duration-150 ease-in-out z-[-1] invisible pointer-events-none" 1640 :class="{ 1641 '!-translate-x-full invisible pointer-events-none': mobilePanelActiveId !== 'category-node-6', 1642 'translate-x-0 visible pointer-events-auto': mobilePanelActiveId === 'category-node-6', 1643 1644 }" 1645 > 1646 <button 1647 type="button" 1648 class="absolute top-5 left-5 flex justify-start w-auto self-start cursor-pointer w-full md:left-10 lg:hidden focu
1648s:border-primary-lighter focus:outline-none focus:ring-0" 1649 @click="backToMainCategories('category-node-6-main')" 1650 aria-label="Back to main categories" 1651 > 1652 <i class="ti ti-arrow-left text-xl"></i> 1653 </button> 1654 <ul aria-label="Subcategories"> 1655 <li x-show="mobilePanelActiveId === 'category-node-6'" 1656 x-transition:enter="transition ease-out duration-700" 1657 x-transition:enter-start="opacity-0 -translate-y-2" 1658 x-transition:enter-end="opacity-100 translate-y-0" 1659 style="transition-delay: 0ms;" class="px-5 pb-2 md:px-10"> 1660 <span class="relative text-[10px] text-primary font-grotesk uppercase tracking-[0.8px]"> 1661 Safes </span> 1662 </li> 1663 <li x-show="mobilePanelActiveId === 'category-node-6'" 1664 x-transition:enter="transition ease-out duration-700" 1665 x-transition:enter-start="opacity-0 -translate-y-2" 1666 x-transition:enter-end="opacity-100 translate-y-0" 1667 style="transition-delay: 40ms;" class="px-5 pb-2 md:px-10"> 1668 <a 1669 href="https://www.banham.co.uk/safes/" 1670 title="Safes" 1671 > 1672 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 1673 All Safes </span> 1674 </a> 1675 </li> 1676 <li 1677 x-show="mobilePanelActiveId === 'category-node-6'" 1678 x-transition:enter="transition ease-out duration-700" 1679 x-transition:enter-start="opacity-0 -translate-y-2" 1680 x-transition:enter-end="opacity-100 translate-y-0" 1681 style="transition-delay: 80ms;" 1682 class="px-5 pb-2 md:px-10"> 1683 <a 1684 href="https://www.banham.co.uk/bespoke-safes/" 1685 title="Bespoke Safes" 1686 > 1687 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 1688 Bespoke Safes </span> 1689 </a> 1690 </li> 1691 <li 1692 x-show="mobilePanelActiveId === 'category-node-6'" 1693 x-transition:enter="transition ease-out duration-700" 1694 x-transition:enter-start="opacity-0 -translate-y-2" 1695 x-transition:enter-end="opacity-100 translate-y-0" 1696 style="transition-delay: 120ms;" 1697 class="px-5 pb-2 md:px-10"> 1698 <a 1699 href="https://www.banham.co.uk/safes/freestanding-safes/" 1700 title="Freestanding Safes" 1701 >
1702 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 1703 Freestanding Safes </span> 1704 </a> 1705 </li> 1706 </ul> 1707 <ul class="submenu-custom-link"> 1708 <li x-show="mobilePanelActiveId === 'category-node-6'" 1709 x-transition:enter="transition ease-out duration-700" 1710 x-transition:enter-start="opacity-0 -translate-y-2" 1711 x-transition:enter-end="opacity-100 translate-y-0" 1712 style="transition-delay: 160ms;" 1713 class="px-5 pt-10 pb-2 md:px-10"> 1714 <span class="relative text-[10px] text-primary font-grotesk uppercase tracking-[0.8px]">Services</span> 1715 </li> 1716 <li x-show="mobilePanelActiveId === 'category-node-6'" 1717 x-transition:enter="transition ease-out duration-700" 1718 x-transition:enter-start="opacity-0 -translate-y-2" 1719 x-transition:enter-end="opacity-100 translate-y-0" 1720 style="transition-delay: 200ms;" 1721 class="px-5 pb-2 md:px-10"> 1722 <a href="/safes/safe-services/" title="Safe Services"> 1723 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Safe Services</span> 1724 </a> 1725 </li> 1726 <li x-show="mobilePanelActiveId === 'category-node-6'" 1727 x-transition:enter="transition ease-out duration-700" 1728 x-transition:enter-start="opacity-0 -translate-y-2" 1729 x-transition:enter-end="opacity-100 translate-y-0" 1730 style="transition-delay: 240ms;" 1731 class="px-5 pb-2 md:px-10"> 1732 <a href="/locksmith-service/" title="Emergency Locksmiths"> 1733 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Emergency Locksmiths</span> 1734 </a> 1735 </li> 1736 </ul> 1737 <div x-show="mobilePanelActiveId === 'category-node-6'" 1738 x-transition:enter="transition ease-out duration-700" 1739 x-transition:enter-start="opacity-0 -translate-y-2" 1740 x-transition:enter-end="opacity-100 translate-y-0" 1741 style="transition-delay: 280ms;" 1742 class="menu-cutom-block px-5 mt-[60px] md:px-10"> 1743 <img src="https://a.storyblok.com/f/286600090739628/359x261/2f56f4ebea/meganav_safes.png" alt="" class="w-full"/> 1744 <div class="inner-detials flex flex-col gap-4 bg-primary px-7 pt-7 pb-[60px]"> 1745 <h4 class="text-white mb-0">Safes</h4> 1746 <p class="text-white mb-0"> Beautifully-designed, insurance-approved safes to keep your valuables secure.</p> 1747 <a href="/safes" class="underline-link"><span class="text-white">Learn More</span></a> 1748 </div> 1749 </div> 1750 </div> 1751 </li> 1752 <li 1753 x-show="open" 1754 data-child-id="category-node-7-main" 1755 x-transition:enter="transition ease-out duration-700" 1756 x-transition:enter-start="opacity-0 -translate-y-2" 1757 x-transition:enter-end="opacity-100 translate-y-0" 1758 style="transition-delay: 240ms;" 1759 class="level-0 px-5 py-[6px] md:px-10" 1760 :class="{ 'active': mobilePanelActiveId === 'category-node-7' }" 1761 > 1762 <div class="flex items-center transition-transform duration-150 ease-in-out transform"> 1763 <a 1764 class="relative flex items-center justify-between cursor-pointer level-0 w-full focus:border-primary-lighter focus:outline-none focus:ring-0 hover:text-primary-darker" 1765 title="Gates & Grilles" 1766 @click="openSubcategory('category-node-7')" 1767 aria-label="Open Gates & Grilles subcategories" 1768 aria-haspopup="true" 1769 :aria-expanded="mobilePanelActiveId === 'category-node-7'" 1770 > 1771 Gates & Grilles <i class="ti ti-chevron-right text-base"></i> 1772 </a> 1773 </div> 1774 <div 1775 data-child-id="category-node-7" 1776 class="absolute top-[0] left-[0] flex flex-col w-full h-full bg-white border-l border-primary pt-16 pb-[60px] overflow-y-auto overflow-x-hidden lg:left-[480px] lg:max-w-[480px] lg:translate-x-0 max-lg:translate-x-full transition-transform duration-150 ease-in-out z-[-1] invisible pointer-events-none" 1777 :class="{ 1778 '!-translate-x-full invisible pointer-events-none': mobilePanelActiveId !== 'category-node-7', 1779 'translate-x-0 visible pointer-events-auto': mobilePanelActiveId === 'category-node-7', 1780 1781 }" 1782 > 1783 <button 1784 type="button" 1785 class="absolute top-5 left-5 flex justify-start w-auto self-start cursor-pointer w-full md:left-10 lg:hidden focu
1785s:border-primary-lighter focus:outline-none focus:ring-0" 1786 @click="backToMainCategories('category-node-7-main')" 1787 aria-label="Back to main categories" 1788 > 1789 <i class="ti ti-arrow-left text-xl"></i> 1790 </button> 1791 <ul aria-label="Subcategories"> 1792 <li x-show="mobilePanelActiveId === 'category-node-7'" 1793 x-transition:enter="transition ease-out duration-700" 1794 x-transition:enter-start="opacity-0 -translate-y-2" 1795 x-transition:enter-end="opacity-100 translate-y-0" 1796 style="transition-delay: 0ms;" class="px-5 pb-2 md:px-10"> 1797 <span class="relative text-[10px] text-primary font-grotesk uppercase tracking-[0.8px]"> 1798 Gates & Grilles </span> 1799 </li> 1800 <li x-show="mobilePanelActiveId === 'category-node-7'" 1801 x-transition:enter="transition ease-out duration-700" 1802 x-transition:enter-start="opacity-0 -translate-y-2" 1803 x-transition:enter-end="opacity-100 translate-y-0" 1804 style="transition-delay: 40ms;" class="px-5 pb-2 md:px-10"> 1805 <a 1806 href="https://www.banham.co.uk/gates-grilles-shutters/" 1807 title="Gates & Grilles" 1808 > 1809 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 1810 All Gates & Grilles </span> 1811 </a> 1812 </li> 1813 <li 1814 x-show="mobilePanelActiveId === 'category-node-7'" 1815 x-transition:enter="transition ease-out duration-700" 1816 x-transition:enter-start="opacity-0 -translate-y-2" 1817 x-transition:enter-end="opacity-100 translate-y-0" 1818 style="transition-delay: 80ms;" 1819 class="px-5 pb-2 md:px-10"> 1820 <a 1821 href="https://www.banham.co.uk/gates-grilles-shutters/gates/" 1822 title="Security Gates" 1823 > 1824 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 1825 Security Gates </span> 1826 </a> 1827 </li> 1828 <li 1829 x-show="mobilePanelActiveId === 'category-node-7'" 1830 x-transition:enter="transition ease-out duration-700" 1831 x-transition:enter-start="opacity-0 -translate-y-2" 1832 x-transition:enter-end="opacity-100 translate-y-0" 1833 style="transition-delay: 120ms;" 1834 class="px-5 pb-2 md:px-10"> 1835 <a 1836 href="https://www.banham.co.uk/gates-grilles-shutters/gr
1836illes/" 1837 title="Security Grilles" 1838 > 1839 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 1840 Security Grilles </span> 1841 </a> 1842 </li> 1843 <li 1844 x-show="mobilePanelActiveId === 'category-node-7'" 1845 x-transition:enter="transition ease-out duration-700" 1846 x-transition:enter-start="opacity-0 -translate-y-2" 1847 x-transition:enter-end="opacity-100 translate-y-0" 1848 style="transition-delay: 160ms;" 1849 class="px-5 pb-2 md:px-10"> 1850 <a 1851 href="https://www.banham.co.uk/gates-grilles-shutters/railings/" 1852 title="Railings" 1853 > 1854 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 1855 Railings </span> 1856 </a> 1857 </li> 1858 <li 1859 x-show="mobilePanelActiveId === 'category-node-7'" 1860 x-transition:enter="transition ease-out duration-700" 1861 x-transition:enter-start="opacity-0 -translate-y-2" 1862 x-transition:enter-end="opacity-100 translate-y-0" 1863 style="transition-delay: 200ms;" 1864 class="px-5 pb-2 md:px-10"> 1865 <a 1866 href="https://www.banham.co.uk/gates-grilles-shutters/roller-shutters/" 1867 title="Roller Shutters" 1868 > 1869 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 1870 Roller Shutters </span> 1871 </a> 1872 </li> 1873 </ul> 1874 <ul class="submenu-custom-link"> 1875 <li x-show="mobilePanelActiveId === 'category-node-7'" 1876 x-transition:enter="transition ease-out duration-700" 1877 x-transition:enter-start="opacity-0 -translate-y-2" 1878 x-transition:enter-end="opacity-100 translate-y-0" 1879 style="transition-delay: 240ms;" 1880 class="px-5 pt-10 pb-2 md:px-10"> 1881 <span class="relative text-[10px] text-primary font-grotesk uppercase tracking-[0.8px]">Related Products </span> 1882 </li> 1883 <li x-show="mobilePanelActiveId === 'category-node-7'" 1884 x-transition:enter="transition ease-out duration-700" 1885 x-transition:enter-start="opacity-0 -translate-y-2" 1886 x-transition:enter-end="opacity-100 translate-y-0" 1887 style="transition-delay: 280ms;" 1888 class="px-5 pb-2 md:px-10"> 1889 <a href="/locks/key-cutting-key-registration/" title="Keycutting and Key Registration">
1890 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Keycutting and Key Registration</span> 1891 </a> 1892 </li> 1893 <li x-show="mobilePanelActiveId === 'category-node-7'" 1894 x-transition:enter="transition ease-out duration-700" 1895 x-transition:enter-start="opacity-0 -translate-y-2" 1896 x-transition:enter-end="opacity-100 translate-y-0" 1897 style="transition-delay: 320ms;" 1898 class="px-5 pb-2 md:px-10"> 1899 <a href="/locksmith-service/" title="Emergency Locksmiths"> 1900 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Emergency Locksmiths</span> 1901 </a> 1902 </li> 1903 <li x-show="mobilePanelActiveId === 'category-node-7'" 1904 x-transition:enter="transition ease-out duration-700" 1905 x-transition:enter-start="opacity-0 -translate-y-2" 1906 x-transition:enter-end="opacity-100 translate-y-0" 1907 style="transition-delay: 360ms;" 1908 class="px-5 pb-2 md:px-10"> 1909 <a href="/commercial" title="Banham Commercial"> 1910 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Banham Commercial</span> 1911 </a> 1912 </li> 1913 </ul> 1914 <div x-show="mobilePanelActiveId === 'category-node-7'" 1915 x-transition:enter="transition ease-out duration-700" 1916 x-transition:enter-start="opacity-0 -translate-y-2" 1917 x-transition:enter-end="opacity-100 translate-y-0" 1918 style="transition-delay: 400ms;" 1919 class="menu-cutom-block px-5 mt-[60px] md:px-10"> 1920 <img src="https://a.storyblok.com/f/286600090739628/359x261/4001e6e188/meganav_gates_grilles_shutters.png" alt="" class="w-full"/> 1921 <div class="inner-detials flex flex-col gap-4 bg-primary px-7 pt-7 pb-[60px]"> 1922 <h4 class="text-white mb-0">Gates, Grilles & Shutters</h4> 1923 <p class="text-white mb-0"> Sturdy gates, grilles and shutters we can customise to match your property.</p> 1924 <a href="/gates-grilles-shutters/" class="underline-link"><span class="text-white">Learn More</span></a> 1925 </div> 1926 </div> 1927 </div> 1928 </li> 1929 <li 1930 x-show="open" 1931 data-child-id="category-node-54-main" 1932 x-transition:enter="transition ease-out duration-700" 1933 x-transition:enter-start="opacity-0 -translate-y-2" 1934 x-transition:enter-end="opacity-100 translate-y-0" 1935 style="transition-delay: 280ms;" 1936 class="level-0 px-5 py-[6px] md:px-10" 1937 :class="{ 'active': mobilePanelActiveId === 'category-node-54' }" 1938 > 1939 <div class="flex items-center transition-transform duration-150 ease-in-out transform"> 1940 <a 1941 class="relative flex items-center justify-between cursor-pointer level-0 w-full focus:border-primary-lighter focus:outline-none focus:ring-0 hover:text-primary-darker" 1942 title="Keyholding" 1943 @click="openSubcategory('category-node-54')" 1944 aria-label="Open Keyholding subcategories" 1945 aria-haspopup="true" 1946 :aria-expanded="mobilePanelActiveId === 'category-node-54'" 1947 > 1948 Keyholding <i class="ti ti-chevron-right text-base"></i> 1949 </a> 1950 </div> 1951 <div 1952 data-child-id="category-node-54" 1953 class="absolute top-[0] left-[0] flex flex-col w-full h-full bg-white border-l border-primary pt-16 pb-[60px] overflow-y-auto overflow-x-hidden lg:left-[480px] lg:max-w-[480px] lg:translate-x-0 max-lg:translate-x-full transition-transform duration-150 ease-in-out z-[-1] invisible pointer-events-none" 1954 :class="{ 1955 '!-translate-x-full invisible pointer-events-none': mobilePanelActiveId !== 'category-node-54', 1956 'translate-x-0 visible pointer-events-auto': mobilePanelActiveId === 'category-node-54', 1957 1958 }" 1959 > 1960 <button 1961 type="button" 1962 class="absolute top-5 left-5 flex justify-start w-auto self-start cursor-pointer w-full md:left-10 lg:hidden focu
1962s:border-primary-lighter focus:outline-none focus:ring-0" 1963 @click="backToMainCategories('category-node-54-main')" 1964 aria-label="Back to main categories" 1965 > 1966 <i class="ti ti-arrow-left text-xl"></i> 1967 </button> 1968 <ul aria-label="Subcategories"> 1969 <li x-show="mobilePanelActiveId === 'category-node-54'" 1970 x-transition:enter="transition ease-out duration-700" 1971 x-transition:enter-start="opacity-0 -translate-y-2" 1972 x-transition:enter-end="opacity-100 translate-y-0" 1973 style="transition-delay: 0ms;" class="px-5 pb-2 md:px-10"> 1974 <span class="relative text-[10px] text-primary font-grotesk uppercase tracking-[0.8px]"> 1975 Keyholding </span> 1976 </li> 1977 <li x-show="mobilePanelActiveId === 'category-node-54'" 1978 x-transition:enter="transition ease-out duration-700" 1979 x-transition:enter-start="opacity-0 -translate-y-2" 1980 x-transition:enter-end="opacity-100 translate-y-0" 1981 style="transition-delay: 40ms;" class="px-5 pb-2 md:px-10"> 1982 <a 1983 href="https://www.banham.co.uk/keyholding/" 1984 title="Keyholding" 1985 > 1986 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 1987 All Keyholding </span> 1988 </a> 1989 </li> 1990 <li 1991 x-show="mobilePanelActiveId === 'category-node-54'" 1992 x-transition:enter="transition ease-out duration-700" 1993 x-transition:enter-start="opacity-0 -translate-y-2" 1994 x-transition:enter-end="opacity-100 translate-y-0" 1995 style="transition-delay: 80ms;" 1996 class="px-5 pb-2 md:px-10"> 1997 <a 1998 href="https://www.banham.co.uk/keyholding/residential/" 1999 title="For Homes" 2000 > 2001 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 2002 For Homes </span> 2003 </a> 2004 </li> 2005 <li 2006 x-show="mobilePanelActiveId === 'category-node-54'" 2007 x-transition:enter="transition ease-out duration-700" 2008 x-transition:enter-start="opacity-0 -translate-y-2" 2009 x-transition:enter-end="opacity-100 translate-y-0" 2010 style="transition-delay: 120ms;" 2011 class="px-5 pb-2 md:px-10"> 2012 <a 2013 href="https://www.banham.co.uk/keyholding/pricing/" 2014 title="Pricing" 2015 >
2016 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 2017 Pricing </span> 2018 </a> 2019 </li> 2020 </ul> 2021 <ul class="submenu-custom-link"> 2022 <li x-show="mobilePanelActiveId === 'category-node-54'" 2023 x-transition:enter="transition ease-out duration-700" 2024 x-transition:enter-start="opacity-0 -translate-y-2" 2025 x-transition:enter-end="opacity-100 translate-y-0" 2026 style="transition-delay: 160ms;" 2027 class="px-5 pt-10 pb-2 md:px-10"> 2028 <span class="relative text-[10px] text-primary font-grotesk uppercase tracking-[0.8px]">Related Services</span> 2029 </li> 2030 <li x-show="mobilePanelActiveId === 'category-node-54'" 2031 x-transition:enter="transition ease-out duration-700" 2032 x-transition:enter-start="opacity-0 -translate-y-2" 2033 x-transition:enter-end="opacity-100 translate-y-0" 2034 style="transition-delay: 200ms;" 2035 class="px-5 pb-2 md:px-10"> 2036 <a href="/commercial" title="Banham Commercial"> 2037 <span class="relative text-sm text-primary-darker hover:text-primary-darker">Banham Commercial</span> 2038 </a> 2039 </li> 2040 </ul> 2041 <div x-show="mobilePanelActiveId === 'category-node-54'" 2042 x-transition:enter="transition ease-out duration-700" 2043 x-transition:enter-start="opacity-0 -translate-y-2" 2044 x-transition:enter-end="opacity-100 translate-y-0" 2045 style="transition-delay: 240ms;" 2046 class="menu-cutom-block px-5 mt-[60px] md:px-10"> 2047 <img src="https://a.storyblok.com/f/286600090739628/359x261/64109011f1/meganav-trade-more-from-banham.jpg" alt="" class="w-full"/> 2048 <div class="inner-detials flex flex-col gap-4 bg-primary px-7 pt-7 pb-[60px]"> 2049 <h4 class="text-white mb-0">Keyholding & Alarm Response</h4> 2050 <p class="text-white mb-0"> Peace of mind knowing we'll be there when you need us.</p> 2051 <a href="/keyholding" class="underline-link"><span class="text-white">Learn More</span></a> 2052 </div> 2053 </div> 2054 </div> 2055 </li> 2056 <li 2057 x-show="open" 2058 data-child-id="category-node-47-main" 2059 x-transition:enter="transition ease-out duration-700" 2060 x-transition:enter-start="opacity-0 -translate-y-2" 2061 x-transition:enter-end="opacity-100 translate-y-0" 2062 style="transition-delay: 320ms;" 2063 class="level-0 px-5 py-[6px] md:px-10" 2064 :class="{ 'active': mobilePanelActiveId === 'category-node-47' }" 2065 > 2066 <div class="flex items-center transition-transform duration-150 ease-in-out transform"> 2067 <a 2068 class="relative flex items-center justify-between cursor-pointer level-0 w-full focus:border-primary-lighter focus:outline-none focus:ring-0 hover:text-primary-darker" 2069 title="Locksmith" 2070 @click="openSubcategory('category-node-47')" 2071 aria-label="Open Locksmith subcategories" 2072 aria-haspopup="true" 2073 :aria-expanded="mobilePanelActiveId === 'category-node-47'" 2074 > 2075 Locksmith <i class="ti ti-chevron-right text-base"></i> 2076 </a> 2077 </div> 2078 <div 2079 data-child-id="category-node-47" 2080 class="absolute top-[0] left-[0] flex flex-col w-full h-full bg-white border-l border-primary pt-16 pb-[60px] overflow-y-auto overflow-x-hidden lg:left-[480px] lg:max-w-[480px] lg:translate-x-0 max-lg:translate-x-full transition-transform duration-150 ease-in-out z-[-1] invisible pointer-events-none" 2081 :class="{ 2082 '!-translate-x-full invisible pointer-events-none': mobilePanelActiveId !== 'category-node-47', 2083 'translate-x-0 visible pointer-events-auto': mobilePanelActiveId === 'category-node-47', 2084 2085 }" 2086 > 2087 <button 2088 type="button" 2089 class="absolute top-5 left-5 flex justify-start w-auto self-start cursor-pointer w-full md:left-10 lg:hidden focu
2089s:border-primary-lighter focus:outline-none focus:ring-0" 2090 @click="backToMainCategories('category-node-47-main')" 2091 aria-label="Back to main categories" 2092 > 2093 <i class="ti ti-arrow-left text-xl"></i> 2094 </button> 2095 <ul aria-label="Subcategories"> 2096 <li x-show="mobilePanelActiveId === 'category-node-47'" 2097 x-transition:enter="transition ease-out duration-700" 2098 x-transition:enter-start="opacity-0 -translate-y-2" 2099 x-transition:enter-end="opacity-100 translate-y-0" 2100 style="transition-delay: 0ms;" class="px-5 pb-2 md:px-10"> 2101 <span class="relative text-[10px] text-primary font-grotesk uppercase tracking-[0.8px]"> 2102 Locksmith </span> 2103 </li> 2104 <li x-show="mobilePanelActiveId === 'category-node-47'" 2105 x-transition:enter="transition ease-out duration-700" 2106 x-transition:enter-start="opacity-0 -translate-y-2" 2107 x-transition:enter-end="opacity-100 translate-y-0" 2108 style="transition-delay: 40ms;" class="px-5 pb-2 md:px-10"> 2109 <a 2110 href="https://www.banham.co.uk/locksmith-service/" 2111 title="Locksmith" 2112 > 2113 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 2114 All Locksmith </span> 2115 </a> 2116 </li> 2117 <li 2118 x-show="mobilePanelActiveId === 'category-node-47'" 2119 x-transition:enter="transition ease-out duration-700" 2120 x-transition:enter-start="opacity-0 -translate-y-2" 2121 x-transition:enter-end="opacity-100 translate-y-0" 2122 style="transition-delay: 80ms;" 2123 class="px-5 pb-2 md:px-10"> 2124 <a 2125 href="https://www.banham.co.uk/locksmith-service/emergency-locksmith/" 2126 title="Emergency Locksmith" 2127 > 2128 <span class="relative text-sm text-primary-darker hover:text-primary-darker"> 2129 Emergency Locksmith </span> 2130 </a> 2131 </li> 2132 </ul> 2133 <div x-show="mobilePanelActiveId === 'category-node-47'" 2134 x-transition:enter="transition ease-out duration-700" 2135 x-transition:enter-start="opacity-0 -translate-y-2" 2136 x-transition:enter-end="opacity-100 translate-y-0" 2137 style="transition-delay: 120ms;" 2138 class="menu-cutom-block px-5 mt-[60px] md:px-10"> 2139 <img src="https://a.storyblok.com/f/286600090739628/359x261/eb8d21273f/locksmith-menu.jpg" alt="" class="w-full"/> 2140 <div class="inner-detials flex flex-col gap-4 bg-primary px-7 pt-7 pb-[60px]"> 2141 <h4 class="text-white mb-0">Locksmith Services</h4> 2142 <p class="text-white mb-0"> Expert locksmiths providing lock changes, cylinder changes, emergency lock outs and more.</p> 2143 <a href="/locksmith-service/emergency-locksmith" class="underline-link"><span class="text-white">Learn More</span></a> 2144 </div> 2145 </div> 2146 </div> 2147 </li> 2148 </ul> 2149 <div class="menu-custom-links"> 2150 <ul> 2151 <li x-show="open" 2152 x-transition:enter="transition ease-out duration-700" 2153 x-transition:enter-start="opacity-0 -translate-y-2" 2154 x-transition:enter-end="opacity-100 translate-y-0" 2155 style="transition-delay: 360ms;"> 2156 <a 2157 href="/customer/account/login/" 2158 :href="isLoggedIn ? accountUrl : '\u002Fcustomer\u002Faccount\u002Flogin\u002F'" 2159 :title="isLoggedIn ? accountText : signInText" 2160 class="relative flex items-center justify-between w-full" 2161 @click="if (isLoggedIn) { $event.preventDefault(); openSubcategory('account') }" 2162 >
2163 <span class="relative" x-text="isLoggedIn ? accountText : signInText">Sign In</span> 2164 <i class="ti ti-chevron-right text-base" x-show="isLoggedIn" x-cloak></i> 2165 </a> 2166 </li> 2167 <li x-show="open && !isLoggedIn" 2168 x-transition:enter="transition ease-out duration-700" 2169 x-transition:enter-start="opacity-0 -translate-y-2" 2170 x-transition:enter-end="opacity-100 translate-y-0" 2171 style="transition-delay: 400ms;"> 2172 <a href="/customer/account/login/"><span>Make a Payment</span></a> 2173 </li> 2174 <li x-show="open" 2175 x-transition:enter="transition ease-out duration-700" 2176 x-transition:enter-start="opacity-0 -translate-y-2" 2177 x-transition:enter-end="opacity-100 translate-y-0" 2178 style="transition-delay: 440ms;"> 2179 <a href="/customer-help-centre"><span>Help and Support</span></a> 2180 </li> 2181 <li x-show="open" 2182 x-transition:enter="transition ease-out duration-700" 2183 x-transition:enter-start="opacity-0 -translate-y-2" 2184 x-transition:enter-end="opacity-100 translate-y-0" 2185 style="transition-delay: 480ms;"> 2186 <a href="/contact/""><span>Contact Us</span></a> 2187 </li> 2188 <li x-show="open" 2189 x-transition:enter="transition ease-out duration-700" 2190 x-transition:enter-start="opacity-0 -translate-y-2" 2191 x-transition:enter-end="opacity-100 translate-y-0" 2192 style="transition-delay: 520ms;"> 2193 <a href="/trade/trade-area"><span>Trade Site</span></a> 2194 </li> 2195 <li x-show="open" 2196 x-transition:enter="transition ease-out duration-700" 2197 x-transition:enter-start="opacity-0 -translate-y-2" 2198 x-transition:enter-end="opacity-100 translate-y-0" 2199 style="transition-delay: 560ms;"> 2200 <a href="/about-us"><span>About Us</span></a> 2201 </li> 2202 <li x-show="open" 2203 x-transition:enter="transition ease-out duration-700" 2204 x-transition:enter-start="opacity-0 -translate-y-2" 2205 x-transition:enter-end="opacity-100 translate-y-0" 2206 style="transition-delay: 600ms;"> 2207 <a href="/journal"><span>Journal</span></a> 2208 </li> 2209 <li x-show="open" 2210 x-transition:enter="transition ease-out duration-700" 2211 x-transition:enter-start="opacity-0 -translate-y-2" 2212 x-transition:enter-end="opacity-100 translate-y-0" 2213 style="transition-delay: 640ms;"> 2214 <a href="/showrooms"><span>Showrooms</span></a> 2215 </li> 2216 <li x-show="open" 2217 x-transition:enter="transition ease-out duration-700" 2218 x-transition:enter-start="opacity-0 -translate-y-2" 2219 x-transition:enter-end="opacity-100 translate-y-0" 2220 style="transition-delay: 680ms;"> 2221 <a href="/book-a-survey"><span>Arrange a Consultation</span></a> 2222 </li> 2223 <li x-show="open" 2224 x-transition:enter="transition ease-out duration-700" 2225 x-transition:enter-start="opacity-0 -translate-y-2" 2226 x-transition:enter-end="opacity-100 translate-y-0" 2227 style="transition-delay: 720ms;"> 2228 <a href="tel:+442076225151""><span>020 7622 5151</span></a> 2229 </li> 2230 </ul> 2231 </div> 2232 <div 2233 data-child-id="account" 2234 class="absolute top-0 left-0 flex flex-col w-full h-full bg-white border-l border-primary pt-16 pb-[60px] overflow-y-auto overflow-x-hidden lg:left-[480px] lg:max-w-[480px] lg:translate-x-0 max-lg:translate-x-full transition-transform duration-150 ease-in-out z-[-1] invisible pointer-events-none" 2235 :class="{ 2236 '!-translate-x-full invisible pointer-events-none': mobilePanelActiveId !== 'account', 2237 'translate-x-0 visible pointer-events-auto': mobilePanelActiveId === 'account', 2238 }" 2239 > 2240 <button type="button" class="absolute top-5 left-5 md:left-10 lg:hidden" @click="mobilePanelActiveId = 0"> 2241 <i class="ti ti-arrow-left text-xl"></i> 2242 </button> 2243 <ul> 2244 <li class="px-5 pb-2 md:px-10"><span class="relative text-[10px] text-primary font-grotesk uppercase tracking-[0.8px]">My Account</span></li> 2245 <li class="px-5 pb-2 md:px-10"><a href="https://www.banham.co.uk/customer/account/"><span class="relative text-sm text-primary-darker">View</span></a></li> 2246 <li class="px-5 pb-2 md:px-10"><a href="https://www.banham.co.uk/banham-payment/customer/view/"><span class="relative text-sm text-primary-darker">Make Payment</span></a></li> 2247 <li class="px-5 pb-2 md:px-10"><a href="https://www.banham.co.uk/customer/account/logout/" @click.prevent="hyva.postForm({action: signOutUrl, data: {form_key: hyva.getFormKey()}})"><span class="relative text-sm text-primary-darker">Sign Out</span></a></li> 2248 </ul> 2249 </div> 2250 </div> 2251 </div> 2252 </div> 2253 <div 2254 class="menu-overlay bg-black fixed inset-0 z-[-1] transition-opacity duration-300 opacity-0" 2255 :class="{ 'opacity-50': open, 'opacity-0 pointer-events-none': !open }" 2256 ></div> 2257</nav> 2258 <div class="header-custom-links gap-4 flex"> 2259 <a href="#open_enquiry_form" title="Book a Survey "><i class="ti ti-phone lg:hidden text-xl"></i><span class="text-[0] lg:text-sm">Book a Survey </span></a> 2260 <a href="tel:+442076225151" title="020 7622 5151"><i class="ti ti-phone lg:hidden text-xl"></i><span class="text-[0] lg:text-sm">Call Us</span></a> 2261 </div> 2262 </div> 2263 2264 <!--Logo--> 2265 <div class="absolute left-1/2 transform -translate-x-1/2"> 2266 <a 2267 class="flex items-center justify-center text-xl font-medium tracking-wide text-gray-800 2268 no-underline hover:no-underline font-title" 2269 href="https://www.banham.co.uk/" 2270 aria-label="Go to Home page" 2271 > 2272 <svg class="w-[135px] lg:w-auto" width="190" height="28" viewBox="0 0 190 28" fill="none" xmlns="http://www.w3.org/2000/svg"> 2273 <g clip-path="url(#clip0_645_11687)"> 2274 <mask id="mask0_645_11687" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="190" height="28"> 2275 <path d="M189.5 0H0.5V28H189.5V0Z" fill="white"/> 2276 </mask> 2277 <g mask="url(#mask0_645_11687)"> 2278 <path d="M22.7401 19.7514C22.7401 24.5436 18.5183 27.5017 11.8285 27.5017H0.626192L0.5 25.9176L2.29703 25.3762C3.13245 25.1263 3.42637 24.7092 3.42637 23.6679V3.83374C3.42637 2.79252 3.13405 2.37539 2.29703 2.12543L0.5 1.58412L0.626192 0H11.747C17.5566 0 21.0261 2.45818 21.0261 6.54188C21.0261 9.49997 19.0198 11.834 15.8427 12.8752C20.1492 13.6251 22.7401 16.1676 22.7401 19.7498M10.5746 2.37539H8.1913V12.1253H10.3653C13.9194 12.1253 16.2612 10.0429 16.2612 6.91761C16.2612 4.12668 14.0872 2.37539 10.5
2278762 2.37539M10.9515 25.1263C15.258 25.1263 17.8074 23.168 17.8074 20.0014C17.8074 16.6261 15.0488 14.5007 10.7423 14.5007H8.1929V25.1263H10.9515Z" fill="black"/> 2279 <path d="M54.4701 27.5017H43.8093L43.6831 25.9176L45.2725 25.5418C46.3172 25.2919 46.6942 24.792 46.3172 23.7077L44.6447 18.9156H34.821L33.0239 23.6648C32.6469 24.6646 32.8977 25.2903 33.944 25.5402L35.5334 25.916L35.4072 27.5001H26.8373L26.7127 25.916L27.8405 25.6246C28.9698 25.3333 29.5129 24.9161 29.8899 23.9577L37.5397 4.24769C38.0412 2.95651 37.832 2.24803 36.7873 1.95668L35.4503 1.58095L35.5749 -0.00158691H42.8493C43.4339 1.91528 44.5217 5.04055 46.0265 9.16564L51.3776 23.8319C51.7115 24.749 52.2562 25.2489 53.1331 25.4988L54.5963 25.916L54.4701 27.5001V27.5017ZM41.3015 9.29141C40.5491 7.12459 40.2137 5.49907 39.9629 3.29085C39.629 5.49907 39.2105 7.12459 38.4166 9.25002L35.6995 16.5418H43.8509L41.3015 9.29141Z" fill="black"/> 2280 <path d="M77.504 16.209C78.9256 18.2501 79.9703 20.2083 81.0166 22.7923C80.6396 20.3341 80.5565 18.1673 80.5565 15.1678V3.83374C80.5565 2.79252 80.2642 2.334 79.4272 2.12543L77.2947 1.58412L77.4209 0H86.2L86.3262 1.58412L84.5292 2.12543C83.6937 2.37539 83.3998 2.79252 83.3998 3.83374V13.7508C83.3998 20.5841 83.4414 23.3766 83.6091 27.1259L80.0134 28.0016L68.6833 11.834C66.8432 9.20862 65.6739 6.91761 64.7953 4.41804C65.0877 6.54347 65.1723 8.79309 65.1723 11.834V23.6679C65.1723 24.7092 65.4646 25.1677 66.3017 25.3762L68.4341 25.9176L68.3079 27.5017H59.5288L59.4042 25.9176L61.2013 25.3762C62.0367 25.1263 62.3306 24.7092 62.3306 23.6679V3.83374C62.3306 2.79252 62.0383 2.37539 61.2013 2.12543L59.4042 1.58412L59.5272 0H66.1755L77.504 16.209Z" fill="black"/> 2281 <path d="M116.885 23.6679C116.885 24.7092 117.178 25.1263 118.015 25.3762L119.812 25.9175L119.687 27.5017H108.985L108.859 25.9175L110.991 25.3762C111.827 25.1677 112.121 24.7092 112.121 23.6679V14.3335H99.8704V23.6679C99.8704 24.7092 100.163 25.1677 101 25.3762L103.132 25.9175L103.006 27.5017H92.3036L92.1774 25.9175L93.9745 25.3762C94.8115 25.1263 95.1038 24.7092 95.1038 23.6679V3.83374C95.1038 2.79251 94.8115 2.37539 93.9745 2.12543L92.1774 1.58412L92.3036 0.00158691H103.006L103.132 1.58412L101 2.12543C100.164 2.33399 99.8704 2.79251 99.8704 3.83374V11.9581H112.121V3.83374C112.121 2.79251 111.828 2.33399 110.991 2.12543L108.859 1.58412L108.985 0.00158691H119.687L119.812 1.58412L118.015 2.12543C117.179 2.37539 116.885 2.79251 116.885 3.83374V23.6679Z" fill="black"/> 2282 <path d="M152.377 27.5017H141.716L141.59 25.9176L143.18 25.5418C144.224 25.2919 144.601 24.792 144.224 23.7077L142.552 18.9156H132.728L130.931 23.6648C130.554 24.6646 130.805 25.2903 131.851 25.5402L133.439 25.916L133.314 27.5001H124.744L124.62 25.916L125.748 25.6246C126.877 25.3333 127.42 24.9161 127.797 23.9577L135.447 4.24769C135.948 2.95651 135.739 2.24803 134.694 1.95668L133.357 1.58095L133.482 -0.00158691H140.756C141.341 1.91528 142.429 5.04055 143.933 9.16564L149.285 23.8319C149.619 24.749 150.163 25.2489 151.04 25.4988L152.503 25.916L152.377 27.5001V27.5017ZM139.208 9.29141C138.456 7.12459 138.121 5.49907 137.87 3.29085C137.536 5.49907 137.118 7.12459 136.324 9.25002L133.607 16.5418H141.758L139.208 9.29141Z" fill="black"/> 2283 <path d="M186.573 23.6679C186.573 24.7092 186.866 25.1263 187.703 25.3762L189.5 25.9176L189.375 27.5017H178.673L178.547 25.9176L180.679 25.3762C181.515 25.1677 181.807 24.7092 181.807 23.6679V13.1267C181.807 9.41878 182.016 6.75203 182.35 4.29385C181.513 6.75203 180.636 8.75328 179.297 11.4614L174.029 22.087H170.728L165.293 11.5872C164.039 9.17041 163.161 6.79502 162.533 4.33684C162.91 7.04498 163.076 9.75311 163.076 13.1283V23.6695C163.076 24.7108 163.369 25.1693 164.206 25.3778L166.338 25.9191L166.212 27.5033H157.433L157.308 25.9191L159.105 25.3778C159.942 25.1279 160.235 24.7108 160.235 23.6695V3.83374C160.235 2.79252 159.942 2.37539 159.105 2.12543L157.308 1.58412L157.433 0.00159209H164.498L170.769 12.0855C171.772 14.0438 172.525 15.7107 173.362 17.8361C174.156 15.8365 174.908 14.1696 175.911 12.0855L181.807 0H189.374L189.498 1.58253L187.701 2.12384C186.866 2.3738 186.572 2.79093 186.572 3.83215V23.6663L186.573 23.6679Z" fill="black"/> 2284 </g> 2285 </g> 2286 <defs> 2287 <clipPath id="clip0_645_11687"> 2288 <rect width="189" height="28" fill="white" transform="translate(0.5)"/> 2289 </clipPath> 2290 </defs> 2291 </svg> 2292 2293 </a> 2294</div> 2295 <div class="flex items-center gap-4 md:gap-4 order-3"> 2296 <!--Search Icon--> 2297 <button 2298 id="menu-search-icon" 2299 class="leading-none" 2300 @click.prevent=" 2301 searchOpen = !searchOpen;
2302 $dispatch('search-open'); 2303 " 2304 aria-label="Toggle search form" 2305 aria-haspopup="true" 2306 :aria-expanded="searchOpen" 2307 x-ref="searchButton" 2308 title="Search" 2309 > 2310 <i class="ti ti-search text-xl"></i> 2311 </button> 2312 2313 <!-- Additional Header Elements --> 2314 2315 <!--Customer Icon & Dropdown--> 2316 <!-- BLOCK header.customer --> 2317<div 2318 class="relative hidden lg:inline-block" 2319 x-data="{ open: false }" 2320 @keyup.escape="open = false" 2321 @click.outside="open = false" 2322> 2323 <button 2324 type="button" 2325 id="customer-menu" 2326 class="block leading-none" 2327 @click="open = !open" 2328 :aria-expanded="open ? 'true' : 'false'" 2329 aria-label="My Account" 2330 aria-haspopup="true" 2331 title="My Account" 2332 > 2333 <i class="ti ti-user text-xl"></i> 2334 </button> 2335 <nav 2336 class=" 2337 absolute right-0 z-20 w-40 py-2 mt-2 -mr-4 px-1 overflow-auto origin-top-right rounded-sm 2338 shadow-lg sm:w-48 lg:mt-3 bg-container-lighter 2339 " 2340 x-cloak 2341 x-show="open" 2342 aria-labelledby="customer-menu" 2343 > 2344 2345<a id="customer.header.sign.in.link" 2346 class="block px-4 py-2 lg:px-5 lg:py-2 hover:bg-gray-100" 2347 onclick="hyva.setCookie && hyva.setCookie( 2348 'login_redirect', 2349 window.location.href, 2350 1 2351 )" 2352 href="https://www.banham.co.uk/customer/account/index/" 2353 title="Sign In" 2354> 2355 Sign In</a> 2356 <a id="customer.header.register.link" 2357 class="block px-4 py-2 lg:px-5 lg:py-2 hover:bg-gray-100" 2358 href="https://www.banham.co.uk/customer/account/create/" 2359 title="Create an Account" 2360 > 2361 Create an Account </a> 2362 <a id="customer.header.online.payment.link.guest" class="block px-4 py-2 lg:px-5 lg:py-2 hover:bg-gray-100" href="https://www.banham.co.uk/banham-payment/customer/view/" title="Online Payment" >Online Payment</a> </nav> 2363</div><!-- /BLOCK header.customer --> 2364 <!--Cart Icon--> 2365 <button 2366 id="menu-cart-icon" 2367 class="relative inline-block leading-none" 2368 x-ref="cartButton" 2369 :aria-disabled="isCartEmpty()" 2370 :aria-label="` 2371 Toggle minicart, 2372 ${isCartEmpty() 2373 ? 'Cart is empty' 2374 : cart.summary_count > 1 2375 ? hyva.str('%1 items', cart.summary_count) 2376 : hyva.str('%1 item', cart.summary_count) 2377 }`" 2378 title="View cart" 2379 @click.prevent="() => { 2380 $dispatch('toggle-cart', { isOpen: true }) 2381 }" 2382 @toggle-cart.window="toggleCart($event)" 2383 :aria-expanded="isCartOpen" 2384 aria-haspopup="dialog" 2385 > 2386 <i class="ti ti-shopping-bag text-xl"></i> 2387 <span 2388 x-text="cart.summary_count" 2389 x-show="!isCartEmpty()" 2390 x-cloak 2391 class="mini-count absolute bottom-[1px] right-[1px] w-[7px] h-[7px] border-0 rounded-full bg-primary text-white text-[0]" 2392 aria-hidden="true" 2393 ></span> 2394 </button> 2395 </div> 2396 </div> 2397 <!--Search--> 2398 <div class="header-search absolute z-10 w-full border-t bg-white border-container-lighter" 2399 id="search-content" 2400 x-cloak x-show="searchOpen" 2401 @click.outside="searchOpen = false" 2402 @keydown.escape=" 2403 searchOpen = false; 2404 $refs.searchButton.focus(); 2405 " 2406 > 2407 2408<div class="container py-10 mx-auto text-black" x-data="initMiniSearch()"> 2409 <search title="Store" role="search"> 2410 <form 2411 id="search_mini_form" 2412 class="form minisearch flex justify-center" 2413 action="https://www.banham.co.uk/catalogsearch/result/" 2414 method="get" 2415 @submit.prevent="search()" 2416 x-ref="form" 2417 > 2418 <div class="relative flex w-full lg:max-w-[660px]"> 2419 <i class="ti ti-search absolute left-5 top-1/2 transform -translate-y-1/2 text-xl"></i> 2420 <input 2421 id="search" 2422 x-ref="searchInput" 2423 type="search" 2424 autocomplete="off" 2425 name="q" 2426 value="" 2427 placeholder="Search" 2428 maxlength="64" 2429 aria-label="Search" 2430 class="form-input min-h-[53px] py-4 pl-12 placeholder:text-black" 2431 @search-open.window.debounce.10="$el.focus(); $el.select()" 2432 > 2433 </div> 2434 <button 2435 type="submit" 2436 class="hidden action search btn btn-primary rounded-l-none rounded-r shrink-0 p-3 shadow-none" 2437 title="Search" 2438 aria-label="Search" 2439 > 2440 <i class="ti ti-search text-xl"></i> 2441 </button> 2442 </form> 2443 </search> 2444 </div> </div> 2445
2446 <!--Cart Drawer--> 2447 2448<section x-cloak 2449 x-show="cart" 2450 id="cart-drawer" 2451 x-data="initCartDrawer()" 2452 @private-content-loaded.window="getData($event.detail.data)" 2453 @toggle-cart.window="toggleCartDrawer($event)" 2454 @keydown.escape="closeCartDrawer" 2455> 2456 <div role="dialog" 2457 aria-labelledby="cart-drawer-title" 2458 aria-modal="true" 2459 :aria-hidden="!open" 2460 class="fixed inset-y-0 right-0 z-50 flex max-w-full"> 2461 <div class="backdrop" 2462 x-show="open" 2463 x-transition:enter="ease-in-out duration-500" 2464 x-transition:enter-start="opacity-0" 2465 x-transition:enter-end="opacity-100" 2466 x-transition:leave="ease-in-out duration-500" 2467 x-transition:leave-start="opacity-100" 2468 x-transition:leave-end="opacity-0" 2469 role="button" 2470 @click="closeCartDrawer" 2471 aria-label="Close minicart"></div> 2472 <div class="relative w-screen max-w-[430px] shadow-2xl" 2473 x-show="open" 2474 x-transition:enter="transform transition ease-in-out duration-500 sm:duration-700" 2475 x-transition:enter-start="translate-x-full" 2476 x-transition:enter-end="translate-x-0" 2477 x-transition:leave="transform transition ease-in-out duration-500 sm:duration-700" 2478 x-transition:leave-start="translate-x-0" 2479 x-transition:leave-end="translate-x-full" 2480 x-ref="cartDialogContent" 2481 role="region" 2482 :tabindex="open ? 0 : -1" 2483 aria-label="Bag" 2484 2485 > 2486 <div class="flex flex-col h-full max-h-screen bg-white text-black shadow-xl"> 2487 2488 <header class="relative flex justify-between items-center px-5 sm:px-6 md:px-10 bg-white text-primary "> 2489 <div class="relative flex justify-between items-center w-full py-5 border-b border-container"> 2490 <h4 id="cart-drawer-title" class="flex items-center m-0"> 2491 Bag <span class="ml-2"> 2492 (<span x-text="itemsCount"></span>) 2493 </span> 2494 <!-- <span class="items-total text-xs" 2495 x-show="maxItemsToDisplay && maxItemsToDisplay < itemsCount" 2496 x-text="getItemCountTitle()"> 2497 </span> --> 2498 </h4> 2499 <button 2500 type="button" 2501 @click="closeCartDrawer" 2502 aria-label="Close minicart" 2503 class="transition-colors hover:text-primary leading-none" 2504 title="Close minicart" 2505 > 2506 <i class="ti ti-x text-xl"></i> 2507 </button> 2508 </div> 2509 </header> 2510 2511 2512 <template x-if="!itemsCount"> 2513 <div class="relative flex items-center justify-center px-10 py-6 bg-white border-bs sm:px-6 border-container"> 2514 You have no items in your bag. </div> 2515 </template> 2516 2517 <template x-if="itemsCount"> 2518 <div class="relative grid bg-white overflow-y-auto overscroll-y-contain px-5 sm:px-6 md:px-10"> 2519 <template x-for="item in cartItems"> 2520 <div class="item-wrapper flex items-start space-x-4 py-5 border-b border-container last:border-0"> 2521 <div class="flex items-start"> 2522 <template x-if="item.product_has_url && item.is_visible_in_site_visibility"> 2523 <a :href="item.product_url" 2524 class="w-1/4 !ml-0" 2525 :aria-label="hyva.strf('Product\u0020\u0022\u00250\u0022', item.product_name)" 2526 > 2527 <img class="w-full object-cover" 2528 :src="item.product_image.src" 2529 :width="item.product_image.width" 2530 :height="item.product_image.height" 2531 loading="lazy" 2532 :alt="item.product_name" 2533 /> 2534 </a> 2535 </template> 2536 <template x-if="!item.product_has_url || !item.is_visible_in_site_visibility"> 2537 <div class="w-1/4 !ml-0"> 2538 <img class="w-full object-cover" 2539 :src="item.product_image.src" 2540 :width="item.product_image.width" 2541 :height="item.product_image.height" 2542 loading="lazy" 2543 :alt="item.product_name" 2544 /> 2545 </div> 2546 </template> 2547 <div class="w-3/4"> 2548 <div> 2549 <p class="mb-0"><span x-html="item.product_sku"></span></p> 2550 <p>
2551 <span x-html="item.product_name"></span> 2552 </p> 2553 </div> 2554 <template x-for="option in item.options"> 2555 <div class="flex gap-1 mb-3"> 2556 <p class="mb-0" x-text="option.label + ':'"></p> 2557 <p class="mb-0" x-html="option.value"></p> 2558 </div> 2559 </template> 2560 <div class="flex items-center justify-between m-0 p-0"> 2561 <div class="flex items-center gap-2"> 2562 <div class="flex border border-primary rounded"> 2563 <button @click="decrementQty(item)" 2564 class="flex items-center justify-center w-[32px] h-[32px]" 2565 aria-label="Decrease Quantity"> 2566 <i class="ti ti-minus"></i> 2567 </button> 2568 <input x-model="item.qty" 2569 x-on:change="updateItemQty(item)" 2570 type="number" 2571 size="4" 2572 step="any" 2573 class="flex items-center justify-center border-primary border-t-0 border-b-0 w-[60px] h-[32px] text-center [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none [-moz-appearance:textfield]" 2574 min="1" 2575 data-role="cart-item-qty"> 2576 <button @click="incrementQty(item)" 2577 class="flex items-center justify-center w-[32px] h-[32px]" 2578 aria-label="Increase Quantity"> 2579 <i class="ti ti-plus"></i> 2580 </button> 2581 </div> 2582 <button type="button" 2583 class="flex p-[6px] h-[33px] btn btn-lighter" 2584 @click="deleteItemFromCart(item.item_id)" 2585 :aria-label="hyva.strf('Remove\u0020product\u0020\u0022\u00250\u0022\u0020from\u0020cart', item.product_name)" 2586 title="Remove product" 2587 > 2588 <i class="ti ti-trash text-lg"></i> 2589 </button> 2590 </div> 2591 <span x-html="hyva.formatPrice(item.product_price_value * item.qty)" class="text-sm"></span> 2592 </div> 2593 </div> 2594 </div> 2595 </div> 2596 </template> 2597 </div> 2598 </template> 2599 2600 <template x-if="itemsCount"> 2601 <div class="mt-auto pb-10 px-5 sm:px-6 md:px-10"> 2602 <div x-show="extraProducts.length > 0" class="flex flex-col gap-3 pt-5 border-t border-primary"> 2603 <h4 class="mb-0" x-text="extraProducts[0]?.title"></h4> 2604 <p class="mb-0 text-xs" x-text="extraProducts[0]?.description"></p> 2605 </div> 2606 <!-- Extra Products Section --> 2607 <template x-if="extraProducts.length > 0"> 2608 <div class="extra-products-section py-5"> 2609 <div 2610 x-ref="extraProductsSplide" 2611 class="extra-products-splide" 2612 :class="{ 'splide': extraProducts.length > 1 }" 2613 > 2614 <div class="splide__track"> 2615 <ul class="splide__list"> 2616 <template x-for="(product, index) in extraProducts" :key="product.id"> 2617 <li class="splide__slide"> 2618 <div class="extra-product-item flex gap-3"> 2619 <div class="flex-shrink-0 w-16"> 2620 <img class="w-full h-auto object-cover rounded" 2621 :src="product.image" 2622 :alt="product.name" 2623 loading="lazy" /> 2624 </div> 2625 <div class="flex-1 min-w-0"> 2626 <p class="mb-0" x-text="product.name"></p> 2627 <div class="flex justify-between"> 2628 <p class="mb-0" x-text="product.sku"></p> 2629 <sp
2629an class="text-sm" x-html="product.price"></span> 2630 </div> 2631 2632 <div class="flex gap-2 mt-2"> 2633 <div class="flex items-center justify-between"> 2634 <div class="flex items-center border rounded"> 2635 <button type="button" 2636 @click="decrementExtraQty(index)" 2637 class="flex items-center justify-center w-[32px] [34px]"> 2638 <span class="text-sm">â</span> 2639 </button> 2640 <input type="number" 2641 x-model.number="extraProductQtys[index]" min="1" max="10000" 2642 class="flex items-center justify-center border-primary border-t-0 border-b-0 w-[60px] h-[34px] text-center [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none [-moz-appearance:textfield]" /> 2643 <button type="button" 2644 @click="incrementExtraQty(index)" 2645 class="flex items-center justify-center w-[32px] h-[34px]"> 2646 <span class="">+</span> 2647 </button> 2648 </div> 2649 </div> 2650 <button type="button" 2651 @click="addExtraProduct(product, index)" 2652 class="btn btn-primary btn-size-xs w-full"> 2653 Add to Bag </button> 2654 </div> 2655 </div> 2656 </div> 2657 </li> 2658 </template> 2659 </ul> 2660 </div> 2661 </div> 2662 </div> 2663 </template> 2664 2665 <div class="relative grid bg-white"> 2666 <div class="relative flex justify-between items-center w-full py-5 bg-white text-primary border-t border-container"> 2667 <p class="flex items-center justify-between w-full mb-0">Subtotal: <span x-html="cart.subtotal"></span></p> 2668 </div> 2669 <div class="flex gap-3 w-full"> 2670 <a @click.prevent.stop="closeCartDrawer; $dispatch('toggle-authentication', 2671 {url: 'https://www.banham.co.uk/checkout/'});" 2672 href="https://www.banham.co.uk/checkout/" 2673 class="w-full btn btn-primary" 2674 > 2675 Checkout </a> 2676 <a href="https://www.banham.co.uk/checkout/cart/" 2677 class="w-full btn btn-light-gray" 2678 > 2679 View Bag </a> 2680 </div>
2681 <div x-html="getSectionDataExtraActions()"></div> 2682 </div> 2683 </div> 2684 </template> 2685 2686 </div> 2687 </div> 2688 <div 2689 class="z-50 fixed inset-0 flex justify-center items-center bg-white/70 select-none" 2690 x-show="isLoading" 2691 x-cloak 2692 x-transition.opacity.duration.200ms 2693> 2694 <div class="flex items-center"> 2695 <svg xmlns="http://www.w3.org/2000/svg" width="54" height="54" viewBox="0 0 24 24" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" class="animate-spin" aria-hidden="true"> 2696 <circle style="opacity: .3;" cx="12" cy="12" r="10"/> 2697 <path fill="none" stroke-width="2" d="M12,2 a10,10 0 0,1 10,10"/> 2698</svg> 2699 <div class="ml-10 text-xl"> 2700 Loading... </div> 2701 </div> 2702</div> </div> 2703 2704</section> 2705 <!--Authentication Pop-Up--> 2706 <!-- BLOCK authentication-popup --> 2707 2708<dialog 2709 id="authentication-popup" 2710 aria-labelledby="cart-drawer-title" 2711 class="open:flex flex-col transition ease-out duration-300 motion-reduce:duration-0 p-0" 2712 x-data="initAuthentication" 2713 x-bind="eventListeners" 2714 x-show="open" 2715 x-htmldialog="close" 2716 closeby="any" 2717 x-transition 2718> 2719 <div class="pt-8 px-8"> 2720 <div class="flex gap-2 justify-between items-center"> 2721 <p class="text-xl"> 2722 <strong>Customer Login</strong> 2723 </p> 2724 <button 2725 type="button" 2726 class="btn bg-transparent border-transparent p-2" 2727 @click="close" 2728 aria-label="Close panel" 2729 > 2730 <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12"/></svg> 2731 </button> 2732 </div> 2733 2734 <div class="message error" x-show="errors" x-cloak> 2735 <template x-for="(message, index) in errorMessages" :key="index"> 2736 <div> 2737 <template x-if="message"> 2738 <span x-html="message"></span> 2739 </template> 2740 </div> 2741 </template> 2742 </div> 2743 </div> 2744 2745 <div class="block-customer-login p-8"> 2746 <p id="authenticate-customer-login" class="text-lg mb-2"> 2747 <strong>Checkout using your account</strong> 2748 </p> 2749 2750 <form 2751 id="login-form" 2752 class="form form-login" 2753 method="post" 2754 @submit.prevent="submitForm" 2755 > 2756 <div class="control"> 2757 <input type="hidden" 2758 value="" 2759 name="g-recaptcha-response" 2760 > 2761</div> <input name="context" type="hidden" value="checkout"> 2762 2763 <div class="flex flex-col gap-4"> 2764 <div class="field email required"> 2765 <label for="form-login-username"> 2766 <span>Email Address</span> 2767 </label> 2768 <div class="control"> 2769 <input 2770 id="form-login-username" 2771 name="username" 2772 type="email" 2773 class="form-input w-full" 2774 required 2775 autocomplete="off" 2776 x-ref="customer-email" 2777 @change="resetErrors" 2778 > 2779 </div> 2780 </div> 2781 <div class="field password required"> 2782 <label for="form-login-password"> 2783 <span>Password</span> 2784 </label> 2785 <div class="control"> 2786 <input 2787 id="form-login-password" 2788 name="password" 2789 type="password" 2790 class="form-input w-full" 2791 required 2792 autocomplete="off" 2793 x-ref="customer-password" 2794 @change="resetErrors" 2795 > 2796 </div> 2797 </div> 2798 </div> 2799 2800 <div class="actions-toolbar"> 2801 <button 2802 type="submit" 2803 class="btn btn-primary" 2804 > 2805 Sign In </button> 2806 <a 2807 href="https://www.banham.co.uk/customer/account/forgotpassword/" 2808 class="underline hover:no-underline" 2809 > 2810 Forgot Your Password? </a> 2811 </div> 2812 </form> 2813 <p class="my-4 text-sm"> 2814 <em>This site is protected by reCAPTCHA.</em> 2815</p> </div> 2816 <div class="block-new-customer p-8 border-t border-gray-300">
2817 <p id="authenticate-new-customer" class="text-lg mb-2"> 2818 <strong>Checkout as a new customer</strong> 2819 </p> 2820 2821 <div class="block-content"> 2822 <p class="mb-1"> 2823 Creating an account has many benefits: </p> 2824 <ul class="list-disc pl-5"> 2825 <li> See order and shipping status</li> 2826 <li> Track order history</li> 2827 <li> Check out faster</li> 2828 </ul> 2829 <div class="actions-toolbar mt-6 mb-2"> 2830 <a href="https://www.banham.co.uk/customer/account/create/" 2831 class="inline-flex btn btn-primary"> 2832 Create an Account </a> 2833 </div> 2834 </div> 2835 </div> 2836</dialog><!-- /BLOCK authentication-popup --></div></header><div class="top-container"><!-- Action Name: /home --> 2837<nav class="breadcrumbs text-black border-b hidden" aria-label="Breadcrumb"> 2838 <div class="container"> 2839 <ol class="items list-reset py-5 flex flex-wrap text-black leading-tight"> 2840 <li class="item flex home"> 2841 <span class="text-sm leading-tight" aria-current="page"> 2842 Home </span> 2843 </li> 2844 </ol> 2845 </div> 2846</nav> 2847 2848 2849</div><main id="maincontent" class="page-main"><div id="contentarea" tabindex="-1"></div><div class="page messages"><!-- Action Name: /home --> 2850<section id="messages" x-data="initMessages" :class="sectionClass" x-bind="eventListeners" role="alert"> 2851 <template x-if="hasMessages"> 2852 <div class="w-full"> 2853 <div class="messages"> 2854 <template x-for="(message, index) in messages" :key="index"> 2855 <div class="flex item-center gap-2"> 2856 <template x-if="hasMessage"> 2857 <div class="message" 2858 :class="message.type" 2859 :ui-id="getMessageUiId" 2860 > 2861 <div class="container flex justify-between"> 2862 <span class="mx-auto text-white" x-html="message.text"></span> 2863 <button 2864 type="button" 2865 class="text-white hover:text-primary-lighter" 2866 aria-label="Close message" 2867 @click.prevent="removeMessage" 2868 > 2869 <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"> 2870 <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/> 2871</svg> 2872 </button> 2873 </div> 2874 </div> 2875 </template> 2876 </div> 2877 </template> 2878 </div> 2879 </div> 2880 </template> 2881</section></div><!-- action name: / --><div class="columns"><div class="column main"> <div id="monkey_campaign" style="display:none;" data-mage-init='{"campaigncatcher":{"checkCampaignUrl": "https://www.banham.co.uk/mailchimp/campaign/check/"}}'></div> <div> 2882<div class="hero-carousel hero-carousel-167baedb-527e-4e20-95f7-f8f91ddbb9c4"> 2883 <div class="splide-wrapper"> 2884 <div class="splide"> 2885 <div class="splide__track"> 2886 <div class="hero-slide splide__list"> 2887 <div class="hero-text-299bf8e1-0b4a-4f78-a5be-d01af54ed0bf flex w-full h-screen [&_figure]:h-full [&_img]:h-full splide__slide"> 2888 <div class="wrapper relative w-full [&_.video-controller]:!flex"> 2889 <div class="absolute inset-0 w-full h-full overflow-hidden bg-black"> 2890 <iframe 2891 id="vimeo-299bf8e1-0b4a-4f78-a5be-d01af54ed0bf" 2892 src="https://player.vimeo.com/video/1215733291?background=1&auto
2892play=1&loop=1&muted=1&transparent=0&api=1&player_id=vimeo-299bf8e1-0b4a-4f78-a5be-d01af54ed0bf" 2893 frameborder="0" 2894 allow="autoplay; fullscreen" 2895 allowfullscreen 2896 class="pointer-events-none block" 2897 style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:177.78vh;height:56.25vw;min-width:100%;min-height:100%;border:0;background:#000;" 2898 ></iframe> 2899 </div> 2900 <div class="bg-black absolute inset-0 pointer-events-none z-0" style="opacity: 0.3;"></div> 2901 <div class="video-controller hidden absolute right-10 bottom-10 z-100 gap-2 max-md:left-5 max-md:right-5 max-md:bottom-[30px] max-md:justify-between" 2902 x-data="{ 2903 isPlaying: true, 2904 isMuted: true, 2905 postMessage(method, value) { 2906 const player = document.getElementById('vimeo-299bf8e1-0b4a-4f78-a5be-d01af54ed0bf'); 2907 if (!player?.contentWindow) return; 2908 player.contentWindow.postMessage(JSON.stringify(value !== undefined ? { method, value } : { method }), '*'); 2909 }, 2910 togglePlay() { 2911 this.postMessage(this.isPlaying ? 'pause' : 'play'); 2912 this.isPlaying = !this.isPlaying; 2913 }, 2914 toggleMute() { 2915 this.isMuted = !this.isMuted; 2916 this.postMessage('setVolume', this.isMuted ? 0 : 1); 2917 } 2918 }"> 2919 <!-- Mute/Unmute Button --> 2920 <button 2921 type="button" 2922 class="w-9 h-9 px-2 py-1.5 bg-white/30 text-stone-950 rounded-[50px] backdrop-blur-[20px] inline-flex justify-center items-center hover:bg-white" 2923 @click="toggleMute()" 2924 > 2925 <svg x-show="!isMuted" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none"> 2926 <g clip-path="url(#clip0_1123_18554)"> 2927 <path fill-rule="evenodd" clip-rule="evenodd" d="M2 8H4L8 5V15L4 12H2V8Z" fill="black"/> 2928 <path d="M13 14C15.2091 11.7909 15.2091 8.20914 13 6" stroke="black" stroke-width="0.942809"/> 2929 <path d="M11 12C12.1046 10.8954 12.1046 9.10457 11 8" stroke="black" stroke-width="0.942809"/> 2930 <path d="M15 16C18.3137 12.6863 18.3137 7.31371 15 4" stroke="black" stroke-width="0.942809"/> 2931 </g> 2932 <defs> 2933 <clipPath id="clip0_1123_18554"> 2934 <rect width="20" height="20" fill="white"/> 2935 </clipPath> 2936 </defs> 2937 </svg> 2938 <svg x-show="isMuted" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none"> 2939 <path fill-rule="evenodd" clip-rule="evenodd" d="M2 8H4L8 5V15L4 12H2V8Z" fill="black"/> 2940 <path d="M11 7L17 13" stroke="black"/> 2941 <path d="M17 7L11 13" stroke="black"/> 2942 </svg> 2943 </button> 2944 <!-- Play/Pause Button --> 2945 <button 2946 type="button" 2947 class="w-9 h-9 px-2 py-1.5 bg-white/30 text-stone-950 rounded-[50px] backdrop-blur-[20px] inline-flex justify-center items-center hover:bg-white" 2948 @click="togglePlay()" 2949 > 2950 <i class="ti" :class="isPlaying ? ' ti-player-pause-filled' : ' ti-player-play-filled'"></i> 2951 </button> 2952 </div> 2953 2954 <div class="content absolute inset-0 z-5"> 2955 <div class="container flex flex-col gap-5 h-full py-24 md:py-[60px] lg:py-24 justify-end items-center text-center"> 2956 <h1 class="text-4xl lg:text-5xl text-white font-normal font-heading leading-10 lg:leading-[52.80px] tracking-wide mb-0 max-w-[310px] md:max-w-[466px] lg:max-w-[426px]">Protect What You Cherish</h1> 2957 2958 <p class="!text-base text-white max-w-[310px] md:max-w-[466px] lg:max-w-[426px] mb-2">To celebrate 100 years of Banham, we went behind closed doors to discover what you keep under lock and key. Meet Keith & Sue and their collection of 8,500 teapots.</p> 2959 2960<a class="btn btn-lighter default" href="/journal/keith-and-sue-teapot-collection" target="" aria-label="Read More" title="Read More"> 2961 <span>Read More</span> 2962</a> </div> 2963 </div> 2964 </div> 2965</div> </div> 2966 </div> 2967 </div> 2968 </div> 2969</div> 2970 2971 2972<style> 2973 .hero-text-f46eac5b-da6e-4951-b68e-72ebe8000369 { 2974 background-color: #ffffff; } 2975</style> 2976 2977<div class="hero-text-f46eac5b-da6e-4951-b68e-72ebe8000369 flex w-full top-padding-lg bottom-padding-lg"> 2978 <div class="hero-text container flex flex-col gap-[60px]"> 2979 <div class="wrapper relative [&_.video-controller]:!flex " style="background-color: ; "> 2980 <div class="content inset-0 z-5 flex flex-col gap-5 justify-center items-center text-center px-5 md:px-10 lg:px-20" style=""> 2981 <h1 class="text-4xl lg:text-5xl font-normal font-heading leading-10 lg:leading-[52.80px] tracking-[0.36px] lg:tracking-[0.48px] mb-0 max-w-[310px] md:max-w-[400px] lg:max-w-[426px]" style="color:#000;">Handcrafted in Britain Since 1926</h1> 2982 <p class="text-base max-w-[290px] md:max-w-[466px] lg:max-w-[426px] mb-2" style="color:#000;">Marrying innovation with time honoured techniques, every detail of every Banham product is made to last. </p> 2983 </div> 2984 </div> 2985 </div> 2986</div> 2987 2988<style> 2989 .image-columns-124654cd-adcc-44af-a35f-5fa10c06f697 { 2990 background-color: #ffffff; } 2991 .image-columns-124654cd-adcc-44af-a35f-5fa10c06f697 .image-grid { 2992 grid-template-columns: repeat(1, minmax(0, 1fr)); 2993 2994 @media screen and (min-width: 1024px) { 2995 grid-template-columns: repeat(2, minmax(0, 1fr)); 2996 } 2997 } 2998</style> 2999 3000<div class="image-columns-124654cd-adcc-44af-a35f-5fa10c06f697 flex top-padding-sm bottom-padding-md"> 3001 <div class="w-full flex flex-col gap-[60px]"> 3002 <div class="image-grid grid gap-[2px]"> 3003 <div class="image-column-card relative flex flex-col items-center [&_.img-wrapper]:relative [&_.img-wrapper]:after:content-[''] [&_.img-wrapper]:after:absolute [&_.img-wrapper]:after:top-0 [&_.img-wrapper]:after:w-full [&_.img-wrapper]:after:h-full [&_.img-wrapper]:after:bg-black [&_.img-wrapper]:after:opacity-20"> 3004 3005 3006 <figure class="w-full h-full" 3007 x-data="responsiveMedia('https://a.storyblok.com/f/286600090739628/678x1020/477258de5b/banham-handcrafted-locks.png','','','https://a.storyblok.com/f/286600090739628/678x1020/477258de5b/banham-handcrafted-locks.png','0')"> 3008 3009 3010 <div class="img-wrapper w-full"> 3011 <img 3012 :src="src" 3013 :data-src="src" 3014 alt="A gloved hand holds a Banham lock, handcrafted in the UK at the highest standards of craftsmanship." 3015 class="lazyload w-full h-auto object-cover object-center"> 3016 </div> 3017 3018 </figure> 3019 3020
3021 <div class="card-details absolute bottom-[60px] left-0 w-full h-full flex flex-col items-center justify-end text-center gap-4 z-30"> 3022 <h3 class="mb-0 text-white"> 3023 Signature Locks </h3> 3024 3025 <p class="max-w-[398px] !mb-0 text-primary-darker !tracking-normal text-white"> 3026 Meticulously crafted, step by step: before you turn the key, each of our locks passes through the hands of our expert makers. </p> 3027 3028 3029<a class="btn btn-lighter btn-size-md " href="/locks" target="" aria-label="Discover Locks" title="Discover Locks"> 3030 <span>Discover Locks</span> 3031</a> </div> 3032</div><div class="image-column-card relative flex flex-col items-center [&_.img-wrapper]:relative [&_.img-wrapper]:after:content-[''] [&_.img-wrapper]:after:absolute [&_.img-wrapper]:after:top-0 [&_.img-wrapper]:after:w-full [&_.img-wrapper]:after:h-full [&_.img-wrapper]:after:bg-black [&_.img-wrapper]:after:opacity-20"> 3033 3034 3035 <figure class="w-full h-full" 3036 x-data="responsiveMedia('https://a.storyblok.com/f/286600090739628/1143x1720/ec5ea5ecf8/banham-bespoke-doors.png','','','https://a.storyblok.com/f/286600090739628/1143x1720/ec5ea5ecf8/banham-bespoke-doors.png','0')"> 3037 3038 3039 <div class="img-wrapper w-full"> 3040 <img 3041 :src="src" 3042 :data-src="src" 3043 alt="A blue Banham bespoke door with intricate stained glass panels and a gold letterbox, partially open, surrounded by ornate floral decorations." 3044 class="lazyload w-full h-auto object-cover object-center"> 3045 </div> 3046 3047 </figure> 3048 3049 3050 <div class="card-details absolute bottom-[60px] left-0 w-full h-full flex flex-col items-center justify-end text-center gap-4 z-30"> 3051 <h3 class="mb-0 text-white"> 3052 Bespoke Doors </h3> 3053 3054 <p class="max-w-[398px] !mb-0 text-primary-darker !tracking-normal text-white"> 3055 Kerb appeal with a difference: our reinforced steel doors are handcrafted and tailor made for your home. </p> 3056 3057 3058<a class="btn btn-lighter btn-size-md " href="/doors" target="" aria-label="Discover Doors" title="Discover Doors"> 3059 <span>Discover Doors</span> 3060</a> </div> 3061</div> </div> 3062 </div> 3063</div> 3064 3065<style> 3066 .image-card-grid-1d86e92d-ab20-4142-b0cc-277bc945a160 { 3067 background-color: #FAFAFA; } 3068 3069 .image-card-grid-1d86e92d-ab20-4142-b0cc-277bc945a160 .image-grid { 3070 @media screen and (min-width: 1024px) { 3071 grid-template-columns: repeat(3, minmax(0, 1fr)) !important; 3072 } 3073 } 3074 3075</style> 3076 3077 3078 3079<div class="image-card-grid-1d86e92d-ab20-4142-b0cc-277bc945a160 top-padding-md bottom-padding-md"> 3080 <div class="container"> 3081 <div class="relative splide-wrapper flex flex-col gap-[60px] max-sm:-mx-5 max-lg:-mx-10"> 3082 <div class="flex flex-col gap-4 items-center justify-center text-center"> 3083 <h2 class="heading-3 text-center m-0"> 3084 Explore Banham </h2> 3085 </div> 3086 3087 <div class="!static flex flex-col splide"> 3088 <div class="splide__track"> 3089 <div class="image-grid lg:!grid lg:gap-10 splide__list"> 3090 <div class="image-card splide__slide"> 3091 <a class="flex flex-col gap-5" href="/keyholding" title="Keyholding"> 3092 <img src="https://a.storyblok.com/f/286600090739628/426x426/208a6378d0/banham-keyholding.png" alt="Banham Keyholding service for residencial and commercial properties in London."
3092class="lazyload w-full h-auto object-cover object-center"> 3093 <div class="card-details flex flex-col items-center text-center gap-4"> 3094 <h4 class="mb-0"> 3095 Keyholding </h4> 3096 <p class="!mb-0 text-primary-darker !tracking-normal px-5"> 3097 From misplaced passports to trusted tradesmen access, our concierge service is there for lifeâs unexpected moments. </p> 3098 <span class="underline-link pb-[3px]"> 3099 <span>Discover Keyholding</span> 3100 </span> 3101 </div> 3102 </a> 3103</div><div class="image-card splide__slide"> 3104 <a class="flex flex-col gap-5" href="burglar-alarms/" title="Alarms"> 3105 <img src="https://a.storyblok.com/f/286600090739628/426x426/b59785f203/banham_alarms.png" alt="Shield-shaped Banham logo featuring "Since 1926" and "London England" on a black background." class="lazyload w-full h-auto object-cover object-center"> 3106 <div class="card-details flex flex-col items-center text-center gap-4"> 3107 <h4 class="mb-0"> 3108 Alarms </h4> 3109 <p class="!mb-0 text-primary-darker !tracking-normal px-5"> 3110 Tailored alarm systems with 24 hour monitoring and rapid response, for complete peace of mind. </p> 3111 <span class="underline-link pb-[3px]"> 3112 <span>Discover Alarms</span> 3113 </span> 3114 </div> 3115 </a> 3116</div><div class="image-card splide__slide"> 3117 <a class="flex flex-col gap-5" href="/safes" title="Bespoke Safes"> 3118 <img src="https://a.storyblok.com/f/286600090739628/426x426/ae30e525ed/discover-banham-safes.png" alt="Banham bespoke luxury safes" class="lazyload w-full h-auto object-cover object-center"> 3119 <div class="card-details flex flex-col items-center text-center gap-4"> 3120 <h4 class="mb-0"> 3121 Bespoke Safes </h4> 3122 <p class="!mb-0 text-primary-darker !tracking-normal px-5"> 3123 From solid wood veneers to leather linings, our insurance-graded safes can be tailored to you in side and out. </p> 3124 <span class="underline-link pb-[3px]"> 3125 <span>Discover Safes</span> 3126 </span> 3127 </div> 3128 </a> 3129</div> </div> 3130 </div> 3131 </div> 3132 </div> 3133 <div class="block-btn flex items-center justify-center"> 3134 </div> 3135 </div> 3136</div> 3137 3138 3139 3140 3141<style> 3142 .split-wrapper-a1ffa33b-4c0b-4fb8-889c-0219aa1f0b55 { 3143 background-color: #FAFAFA; } 3144</style> 3145 3146<div class="split-wrapper split-wrapper-a1ffa33b-4c0b-4fb8-889c-0219aa1f0b55 flex top-padding-md bottom-padding-md"> 3147 <div class="container flex flex-col gap-[60px]"> 3148 <div class="split-grid grid gap-[60px] lg:gap-10"> 3149 3150<style> 3151 .split-card-d422af4d-69e6-4bca-b8c4-ecda07a6f7d8 { 3152 background: #333E48; 3153 3154 @media screen and (min-width: 1024px) { 3155 grid-template-columns: 4fr 3fr; } 3156 } 3157</style> 3158 3159<div id="theBanhamLockWinsProductOfTheYear" class="split-card-d422af4d-69e6-4bca-b8c4-ecda07a6f7d8 grid gap-0 [&_img]:bg-white [&_img]:h-full"> 3160 <div class="img-box flex [&_img]:aspect-[3/2] "> 3161 3162 <figure class="w-full h-full" 3163 x-data="responsiveMedia('https://a.storyblok.com/f/286600090739628/1120x800/cbe3f156e5/banham-best-locks-temporary.png','https://a.storyblok.com/f/286600090739628/1120x800/cbe3f156e5/banham-best-locks-temporary.png','https://a.storyblok.com/f/286600090739628/1120x800/cbe3f156e5/banham-best-locks-temporary.png','https://a.storyblok.com/f/286600090739628/1120x800/cbe3f156e5/banham-best-locks-temporary.png','0')"> 3164 3165 3166 <div class="img-wrapper w-full"> 3167 <img 3168 :src="src" 3169 :data-src="src" 3170 alt="" 3171 class="lazyload w-full h-auto object-cover object-center"> 3172 </div> 3173 3174 </figure> 3175 3176 3177 </div> 3178 <div class="split-details flex flex-col gap-4 justify-center items-center !pt-[60px] sm:px-[160px] lg:px-[120px] lg:!pt-10 text-center pt-5 sm:pt-10 lg:pb-10 px-10 pb-[60px] text-white "> 3179 <h3 class="mb-0 text-white"> 3180 The Banham Lock Wins Product of the Year </h3> 3181 3182 <div class="split-info color-white"> 3183 <p>We are proud to announce our signature locks have won Great British Brand's Best Product 2026.</p> </div> 3184 <div class="flex gap-4 items-center"> 3185 3186<a class="btn btn-lighter default" href="/journal/banham-wins-product-of-the-year" target="" aria-label="About Our Award" title="About Our Award"> 3187 <span>About Our Award</span> 3188</a> </div> 3189 </div> 3190</div> </div> 3191 </div> 3192</div> 3193 3194<style> 3195 .tabs-box-888e495f-a2a6-4e0c-b8ab-4d005bc23af8 { 3196 background-color: #ffffff; } 3197</style> 3198 3199<div class="tabs-box tabs-box-888e495f-a2a6-4e0c-b8ab-4d005bc23af8 flex justify-center top-padding-md bottom-padding-lg" x-data="{ activeTab: 'our-services' }" > 3200 <div class="tab-section flex flex-col gap-[60px] outline-none focus:outline-none overflow-hidden"> 3201 <div class="flex flex-col gap-4 items-center justify-center text-center"> 3202 <h3 class="heading-3 text-center m-0"> 3203 At Your Service </h3> 3204 </div> 3205 3206 <div class="tab-links container max-md:z-10 flex flex-col splide md:!visible"> 3207 <div class="splide__track"> 3208 <div class="md:!flex md:justify-center splide__list"> 3209 <div data-id="our-services" 3210 class="tab-btn cursor-pointer splide__slide two-tabs" 3211 :class="{ 'active': activeTab === 'our-services' }" 3212 @click="activeTab = 'our-services'" 3213 > 3214 Our Services </div> 3215 <div data-id="customer-help" 3216 class="tab-btn cursor-pointer splide__slide two-tabs" 3217 :class="{ 'active': activeTab === 'customer-help' }" 3218 @click="activeTab = 'customer-help'" 3219 > 3220 Customer Support </div> 3221 </div> 3222 </div> 3223 </div> 3224 3225 <div class="tab-contents relative overflow-hidden"> 3226 3227<div 3228 class="tab-content overflow-hidden" 3229 id="our-services" 3230 x-show="activeTab === 'our-services'" 3231 x-transition:enter="transition-opacity duration-300 ease-out" 3232 x-transition:enter-start="opacity-0" 3233 x-transition:enter-end="opacity-100" 3234 x-transition:leave="transition-opacity duration-200 ease-in absolute inset-0" 3235 x-transition:leave-start="opacity-100" 3236 x-transition:leave-end="opacity-0" 3237 x-cloak 3238> 3239 3240 3241<style> 3242 .image-card-grid-c56b845a-9bdb-4f24-a123-cabcd9cef9b0 { 3243 background-color: #ffffff; } 3244 3245 .image-card-grid-c56b845a-9bdb-4f24-a123-cabcd9cef9b0 .image-grid { 3246 @media screen and (min-width: 1024px) { 3247 grid-template-columns: repeat(4, minmax(0, 1fr)) !important; 3248 } 3249 } 3250 3251</style> 3252 3253 3254 3255<div class="image-card-grid-c56b845a-9bdb-4f24-a123-cabcd9cef9b0 top-padding-sm bottom-padding-sm"> 3256 <div class="container"> 3257 <div class="relative splide-wrapper flex flex-col gap-[60px] max-sm:-mx-5 max-lg:-mx-10"> 3258 3259 <div class="!static flex flex-col splide"> 3260 <div class="splide__track"> 3261 <div class="image-grid lg:!grid lg:gap-10 splide__list"> 3262 <div class="image-card splide__slide"> 3263 <a class="flex flex-col gap-5" href="/about-us" title="About Banham"> 3264 <img src="https://a.storyblok.com/f/286600090739628/310x465/efb40ca642/banham-1926-store.png" alt="About Banham" class="lazyload w-full h-auto object-cover object-center">
3265 <div class="card-details flex flex-col items-center text-center gap-4"> 3266 <h4 class="mb-0"> 3267 About Banham </h4> 3268 <span class="underline-link pb-[3px]"> 3269 <span>Learn More</span> 3270 </span> 3271 </div> 3272 </a> 3273</div><div class="image-card splide__slide"> 3274 <a class="flex flex-col gap-5" href="keyholding/" title="Keyholding Plans"> 3275 <img src="https://a.storyblok.com/f/286600090739628/310x465/dbf7058dba/banham-keyholding-service.png" alt="Banham keyholding service for commercial and residential properties" class="lazyload w-full h-auto object-cover object-center"> 3276 <div class="card-details flex flex-col items-center text-center gap-4"> 3277 <h4 class="mb-0"> 3278 Keyholding Plans </h4> 3279 <span class="underline-link pb-[3px]"> 3280 <span>Learn More</span> 3281 </span> 3282 </div> 3283 </a> 3284</div><div class="image-card splide__slide"> 3285 <a class="flex flex-col gap-5" href="/burglar-alarms/alarm-monitoring/" title="24 Hour Alarm Monitoring"> 3286 <img src="https://a.storyblok.com/f/286600090739628/310x465/e94c658cd3/banham-alarm-monitoring-service.png" alt="24 Hour Alarm Monitoring" class="lazyload w-full h-auto object-cover object-center"> 3287 <div class="card-details flex flex-col items-center text-center gap-4"> 3288 <h4 class="mb-0"> 3289 24 Hour Alarm Monitoring </h4> 3290 <span class="underline-link pb-[3px]"> 3291 <span>Learn More</span> 3292 </span> 3293 </div> 3294 </a> 3295</div><div class="image-card splide__slide"> 3296 <a class="flex flex-col gap-5" href="/doors/ " title="Bespoke Solutions"> 3297 <img src="https://a.storyblok.com/f/286600090739628/310x465/7bb24af69a/installation.png" alt="Bespoke Solutions" class="lazyload w-full h-auto object-cover object-center"> 3298 <div class="card-details flex flex-col items-center text-center gap-4"> 3299 <h4 class="mb-0"> 3300 Bespoke Solutions </h4> 3301 <span class="underline-link pb-[3px]"> 3302 <span>Learn More</span> 3303 </span> 3304 </div> 3305 </a> 3306</div> </div> 3307 </div> 3308 </div> 3309 </div> 3310 <div class="block-btn flex items-center justify-center"> 3311 </div> 3312 </div> 3313</div> 3314 3315 3316</div> 3317<div 3318 class="tab-content overflow-hidden" 3319 id="customer-help" 3320 x-show="activeTab === 'customer-help'" 3321 x-transition:enter="transition-opacity duration-300 ease-out" 3322 x-transition:enter-start="opacity-0" 3323 x-transition:enter-end="opacity-100" 3324 x-transition:leave="transition-opacity duration-200 ease-in absolute inset-0" 3325 x-transition:leave-start="opacity-100" 3326 x-transition:leave-end="opacity-0" 3327 x-cloak 3328> 3329 3330 3331<style> 3332 .image-card-grid-101c06c1-0443-420c-ace6-4f1b76dbf01e { 3333 background-color: #ffffff; } 3334 3335 .image-card-grid-101c06c1-0443-420c-ace6-4f1b76dbf01e .image-grid { 3336 @media screen and (min-width: 1024px) { 3337 grid-template-columns: repeat(4, minmax(0, 1fr)) !important; 3338 } 3339 } 3340 3341</style> 3342 3343 3344 3345<div class="image-card-grid-101c06c1-0443-420c-ace6-4f1b76dbf01e top-padding-sm bottom-padding-sm"> 3346 <div class="container"> 3347 <div class="relative splide-wrapper flex flex-col gap-[60px] max-sm:-mx-5 max-lg:-mx-10"> 3348 3349 <div class="!static flex flex-col splide"> 3350 <div class="splide__track"> 3351 <div class="image-grid lg:!grid lg:gap-10 splide__list"> 3352 <div class="image-card splide__slide">
3353 <a class="flex flex-col gap-5" href="moving-home/" title="Moving Home"> 3354 <img src="https://a.storyblok.com/f/286600090739628/310x465/778fecc9b0/banham-moving-home-service.png" alt="Black and white image of a brick townhouse facade with ornate wrought iron balcony railing and detailed architectural moldings." class="lazyload w-full h-auto object-cover object-center"> 3355 <div class="card-details flex flex-col items-center text-center gap-4"> 3356 <h4 class="mb-0"> 3357 Moving Home </h4> 3358 <span class="underline-link pb-[3px]"> 3359 <span>Learn More</span> 3360 </span> 3361 </div> 3362 </a> 3363</div><div class="image-card splide__slide"> 3364 <a class="flex flex-col gap-5" href="key-cutting-key-registration/" title="Key Cutting "> 3365 <img src="https://a.storyblok.com/f/286600090739628/310x465/88ce71a5ad/banham-key-cutting.png" alt="Close-up of a Banham key being cut on a precision key-cutting machine, with metal shavings and grooves visible." class="lazyload w-full h-auto object-cover object-center"> 3366 <div class="card-details flex flex-col items-center text-center gap-4"> 3367 <h4 class="mb-0"> 3368 Key Cutting </h4> 3369 <span class="underline-link pb-[3px]"> 3370 <span>Learn More</span> 3371 </span> 3372 </div> 3373 </a> 3374</div><div class="image-card splide__slide"> 3375 <a class="flex flex-col gap-5" href="customer-help-centre" title="Help Centre"> 3376 <img src="https://a.storyblok.com/f/286600090739628/310x465/63aef67a40/banham-security.png" alt="Help Centre" class="lazyload w-full h-auto object-cover object-center"> 3377 <div class="card-details flex flex-col items-center text-center gap-4"> 3378 <h4 class="mb-0"> 3379 Help Centre </h4> 3380 <span class="underline-link pb-[3px]"> 3381 <span>Learn More</span> 3382 </span> 3383 </div> 3384 </a> 3385</div><div class="image-card splide__slide"> 3386 <a class="flex flex-col gap-5" href="contact/" title="Contact Us"> 3387 <img src="https://a.storyblok.com/f/286600090739628/310x465/9fd54da42a/banham-alarm-monitoring.png" alt="Contact Us" class="lazyload w-full h-auto object-cover object-center"> 3388 <div class="card-details flex flex-col items-center text-center gap-4"> 3389 <h4 class="mb-0"> 3390 Contact Us </h4> 3391 <span class="underline-link pb-[3px]"> 3392 <span>Learn More</span> 3393 </span> 3394 </div> 3395 </a> 3396</div> </div> 3397 </div> 3398 </div> 3399 </div> 3400 <div class="block-btn flex items-center justify-center"> 3401 </div> 3402 </div> 3403</div> 3404 3405 3406</div> </div> 3407 </div> 3408</div> 3409 3410 3411<div class="hero-carousel hero-carousel-f984a7ff-5d45-49c3-9473-9cca511cfa65"> 3412 <div class="splide-wrapper"> 3413 <div class="splide"> 3414 <div class="splide__track"> 3415 <div class="hero-slide splide__list"> 3416 <div class="hero-text-9a04c72a-376c-48ac-9d25-7f6a554be009 flex w-full h-screen [&_figure]:h-full [&_img]:h-full splide__slide"> 3417 <div class="wrapper relative w-full [&_.video-controller]:!flex"> 3418 <div class="absolute inset-0 w-full h-full overflow-hidden bg-black"> 3419 <iframe 3420 id="vimeo-9a04c72a-376c-48ac-9d25-7f6a554be009" 3421 src="https://player.vimeo.com/video/1215113358?background=1&autoplay=1&loop=1&muted=1&transparent=0&api=1&player_id=vimeo-9a04c72a-376c-48ac-9d25-7f6a554be009" 3422 frameborder="0" 3423 allow="autoplay; fullscreen" 3424 allowfullscreen 3425 class="pointer-events-none block" 3426 style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:177.78vh;height:56.25vw;min-width:100%;min-height:100%;border:0;background:#000;" 3427 ></iframe> 3428 </div> 3429 <div class="bg-black absolute inset-0 pointer-events-none z-0" style="opacity: 0.3;"></div> 3430 <div class="video-controller hidden absolute right-10 bottom-10 z-100 gap-2 max-md:left-5 max-md:right-5 max-md:bottom-[30px] max-md:justify-between" 3431 x-data="{ 3432 isPlaying: true, 3433 isMuted: true, 3434 postMessage(method, value) { 3435 const player = document.getElementById('vimeo-9a04c72a-376c-48ac-9d25-7f6a554be009'); 3436 if (!player?.contentWindow) return; 3437 player.contentWindow.postMessage(JSON.stringify(value !== undefined ? { method, value } : { method }), '*'); 3438 }, 3439 togglePlay() { 3440 this.postMessage(this.isPlaying ? 'pause' : 'play'); 3441 this.isPlaying = !this.isPlaying; 3442 }, 3443 toggleMute() { 3444 this.isMuted = !this.isMuted; 3445 this.postMessage('setVolume', this.isMuted ? 0 : 1); 3446 } 3447 }"> 3448 <!-- Mute/Unmute Button --> 3449 <button 3450 type="button" 3451 class="w-9 h-9 px-2 py-1.5 bg-white/30 text-stone-950 rounded-[50px] backdrop-blur-[20px] inline-flex justify-center items-center hover:bg-white" 3452 @click="toggleMute()" 3453 >
3454 <svg x-show="!isMuted" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none"> 3455 <g clip-path="url(#clip0_1123_18554)"> 3456 <path fill-rule="evenodd" clip-rule="evenodd" d="M2 8H4L8 5V15L4 12H2V8Z" fill="black"/> 3457 <path d="M13 14C15.2091 11.7909 15.2091 8.20914 13 6" stroke="black" stroke-width="0.942809"/> 3458 <path d="M11 12C12.1046 10.8954 12.1046 9.10457 11 8" stroke="black" stroke-width="0.942809"/> 3459 <path d="M15 16C18.3137 12.6863 18.3137 7.31371 15 4" stroke="black" stroke-width="0.942809"/> 3460 </g> 3461 <defs> 3462 <clipPath id="clip0_1123_18554"> 3463 <rect width="20" height="20" fill="white"/> 3464 </clipPath> 3465 </defs> 3466 </svg> 3467 <svg x-show="isMuted" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none"> 3468 <path fill-rule="evenodd" clip-rule="evenodd" d="M2 8H4L8 5V15L4 12H2V8Z" fill="black"/> 3469 <path d="M11 7L17 13" stroke="black"/> 3470 <path d="M17 7L11 13" stroke="black"/> 3471 </svg> 3472 </button> 3473 <!-- Play/Pause Button --> 3474 <button 3475 type="button" 3476 class="w-9 h-9 px-2 py-1.5 bg-white/30 text-stone-950 rounded-[50px] backdrop-blur-[20px] inline-flex justify-center items-center hover:bg-white" 3477 @click="togglePlay()" 3478 > 3479 <i class="ti" :class="isPlaying ? ' ti-player-pause-filled' : ' ti-player-play-filled'"></i> 3480 </button> 3481 </div> 3482 3483 <div class="content absolute inset-0 z-5"> 3484 <div class="container flex flex-col gap-5 h-full py-24 md:py-[60px] lg:py-24 justify-end items-center text-center"> 3485 <h1 class="text-4xl lg:text-5xl text-white font-normal font-heading leading-10 lg:leading-[52.80px] tracking-wide mb-0 max-w-[310px] md:max-w-[466px] lg:max-w-[426px]">The Spice Girls Hall of Fame</h1> 3486 3487 <p class="!text-base text-white max-w-[310px] md:max-w-[466px] lg:max-w-[426px] mb-2">Meet Liz West and her record-breaking collection. 3488</p> 3489 3490<a class="btn btn-lighter default" href="/journal/liz-west-spice-girls-collection" target="" aria-label="Read More" title="Read More"> 3491 <span>Read More</span> 3492</a> </div> 3493 </div> 3494 </div> 3495</div> </div> 3496 </div> 3497 </div> 3498 </div> 3499</div> 3500 3501 3502 3503 3504<style> 3505 .split-wrapper-208209ca-f5e7-4ca0-bd24-6ff9a8a9d47e { 3506 background-color: #FAFAFA; } 3507</style> 3508 3509<div class="split-wrapper split-wrapper-208209ca-f5e7-4ca0-bd24-6ff9a8a9d47e flex top-padding-md bottom-padding-md"> 3510 <div class="container flex flex-col gap-[60px]"> 3511 <div class="split-grid grid gap-[60px] lg:gap-10"> 3512 3513<style> 3514 .split-card-6816fe3b-3871-4e66-8045-6ff0e67208d9 { 3515 background: #333E48; 3516 3517 @media screen and (min-width: 1024px) { 3518 grid-template-columns: 3fr 4fr; } 3519 } 3520</style> 3521 3522<div id="getInTouch" class="split-card-6816fe3b-3871-4e66-8045-6ff0e67208d9 grid gap-0 [&_img]:bg-white [&_img]:h-full"> 3523 <div class="img-box flex [&_img]:aspect-[3/2] "> 3524 3525 <figure class="w-full h-full" 3526 x-data="responsiveMedia('https://a.storyblok.com/f/286600090739628/777x520/ecd6fe3594/banham-survey-service.png','https://a.storyblok.com/f/286600090739628/777x520/ecd6fe3594/banham-survey-service.png','https://a.storyblok.com/f/286600090739628/777x520/ecd6fe3594/banham-survey-service.png','https://a.storyblok.com/f/286600090739628/777x520/ecd6fe3594/banham-survey-service.png','0')"> 3527 3528 3529 <div class="img-wrapper w-full"> 3530 <img 3531 :src="src" 3532 :data-src="src" 3533 alt="" 3534 class="lazyload w-full h-auto object-cover object-center"> 3535 </div> 3536 3537 </figure> 3538 3539 3540 </div> 3541 <div class="split-details flex flex-col gap-4 justify-center items-center !pt-[60px] sm:px-[160px] lg:px-[120px] lg:!pt-10 text-center pt-5 sm:pt-10 lg:pb-10 px-10 pb-[60px] text-white lg:order-[-1]"> 3542 <h3 class="mb-0 text-white"> 3543 Get in Touch </h3> 3544 3545 <div class="split-info color-white"> 3546 <p>Request a free security consultation today and our experts will help you design the best way to protect your home.</p> </div> 3547 <div class="flex gap-4 items-center"> 3548 3549<a class="underline-link white-color" href="#open_enquiry_form" target="" aria-label="Enquire Now" title="Enquire Now"> 3550 <span>Enquire Now</span> 3551</a> </div> 3552 </div> 3553</div> </div> 3554 </div> 3555</div> 3556 3557<style> 3558 .image-card-grid-5266d2bf-33d3-4ffc-9f52-7e13d43c1af9 { 3559 background-color: #FAFAFA; } 3560 3561 .image-card-grid-5266d2bf-33d3-4ffc-9f52-7e13d43c1af9 .image-grid { 3562 @media screen and (min-width: 1024px) { 3563 grid-template-columns: repeat(3, minmax(0, 1fr)) !important; 3564 } 3565 } 3566 3567</style> 3568 3569 3570 3571<div class="image-card-grid-5266d2bf-33d3-4ffc-9f52-7e13d43c1af9 top-padding-md bottom-padding-md"> 3572 <div class="container"> 3573 <div class="relative splide-wrapper flex flex-col gap-[60px] max-sm:-mx-5 max-lg:-mx-10"> 3574 <div class="flex flex-col gap-4 items-center justify-center text-center"> 3575 <h2 class="heading-3 text-center m-0"> 3576 Banham Journal </h2> 3577 </div> 3578 3579 <div class="!static flex flex-col splide"> 3580 <div class="splide__track"> 3581 <div class="image-grid lg:!grid lg:gap-10 splide__list"> 3582 <div class="image-card splide__slide"> 3583 <a class="flex flex-col gap-5" href="/journal/keith-and-sue-teapot-collection" title="Protect What You Cherish: 8,500 Teapots, 1 Perfect Cuppa"> 3584 <img src="https://a.storyblok.com/f/286600090739628/955x519/ed128a8428/protect-what-you-cherish-banham-teap
3584ots-small.png" alt="Protect What You Cherish: 8,500 Teapots, 1 Perfect Cuppa" class="lazyload w-full h-auto object-cover object-center"> 3585 <div class="card-details flex flex-col items-center text-center gap-4"> 3586 <h4 class="mb-0"> 3587 Protect What You Cherish: 8,500 Teapots, 1 Perfect Cuppa </h4> 3588 <span class="underline-link pb-[3px]"> 3589 <span>Read More</span> 3590 </span> 3591 </div> 3592 </a> 3593</div><div class="image-card splide__slide"> 3594 <a class="flex flex-col gap-5" href="journal/how-to-choose-your-banham-front-door" title="How to Choose Your Banham Front Door"> 3595 <img src="https://a.storyblok.com/f/286600090739628/955x519/07319e1f60/front-door-banham-thumbnail.png" alt="Blue Banham door with ornate stained glass panels, golden hardware, next to a lush arrangement of vibrant flowers and greenery." class="lazyload w-full h-auto object-cover object-center"> 3596 <div class="card-details flex flex-col items-center text-center gap-4"> 3597 <h4 class="mb-0"> 3598 How to Choose Your Banham Front Door </h4> 3599 <span class="underline-link pb-[3px]"> 3600 <span>Read More</span> 3601 </span> 3602 </div> 3603 </a> 3604</div><div class="image-card splide__slide"> 3605 <a class="flex flex-col gap-5" href="/journal/protect-what-you-cherish-the-joy-of-collecting" title="Protect What You Cherish: The Joy of Collecting"> 3606 <img src="https://a.storyblok.com/f/286600090739628/955x519/baefb411c1/protect-what-you-cherish-banham-collectors-thumbnail.png" alt="Protect What You Cherish: The Joy of Collecting" class="lazyload w-full h-auto object-cover object-center"> 3607 <div class="card-details flex flex-col items-center text-center gap-4"> 3608 <h4 class="mb-0"> 3609 Protect What You Cherish: The Joy of Collecting </h4> 3610 <span class="underline-link pb-[3px]"> 3611 <span>Read More</span> 3612 </span> 3613 </div> 3614 </a> 3615</div> </div> 3616 </div> 3617 </div> 3618 </div> 3619 <div class="block-btn flex items-center justify-center"> 3620 3621<a class="btn btn-primary default" href="/journal" target="" aria-label="Read More" title="Read More"> 3622 <span>Read More</span> 3623</a> </div> 3624 </div> 3625</div> 3626 3627 3628 3629 3630 3631 3632</div> </div></div></main><footer class="footer-container"><style> 3633 .pre-footer { 3634 background-color: #ffffff; } 3635</style> 3636<div class="footer-wrapper" role="contentinfo"> 3637 <div class="pre-footer text-white text-center splide top-padding-lg bottom-padding-lg"> 3638 <div class="container mx-auto splide__track"> 3639 <div class="splide__list md:!flex md:justify-around"> 3640 <a href="/locksmith-service/" class="splide__slide lg:w-1/3 no-underline text-sm max-md:block max-md:mb-1"> 3641 <h4 class="mb-4">Emergency Locksmiths</h4> 3642 <p class="mb-4">24/7 locksmith support when you need it most.</p> 3643 <span class="relative inline-block pb-[3px] after:content-[''] after:absolute after:left-0 after:bottom-0 after:h-[1px] after:w-full after:bg-current after:transition-all after:duration-300 after:ease-out hover:after:w-0"><span>Learn More</span></span> 3644 </a> 3645 3646 <a href="/locks" class="splide__slide lg:w-1/3 no-underline text-sm max-md:block max-md:mb-1"> 3647 <h4 class="mb-4">Insurance Approved</h4> 3648 <p class="mb-4">Our locks are certified to meet the highest security standards.</p>
3649 <span class="relative inline-block pb-[3px] after:content-[''] after:absolute after:left-0 after:bottom-0 after:h-[1px] after:w-full after:bg-current after:transition-all after:duration-300 after:ease-out hover:after:w-0"><span>Learn More</span></span> 3650 </a> 3651 3652 <a href="/about-us" class="splide__slide lg:w-1/3 no-underline text-sm max-md:block max-md:mb-1"> 3653 <h4 class="mb-4">Trusted Since 1926</h4> 3654 <p class="mb-4">100 years of British security expertise.</p> 3655 <span class="relative inline-block pb-[3px] after:content-[''] after:absolute after:left-0 after:bottom-0 after:h-[1px] after:w-full after:bg-current after:transition-all after:duration-300 after:ease-out hover:after:w-0"><span>Learn More</span></span> 3656 </a> 3657 3658 </div> 3659 </div> 3660 </div> 3661 <div class="page-footer bg-[#333E48] bg-gradient-to-b from-black/0 to-black/20 text-white [&_*]:text-white"> 3662 <div class="footer content container max-md:max-w-[490px] mx-auto grid grid-cols-1 md:grid-cols-[2fr_1fr_1fr] py-[60px] justify-between" aria-label="Footer navigation"> 3663 <div class="flex flex-col md:max-w-[427px]"> 3664 <div class="footer-newsletter"> 3665 <p class="heading-2 max-md:text-center">Get Exclusive Updates,<br/>News, and Advice</p> <div class="footer-newsletter"> 3666 <form 3667 class="form subscribe" 3668 action="https://www.banham.co.uk/newsletter/subscriber/new/" 3669 method="post" 3670 x-data="initNewsletterForm" 3671 @submit.prevent="submitForm" 3672 id="newsletter-validate-detail" 3673 aria-label="Subscribe to Newsletter" 3674 > 3675 <div class="flex border-b mb-10"> 3676 <input 3677 name="email" 3678 type="email" 3679 required 3680 id="newsletter-subscribe" 3681 class="form-input inline-flex w-full text-white bg-transparent border-0 placeholder:text-white/50 p-0" 3682 placeholder="Email Address" 3683 autocomplete="email" 3684 aria-describedby="footer-newsletter-heading" 3685 > 3686 <input name="form_key" type="hidden" value="9ScZqxAtVshasw9c" /> <button class="inline-flex items-center text-sm leading-5 hover:text-white/75 focus:outline-none focus:text-white transition ease-in-out duration-150 font-medium" title="Subscribe"> 3687 Subscribe </button> 3688 </div> 3689 <div> 3690 <template x-if="displayErrorMessage"> 3691 <p class="flex items-center text-red"> 3692 <span class="inline-block w-8 h-8 mr-3"> 3693 <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"> 3694 <path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/> 3695</svg> 3696 </span> 3697 <template x-for="errorMessage in errorMessages"> 3698 <span x-html="errorMessage"></span> 3699 </template> 3700 </p> 3701 </template> 3702 </div> 3703 </form> 3704 <div class="w-full"> 3705 </div> 3706 3707 </div> <p class="text-sm [&_a]:underline mb-[60px] max-md:text-center max-md:max-w-96 max-md:mx-auto">If you would like further information on how we use your personal data, please check our <a href="/privacy-promise">Privacy Policy</a>.</p> </div> 3708 <ul class="flex gap-5 max-md:justify-center max-md:mb-[60px]"> 3709 <li> 3710 <a href="https://www.facebook.com/Banh
3710am.Group/" 3711 class="flex items-center justify-center w-9 h-9 rounded-full bg-white/20 hover:bg-white [&:hover>i]:text-primary-darker transition-all duration-300" 3712 target="_blank" 3713 aria-label="brand-facebook" 3714 rel="noopener noreferrer" 3715 > 3716 <i class="ti ti-brand-facebook text-xl"></i> 3717 </a> 3718 </li> 3719 <li> 3720 <a href="https://www.instagram.com/banhamgroup/" 3721 class="flex items-center justify-center w-9 h-9 rounded-full bg-white/20 hover:bg-white [&:hover>i]:text-primary-darker transition-all duration-300" 3722 target="_blank" 3723 aria-label="brand-instagram" 3724 rel="noopener noreferrer" 3725 > 3726 <i class="ti ti-brand-instagram text-xl"></i> 3727 </a> 3728 </li> 3729 <li> 3730 <a href="https://x.com/banhamsecurity" 3731 class="flex items-center justify-center w-9 h-9 rounded-full bg-white/20 hover:bg-white [&:hover>i]:text-primary-darker transition-all duration-300" 3732 target="_blank" 3733 aria-label="brand-x" 3734 rel="noopener noreferrer" 3735 > 3736 <i class="ti ti-brand-x text-xl"></i> 3737 </a> 3738 </li> 3739 <li> 3740 <a href="https://uk.pinterest.com/banhamgroup/" 3741 class="flex items-center justify-center w-9 h-9 rounded-full bg-white/20 hover:bg-white [&:hover>i]:text-primary-darker transition-all duration-300" 3742 target="_blank" 3743 aria-label="brand-pinterest" 3744 rel="noopener noreferrer" 3745 > 3746 <i class="ti ti-brand-pinterest text-xl"></i> 3747 </a> 3748 </li> 3749 </ul> 3750 <span class="text-5xl mt-auto max-md:hidden">Trusted,<br/>Since 1926</span> </div> 3751 3752 <div class="footer-middle-col flex flex-col justify-between md:gap-14 gap-5 max-md:mb-5" x-data="{}"> 3753 <div x-data="{ index: 0 }"> 3754 <h3 class="text-[10px] uppercase leading-4 max-md:mb-0 max-md:flex items-center justify-between font-grotesk tracking-wide" 3755 @click="$store.footerResponsive.isMobile && $store.footerResponsive.toggle(index)"> 3756 Banham <i class="ti ti-chevron-down inline-block ml-auto text-sm md:hidden transition-transform duration-300" 3757 :class="$store.footerResponsive.isOpen(index) ? 'rotate-180' : 'rotate-0'"></i> 3758 </h3> 3759 3760 <ul class="flex flex-col gap-2 max-md:[&>li:first-child]:mt-3 mb-5 md:mb-0" 3761 x-show="!$store.footerResponsive.isMobile || $store.footerResponsive.isOpen(index)" 3762 x-collapse> 3763 <li> 3764 <a href="/about-us/" aria-label="About Us" 3765 class="inline-block text-sm font-normal leading-tight hover:underline"> 3766 About Us </a> 3767 </li> 3768 <li> 3769 <a href="/showrooms/" aria-label="Showrooms" 3770 class="inline-block text-sm font-normal leading-tight hover:underline"> 3771 Showrooms </a> 3772 </li> 3773 <li> 3774 <a href="/key-cutting-key-registration/" aria-label="Key Registration" 3775 class="inline-block text-sm font-normal leading-tight hover:underline"> 3776 Key Registration </a> 3777 </li> 3778 <li> 3779 <a href="https://www.banham.co.uk/trade/trade-area/" aria-label="Trade Buy Online" 3780 class="inline-block text-sm font-normal leading-tight hover:underline"> 3781 Trade Buy Online </a> 3782 </li> 3783 <li> 3784 <a href="/banham-payment/customer/view/" aria-label="Online Payment" 3785 class="inline-block text-sm font-normal leading-tight hover:underline"> 3786 Online Payment </a> 3787 </li> 3788 <li> 3789 <a href="/moving-home/" aria-label="Moving Home" 3790 class="inline-block text-sm font-normal leading-tight hover:underline"> 3791 Moving Home </a> 3792 </li> 3793 <li> 3794 <a href="/patents/" aria-label="Patents" 3795 class="inline-block text-sm font-normal leading-tight hover:underline"> 3796 Patents </a> 3797 </li> 3798 <li> 3799 <a href="/deliveries-returns/" aria-label="Deliveries and Returns" 3800 class="inline-block text-sm font-normal leading-tight hover:underline"> 3801 Deliveries and Returns </a> 3802 </li> 3803 <li> 3804 <a href="/commercial" aria-label="Banham Commercial" 3805 class="inline-block text-sm font-normal leading-tight hover:underline"> 3806 Banham Commercial </a> 3807 </li> 3808 <li> 3809 <a href="/book-a-survey/" aria-label="Book a Free Survey" 3810 class="inline-block text-sm font-normal leading-tight hover:underline"> 3811 Book a Free Survey </a> 3812 </li> 3813 </ul> 3814 </div> 3815 <div x-data="{ index: 1 }"> 3816 <h3 class="text-[10px] uppercase leading-4 max-md:mb-0 max-md:flex items-center justify-between font-grotesk tracking-wide" 3817 @click="$store.footerResponsive.isMobile && $store.footerResponsive.toggle(index)"> 3818 Customer Support <i class="ti ti-chevron-down inline-block ml-auto text-sm md:hidden transition-transform duration-300" 3819 :class="$store.footerResponsive.isOpen(index) ? 'rotate-180' : 'rotate-0'"></i> 3820 </h3> 3821 3822 <ul class="flex flex-col gap-2 max-md:[&>li:first-child]:mt-3 mb-5 md:mb-0" 3823 x-show="!$store.footerResponsive.isMobile || $store.footerResponsive.isOpen(index)" 3824 x-collapse> 3825 <li> 3826 <a href="/customer-help-centre/" aria-label="Support Centre" 3827 class="inline-block text-sm font-normal leading-tight hover:underline"> 3828 Support Centre </a> 3829 </li> 3830 <li> 3831 <a href="/contact/" aria-label="Contact Us" 3832 class="inline-block text-sm font-normal leading-tight hover:underline"> 3833 Contact Us </a> 3834 </li> 3835 <li> 3836 <a href="/update-your-details/" aria-label="Update Your Details" 3837 class="inline-block text-sm font-normal leading-tight hover:underline"> 3838 Update Your Details </a> 3839 </li> 3840 <li> 3841 <a href="/pstn/" aria-label="Digital Phone Switchover" 3842 class="inline-block text-sm font-normal leading-tight hover:underline"> 3843 Digital Phone Switchover </a> 3844 </li> 3845 <li> 3846 <a href="/complaints/" aria-label="Complaints Procedure" 3847 class="inline-block text-sm font-normal leading-tight hover:underline"> 3848 Complaints Procedure </a> 3849 </li> 3850 <li> 3851 <a href="/annual-contract-details/" aria-label="Annual Contracts Details" 3852 class="inline-block text-sm font-normal leading-tight hover:underline"> 3853 Annual Contracts Details </a> 3854 </li> 3855 <li> 3856 <a href="/direct-debits/" aria-label="Direct Debit Details" 3857 class="inline-block text-sm font-normal leading-tight hover:underline"> 3858 Direct Debit Details </a> 3859 </li> 3860 </ul> 3861 </div> 3862 <div x-data="{ index: 2 }"> 3863 <h3 class="text-[10px] uppercase leading-4 max-md:mb-0 max-md:flex items-center justify-between font-grotesk tracking-wide" 3864 @click="$store.footerResponsive.isMobile && $store.footerResponsive.toggle(index)"> 3865 Whatâs New <i class="ti ti-chevron-down inline-block ml-auto text-sm md:hidden transition-transform duration-300" 3866 :class="$store.footerResponsive.isOpen(index) ? 'rotate-180' : 'rotate-0'"></i> 3867 </h3> 3868 3869 <ul class="flex flex-col gap-2 max-md:[&>li:first-child]:mt-3 mb-5 md:mb-0" 3870 x-show="!$store.footerResponsive.isMobile || $store.footerResponsive.isOpen(index)" 3871 x-collapse> 3872 <li> 3873 <a href="/pstn/" aria-label="Digital Phone Switchover" 3874 class="inline-block text-sm font-normal leading-tight hover:underline"> 3875 Digital Phone Switchover </a> 3876 </li> 3877 <li> 3878 <a href="/journal" aria-label="Journal" 3879 class="inline-block text-sm font-normal leading-tight hover:underline">
3880 Journal </a> 3881 </li> 3882 <li> 3883 <a href="/media/wysiwyg/pdf/online-brochure-banham.pdf" aria-label="Banham Brochure" 3884 class="inline-block text-sm font-normal leading-tight hover:underline"> 3885 Banham Brochure </a> 3886 </li> 3887 </ul> 3888 </div> 3889 3890 </div> 3891 3892 <div class="footer-right-col flex flex-col md:gap-14 gap-5" x-data="{}"> 3893 <div x-data="{ index: 3 }"> 3894 <h3 class="text-[10px] uppercase leading-none max-md:mb-0 max-md:flex items-center justify-between font-grotesk tracking-wide" 3895 @click="$store.footerResponsive.isMobile && $store.footerResponsive.toggle(index)"> 3896 Join Us <i class="ti ti-chevron-down inline-block ml-auto text-sm md:hidden transition-transform duration-300" 3897 :class="$store.footerResponsive.isOpen(index) ? 'rotate-180' : 'rotate-0'"></i> 3898 </h3> 3899 3900 <ul class="flex flex-col gap-2 max-md:[&>li:first-child]:mt-3" 3901 x-show="!$store.footerResponsive.isMobile || $store.footerResponsive.isOpen(index)" 3902 x-collapse> 3903 <li> 3904 <a href="https://careers.banham.com/" aria-label="Banham Careers" 3905 class="inline-block text-sm font-normal leading-tight hover:underline"> 3906 Banham Careers </a> 3907 </li> 3908 <li> 3909 <a href="https://www.banhamacademy.co.uk/" aria-label="Banham Academy" 3910 class="inline-block text-sm font-normal leading-tight hover:underline"> 3911 Banham Academy </a> 3912 </li> 3913 <li> 3914 <a href="/media-enquiries" aria-label="Media Enquiries" 3915 class="inline-block text-sm font-normal leading-tight hover:underline"> 3916 Media Enquiries </a> 3917 </li> 3918 </ul> 3919 </div> 3920 <div x-data="{ index: 4 }"> 3921 <h3 class="text-[10px] uppercase leading-none max-md:mb-0 max-md:flex items-center justify-between font-grotesk tracking-wide" 3922 @click="$store.footerResponsive.isMobile && $store.footerResponsive.toggle(index)"> 3923 Policies <i class="ti ti-chevron-down inline-block ml-auto text-sm md:hidden transition-transform duration-300" 3924 :class="$store.footerResponsive.isOpen(index) ? 'rotate-180' : 'rotate-0'"></i> 3925 </h3> 3926 3927 <ul class="flex flex-col gap-2 max-md:[&>li:first-child]:mt-3" 3928 x-show="!$store.footerResponsive.isMobile || $store.footerResponsive.isOpen(index)" 3929 x-collapse> 3930 <li> 3931 <a href="/terms-conditions/" aria-label="Terms and Conditions" 3932 class="inline-block text-sm font-normal leading-tight hover:underline"> 3933 Terms and Conditions </a> 3934 </li> 3935 <li> 3936 <a href="/website-terms-conditions/" aria-label="Website Terms and Conditions" 3937 class="inline-block text-sm font-normal leading-tight hover:underline"> 3938 Website Terms and Conditions </a> 3939 </li> 3940 <li> 3941 <a href="/privacy-promise/" aria-label="Privacy Promise" 3942 class="inline-block text-sm font-normal leading-tight hover:underline"> 3943 Privacy Promise </a> 3944 </li> 3945 <li> 3946 <a href="/eula/" aria-label="Banham App EULA" 3947 class="inline-block text-sm font-normal leading-tight hover:underline"> 3948 Banham App EULA </a> 3949 </li> 3950 <li> 3951 <a href="/cookie-notice/" aria-label="Cookie Notice" 3952 class="inline-block text-sm font-normal leading-tight hover:underline"> 3953 Cookie Notice </a> 3954 </li> 3955 <li> 3956 <a href="/media/wysiwyg/pdf/banham-patent-locks-ltd-section-172-statement-companies-act-2006.pdf" aria-label="Section 172 Policy" 3957 class="inline-block text-sm font-normal leading-tight hover:underline"> 3958 Section 172 Policy </a> 3959 </li> 3960 <li> 3961 <a href="/media/wysiwyg/pdf/sexual-harassment-statement.pdf" aria-label="Zero Tolerance policy" 3962 class="inline-block text-sm font-normal leading-tight hover:underline"> 3963 Zero Tolerance policy </a> 3964 </li> 3965 <li> 3966 <a href="https://a.storyblok.com/f/286600090739628/x/3672f2235d/modern_slavery_statement_dec-2025.pdf" aria-label="Modern Slavery Policy Statement" 3967 class="inline-block text-sm font-normal leading-tight hover:underline"> 3968 Modern Slavery Policy Statement </a> 3969 </li> 3970 </ul> 3971 </div> 3972 </div>
3973 <span class="text-4xl leading-[43.2px] text-center mt-[60px] md:hidden">Trusted,<br/>Since 1926</span> </div> 3974 <div class="footer-bottom container max-md:max-w-[406px] text-xs leading-[18px] pb-[60px] [&_*]:text-xs [&_p]:mb-0 [&_a]:underline text-center max-md:[&_p]:inline"> 3975 <p>Registered in England and Wales under the Registration Number 00457812. Company address: 20 Thornsett Road, Earlsfield, London, SW18 4EF</p> 3976<p>This site is protected by reCAPTCHA and our <a href="https://www.banham.co.uk/privacy-promise/">Privacy Policy</a> and <a href="https://www.banham.co.uk/website-terms-conditions/">Website T&Cs</a> apply.</p> </div> 3977 </div> 3978 </div> 3979 3980 <div x-data="initNewsletterFormModal"> 3981 <template x-if="shouldShowModal"> 3982 <dialog 3983 id="newsletter-modal" 3984 x-show="open" 3985 x-transition 3986 x-htmldialog="closeModal" 3987 class="open:flex flex-col justify-end md:justify-center md:h-fit h-full max-md:mt-auto my-0 md:m-auto bg-transparent lg:flex-row max-w-full md:max-w-fit after:content-[''] after:flex after:fixed after:inset-0 after:bg-black after:w-full after:h-full after:bg-opacity-50 after:z-[-1]"> 3988 3989 <div class="hidden lg:flex lg:w-[343px] h-auto"> 3990 <img 3991 class="w-full h-full object-cover" 3992 alt="Unlock the latest with the Banham newsletter." 3993 loading="eager" 3994 src="https://a.storyblok.com/f/286600090739628/343x515/516aee5d3b/banham-locks-newsletter.png"> 3995 </div> 3996 3997 <div class="relative flex flex-col items-center justify-center w-full md:w-[426px] lg:w-[506px] gap-10 px-5 md:px-10 pt-10 pb-[60px] text-center bg-white"> 3998 3999 <button 4000 class="absolute right-5 top-5 text-black hover:text-primary-darker focus:outline-none focus:ring-0" 4001 aria-label="Close panel" 4002 @click="closeModal"> 4003 <i class="ti ti-x text-xl"></i> 4004 </button> 4005 4006 <h3 class="m-0 md:max-w-[320px]">Unlock the latest with the Banham newsletter.</h3> 4007 4008 <form 4009 id="newsletter-validate-modal" 4010 action="https://www.banham.co.uk/newsletter/subscriber/new/" 4011 method="post" 4012 @submit.prevent="submitForm" 4013 class="flex flex-col w-full gap-5" 4014 > 4015 <div class="flex flex-col items-start gap-3" :class="{'field-error': errors.firstname}"> 4016 <input 4017 type="text" 4018 name="firstname" 4019 id="newsletter-firstname-modal" 4020 x-model="firstname" 4021 @blur="validateField('firstname')" 4022 :class="{'error': errors.firstname}" 4023 class="form-input" 4024 placeholder="First Name *" 4025 > 4026 <span class="text-error text-xs" x-show="errors.firstname" class="mage-error" x-text="errors.firstname"></span> 4027 </div> 4028 <div class="flex flex-col items-start gap-3" :class="{'field-error': errors.lastname}"> 4029 <input 4030 type="text" 4031 name="lastname" 4032 id="newsletter-lastname-modal" 4033 x-model="lastname" 4034 @blur="validateField('lastname')" 4035 :class="{'error': errors.lastname}" 4036 class="form-input" 4037 placeholder="Last Name *" 4038 >
4039 <span class="text-error text-xs" x-show="errors.lastname" class="mage-error" x-text="errors.lastname"></span> 4040 </div> 4041 <div class="relative email-box flex flex-col items-start gap-3" :class="{'field-error': errors.email}"> 4042 <input 4043 type="email" 4044 name="email" 4045 id="newsletter-subscribe-modal" 4046 x-model="email" 4047 @blur="validateField('email')" 4048 :class="{'error': errors.email}" 4049 class="form-input" 4050 placeholder="Email Address *" 4051 > 4052 <span class="text-error text-xs" x-show="errors.email" class="mage-error" x-text="errors.email"></span> 4053 </div> 4054 4055 <input name="form_key" type="hidden" value="9ScZqxAtVshasw9c" /> 4056 <button type="submit" class="btn btn-primary"> 4057 Subscribe </button> 4058 </form> 4059 4060 4061 <div class="bottom-note m-0 text-primary-lighter [&_p]:m-0 [&_p]:text-primary-lighter"><p>If you would like further information on how we use yourâ¨personal data, please check our<a href="/privacy-promise" target="_self"> Privacy Policy</a>.</p></div> 4062 </div> 4063 </dialog> 4064 </template> 4065 </div> 4066 4067 4068 4069</footer> 4070 4071 4072 4073 4074 4075 4076 4077<style> 4078 @keyframes splide-loading{0%{transform:rotate(0)}to{transform:rotate(1turn)}}.splide__track--draggable{-webkit-touch-callout:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}.splide__track--fade>.splide__list>.splide__slide{margin:0!important;opacity:0;z-index:0}.splide__track--fade>.splide__list>.splide__slide.is-active{opacity:1;z-index:1}.splide--rtl{direction:rtl}.splide__track--ttb>.splide__list{display:block}.splide__container{box-sizing:border-box;position:relative}.splide__list{backface-visibility:hidden;display:-ms-flexbox;display:flex;height:100%;margin:0!important;padding:0!important}.splide.is-initialized:not(.is-active) .splide__list{display:grid}.splide__pagination{-ms-flex-align:center;align-items:center;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:center;justify-content:center;margin:0;pointer-events:none}.splide__pagination li{display:inline-block;line-height:1;list-style-type:none;margin:0;pointer-events:auto}.splide:not(.is-overflow) .splide__pagination{display:none}.splide__progress__bar{width:0}.splide{position:relative;visibility:hidden}.splide.is-initialized,.splide.is-rendered{visibility:visible}.splide__slide{backface-visibility:hidden;box-sizing:border-box;-ms-flex-negative:0;flex-shrink:0;list-style-type:none!important;margin:0;position:relative}.splide__slide img{vertical-align:bottom}.splide__spinner{animation:splide-loading 1s linear infinite;border:2px solid #999;border-left-color:transparent;border-radius:50%;bottom:0;contain:strict;display:inline-block;height:20px;left:0;margin:auto;position:absolute;right:0;top:0;width:20px}.splide__sr{clip:rect(0 0 0 0);border:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.splide__toggle.is-active .splide__toggle__play,.splide__toggle__pause{display:none}.splide__toggle.is-active .splide__toggle__pause{display:inline}.splide__track{overflow:hidden;position:relative;z-index:0} 4079 4080 /* Custom additions from the splide.min.css but without the custom style */ 4081 .splide__pagination--ttb { 4082 position: absolute; 4083 display: inline-flex; 4084 flex-direction: column; 4085 top: 0; 4086 bottom: 0; 4087 right: 100%; 4088 } 4089 4090 /* Hide the pagination dots when the input device is a touch based input */ 4091 @media (pointer: coarse) or (max-width: 639px) { 4092 .splide__pagination { display: none } 4093 } 4094</style> 4095 4096 4097 4098 4099 4100 4101 4102 4103 <!-- [FOOTER Miscellaneous HTML (store)] --> 4104 4105<!-- [/FOOTER Miscellaneous HTML (store)] --> 4106 4107 4108 4109<!-- Google Tag Manager --> 4110 4111<!-- End Google Tag Manager --> 4112 4113<!-- Google Tag Manager (noscript) -->
vendor: 73 bytes, lines 4113-4115
4113 4114<noscript> 4115 <iframe src="https://www.googletagmanager.com/ns.html?id=
4115GTM-5VC2G8L
vendor: 92 bytes, lines 4115-4117
4115" height="0" 4116 width="0" style="display:none;visibility:hidden"></iframe> 4117</noscript>
4118<!-- Google Tag Manager --> 4119 4120<!-- End Google Tag Manager --> 4121 4122<!-- Google Tag Manager (noscript) -->
vendor: 73 bytes, lines 4122-4124
4122 4123<noscript> 4124 <iframe src="https://www.googletagmanager.com/ns.html?id=
4124GTM-5VC2G8L
vendor: 95 bytes, lines 4124-4126
4124" height="0" 4125 width="0" style="display:none;visibility:hidden"></iframe> 4126</noscript>
4126</div> <div style="display: none"><div class="block popup-newsletter"><div class="content"><style>#html-body [data-pb-style=GAY0OEC]{justify-content:flex-start;display:flex;flex-direction:column;background-position:center center;background-size:cover;background-repeat:no-repeat;background-attachment:scroll;min-height:188px}</style><div data-content-type="row" data-appearance="full-bleed" data-enable-parallax="0" data-parallax-speed="0.5" data-background-images='{\"desktop_image\":\"https://www.banham.co.uk/media/wysiwyg/locks-q3-hero-newsletter.jpg\",\"mobile_image\":\"https://www.banham.co.uk/media/wysiwyg/locks-q3-hero-page_1.png\"}' data-background-type="image" data-video-loop="true" data-video-play-only-visible="true" data-video-lazy-load="true" data-video-fallback-src="" data-element="main" data-pb-style="GAY0OEC" data-src="https://www.banham.co.uk/media/wysiwyg/locks-q3-hero-newsletter.jpg" data-mobile-source="https://www.banham.co.uk/media/wysiwyg/locks-q3-hero-page_1.png" class=" lazy background-image-6abf643a29a25"></div><style type="text/css">.background-image-6abf643a29a25 {background-image: url(https://www.banham.co.uk/media/wysiwyg/locks-q3-hero-newsletter.jpg);}@media only screen and (max-width: 767px) and (min-width: 640px) {.background-image-6abf643a29a25 {background-image: url(https://www.banham.co.uk/media/wysiwyg/locks-q3-hero-page_1.png);}}@media only screen and (max-width: 640px) {.background-image-6abf643a29a25 {background-image: url(https://www.banham.co.uk/media/wysiwyg/locks-q3-hero-page_1.png);}}</style> <div class="right"><h3>STAY UP TO DATE WITH <br> THE BANHAM NEWSLETTER</h3><h4>For product updates, company news <br> and the latest stylish products</h4><div class="relative container"> 4127 <div class="flex flex-col items-center justify-center w-full md:w-[426px] lg:w-[506px] gap-10 mx-auto pt-10 lg:pt-20 text-center"> 4128 <div class="flex flex-col gap-4 text-center w-full md:max-w-[320px]"> 4129 <h3 class="m-0">Unlock the latest with the Banham newsletter.</h3> 4130 <p class="mb-0">Weâd love to stay in touch. Sign up to our newsletter and be the first to hear our latest news, launches, and innovations.</p> 4131 </div> 4132 <form 4133 id="newsletter-validate-detail" 4134 action="https://www.banham.co.uk/newsletter/subscriber/new/" 4135 method="post" 4136 x-data="newsletterForm()" 4137 @submit.prevent="submitForm" 4138 class="flex flex-col w-full gap-5" 4139 > 4140 <div class="flex flex-col items-start gap-3" :class="{'field-error': errors.firstname}"> 4141 <input 4142 type="text" 4143 name="firstname" 4144 id="firstname" 4145 x-model="firstname" 4146 @blur="validateField('firstname')" 4147 :class="{'error': errors.firstname}" 4148 class="form-input" 4149 placeholder="First Name *" 4150 > 4151 <span class="text-error text-xs mage-error" x-show="errors.firstname" x-text="errors.firstname"></span> 4152 </div> 4153 4154 <div class="flex flex-col items-start gap-3" :class="{'field-error': errors.lastname}"> 4155 <input 4156 type="text" 4157 name="lastname" 4158 id="lastname" 4159 x-model="lastname" 4160 @blur="validateField('lastname')" 4161 :class="{'error': errors.lastname}" 4162 class="form-input" 4163 placeholder="Last Name *" 4164 > 4165 <span class="text-error text-xs mage-error" x-show="errors.lastname" x-text="errors.lastname"></span> 4166 </div> 4167 4168 <div class="relative flex flex-col items-start gap-3" :class="{'field-error': errors.email}"> 4169 <input 4170 type="email" 4171 name="email" 4172 id="email" 4173 x-model="email" 4174 @blur="validateField('email')" 4175 :class="{'error': errors.email}" 4176 class="form-input" 4177 placeholder="Email Address *" 4178 >
4179 <span class="text-error text-xs mage-error" x-show="errors.email" x-text="errors.email"></span> 4180 </div> 4181 4182 <input name="form_key" type="hidden" value="9ScZqxAtVshasw9c" /> 4183 <button type="submit" class="btn-primary btn btn-primary btn-size-md w-full mb-4"> 4184 Subscribe </button> 4185 4186 </form> 4187 4188 <p class="bottom-note m-0 text-primary-lighter"><p>If you would like further information on how we use yourâ¨personal data, please check our<a href="/privacy-promise" target="_self"> Privacy Policy</a>.</p></p> 4189 </div> 4190</div> 4191 4192 <a class="not-now" href="#">Not Now</a> <style>#html-body [data-pb-style=I98KDDQ]{justify-content:flex-start;display:flex;flex-direction:column;background-position:left top;background-size:cover;background-repeat:no-repeat;background-attachment:scroll}</style><div data-content-type="row" data-appearance="full-bleed" data-enable-parallax="0" data-parallax-speed="0.5" data-background-images="{}" data-background-type="image" data-video-loop="true" data-video-play-only-visible="true" data-video-lazy-load="true" data-video-fallback-src="" data-element="main" data-pb-style="I98KDDQ"><div data-content-type="text" data-appearance="default" data-element="main"><p>If you would like further information on how we use your personal data, please check our <a style="text-decoration: none;" tabindex="0" href="/privacy-promise/">Privacy Policy</a>.</p></div></div></div></div></div></div>
4193<script type="text/javascript">window.amasty_checkout_disabled=true;</script>
vendor: 1 bytes, line 4193
4193
4194<script type="text/javascript" src="https://www.banham.co.uk/static/version1790667712/frontend/BraveBison/default/en_GB/BestResponseMedia_MagemeModification/js/source.js"></script>
vendor: 1 bytes, line 4194
4194
4195<script src="https://eu1-config.doofinder.com/2.x/37bb0b70-b046-4147-8e74-bf61c063a23b.js" async></script>
vendor: 1 bytes, line 4195
4195
4196<script src="https://www.googleoptimize.com/optimize.js?id=OPT-PNJW7FQ"></script>
vendor: 1 bytes, line 4196
4196
4197<script>(function(n,t,i,r){var u,f;n[i]=n[i]||{},n[i].initial={accountCode:"BANHA11114",host:"BANHA11114.pcapredict.com"},n[i].on=n[i].on||function(){(n[i].onq=n[i].onq||[]).push(arguments)},u=t.createElement("script"),u.async=!0,u.src=r,f=t.getElementsByTagName("script")[0],f.parentNode.insertBefore(u,f)})(window,document,"pca","//BANHA11114.pcapredict.com/js/sensor.js")</script>
vendor: 1 bytes, line 4197
4197
4198<script type='text/javascript' id='vwoCode'> 4199window._vwo_code || (function() { 4200var account_id=996012, 4201version=2.1, 4202settings_tolerance=2000, 4203hide_element='body', 4204hide_element_style = 'opacity:0 !important;filter:alpha(opacity=0) !important;background:none !important;transition:none !important;', 4205/* DO NOT EDIT BELOW THIS LINE */ 4206f=false,w=window,d=document,v=d.querySelector('#vwoCode'),cK='_vwo_'+account_id+'_settings',cc={};try{var c=JSON.parse(localStorage.getItem('_vwo_'+account_id+'_config'));cc=c&&typeof c==='object'?c:{}}catch(e){}var stT=cc.stT==='session'?w.sessionStorage:w.localStorage;code={nonce:v&&v.nonce,use_existing_jquery:function(){return typeof use_existing_jquery!=='undefined'?use_existing_jquery:undefined},library_tolerance:function(){return typeof library_tolerance!=='undefined'?library_tolerance:undefined},settings_tolerance:function(){return cc.sT||settings_tolerance},hide_element_style:function(){return'{'+(cc.hES||hide_element_style)+'}'},hide_element:function(){if(performance.getEntriesByName('first-contentful-paint')[0]){return''}return typeof cc.hE==='string'?cc.hE:hide_element},getVersion:function(){return version},finish:function(e){if(!f){f=true;var t=d.getElementById('_vis_opt_path_hides');if(t)t.parentNode.removeChild(t);if(e)(new Image).src='https://dev.visualwebsiteoptimizer.com/ee.gif?a='+account_id+e}},finished:function(){return f},addScript:function(e){var t=d.createElement('script');t.type='text/javascript';if(e.src){t.src=e.src}else{t.text=e.text}v&&t.setAttribute('nonce',v.nonce);d.getElementsByTagName('head')[0].appendChild(t)},load:function(e,t){var n=this.getSettings(),i=d.createElement('script'),r=this;t=t||{};if(n){i.textContent=n;d.getElementsByTagName('head')[0].appendChild(i);if(!w.VWO||VWO.caE){stT.removeItem(cK);r.load(e)}}else{var o=new XMLHttpRequest;o.open('GET',e,true);o.withCredentials=!t.dSC;o.responseType=t.responseType||'text';o.onload=function(){if(t.onloadCb){return t.onloadCb(o,e)}if(o.status===200||o.status===304){_vwo_code.addScript({text:o.responseText})}else{_vwo_code.finish('&e=loading_failure:'+e)}};o.onerror=function(){if(t.onerrorCb){return t.onerrorCb(e)}_vwo_code.finish('&e=loading_failure:'+e)};o.send()}},getSettings:function(){try{var e=stT.getItem(cK);if(!e){return}e=JSON.parse(e);if(Date.now()>e.e){stT.removeItem(cK);return}return e.s}catch(e){return}},init:function(){if(d.URL.indexOf('__vwo_disable__')>-1)return;var e=this.settings_tolerance();w._vwo_settings_timer=setTimeout(function(){_vwo_code.finish();stT.removeItem(cK)},e);var t;if(this.hide_element()!=='body'){t=d.createElement('style');var n=this.hide_element(),i=n?n+this.hide_element_style():'',r=d.getElementsByTagName('head')[0];t.setAttribute('id','_vis_opt_path_hides');v&&t.setAttribute('nonce',v.nonce);t.setAttribute('type','text/css');if(t.styleSheet)t.styleSheet.cssText=i;else t.appendChild(d.createTextNode(i));r.appendChild(t)}else{t=d.getElementsByTagName('head')[0];var i=d.createElement('div');i.style.cssText='z-index: 2147483647 !important;position: fixed !important;left: 0 !important;top: 0 !important;width: 100% !important;height: 100% !important;background: white !important;display: block !important;';i.setAttribute('id','_vis_opt_path_hides');i.classList.add('_vis_hide_layer');t.parentNode.insertBefore(i,t.nextSibling)}var o=window._vis_opt_url||d.URL,s='https://dev.visualwebsiteoptimizer.com/j.php?a='+account_id+'&u='+encodeURIComponent(o)+'&vn='+version;if(w.location.search.indexOf('_vwo_xhr')!==-1){this.addScript({src:s})}else{this.load(s+'&x=true')}}};w._vwo_code=code;code.init();})(); 4207</script>
vendor: 1 bytes, line 4207
4207
4208<script> 4209(function () { 4210 function shouldOpen() { 4211 return window.location.hash === '#open_enquiry_form' || 4212 window.location.search.includes('open=enquiry'); 4213 } 4214 4215 function triggerEnquiryForm() { 4216 if (!shouldOpen()) return; 4217 4218 let tries = 0; 4219 const maxTries = 40; 4220 4221 const timer = setInterval(function () { 4222 const btn = document.querySelector('.open-slide-form-button'); 4223 4224 if (btn) { 4225 window.surveyOpeningSource = 'Opened from CTA'; 4226 btn.click(); 4227 clearInterval(timer); 4228 return; 4229 } 4230 4231 tries++; 4232 if (tries >= maxTries) { 4233 clearInterval(timer); 4234 } 4235 }, 250); 4236 } 4237 4238 if (document.readyState === 'loading') { 4239 document.addEventListener('DOMContentLoaded', triggerEnquiryForm); 4240 } else { 4241 triggerEnquiryForm(); 4242 } 4243 4244 window.addEventListener('hashchange', triggerEnquiryForm); 4245})(); 4246</script>
vendor: 1 bytes, line 4246
4246
4247<script type="text/javascript" data-routing="commerce=rum" defer="defer" src="https://rum.hlx.page/.rum/@adobe/helix-rum-js@^2/dist/rum-standalone.js"></script>
vendor: 1 bytes, line 4247
4247
4248<script type="text/javascript" src="" defer="defer"> 4249</script>
vendor: 1 bytes, line 4249
4249
4250<script> 4251 var BASE_URL = 'https://www.banham.co.uk/'; 4252 var THEME_PATH = 'https://www.banham.co.uk/static/version1790667712/frontend/BraveBison/default/en_GB'; 4253 var COOKIE_CONFIG = { 4254 "expires": null, 4255 "path": "\u002F", 4256 "domain": ".www.banham.co.uk", 4257 "secure": true, 4258 "lifetime": "24800", 4259 "cookie_restriction_enabled": false }; 4260 var CURRENT_STORE_CODE = 'default'; 4261 var CURRENT_WEBSITE_ID = '1'; 4262 4263 window.hyva = window.hyva || {} 4264 4265 window.cookie_consent_groups = window.cookie_consent_groups || {} 4266 window.cookie_consent_groups['necessary'] = true; 4267 4268 window.cookie_consent_config = window.cookie_consent_config || {}; 4269 window.cookie_consent_config['necessary'] = [].concat( 4270 window.cookie_consent_config['necessary'] || [], 4271 [ 4272 'user_allowed_save_cookie', 4273 'form_key', 4274 'mage-messages', 4275 'private_content_version', 4276 'mage-cache-sessid', 4277 'last_visited_store', 4278 'section_data_ids' 4279 ] 4280 );</script>
vendor: 1 bytes, line 4280
4280
4281<script> 4282 (function( hyva, undefined ) { 4283 4284 function lifetimeToExpires(options, defaults) { 4285 4286 const lifetime = options.lifetime || defaults.lifetime; 4287 4288 if (lifetime) { 4289 const date = new Date; 4290 date.setTime(date.getTime() + lifetime * 1000); 4291 return date; 4292 } 4293 4294 return null; 4295 } 4296 4297 function generateRandomString() { 4298 4299 const allowedCharacters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 4300 length = 16; 4301 4302 let formKey = '', 4303 charactersLength = allowedCharacters.length; 4304 4305 for (let i = 0; i < length; i++) { 4306 formKey += allowedCharacters[Math.round(Math.random() * (charactersLength - 1))] 4307 } 4308 4309 return formKey; 4310 } 4311 4312 const sessionCookieMarker = {noLifetime: true} 4313 4314 const cookieTempStorage = {}; 4315 4316 const internalCookie = { 4317 get(name) { 4318 const v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)'); 4319 return v ? v[2] : null; 4320 }, 4321 set(name, value, days, skipSetDomain) { 4322 let expires, 4323 path, 4324 domain, 4325 secure, 4326 samesite; 4327 4328 const defaultCookieConfig = { 4329 expires: null, 4330 path: '/', 4331 domain: null, 4332 secure: false, 4333 lifetime: null, 4334 samesite: 'lax' 4335 }; 4336 4337 const cookieConfig = window.COOKIE_CONFIG || {}; 4338 4339 expires = days && days !== sessionCookieMarker 4340 ? lifetimeToExpires({lifetime: 24 * 60 * 60 * days, expires: null}, defaultCookieConfig) 4341 : lifetimeToExpires(window.COOKIE_CONFIG, defaultCookieConfig) || defaultCookieConfig.expires; 4342 4343 path = cookieConfig.path || defaultCookieConfig.path; 4344 domain = !skipSetDomain && (cookieConfig.domain || defaultCookieConfig.domain); 4345 secure = cookieConfig.secure || defaultCookieConfig.secure; 4346 samesite = cookieConfig.samesite || defaultCookieConfig.samesite; 4347 4348 document.cookie = name + "=" + encodeURIComponent(value) + 4349 (expires && days !== sessionCookieMarker ? '; expires=' + expires.toGMTString() : '') + 4350 (path ? '; path=' + path : '') + 4351 (domain ? '; domain=' + domain : '') + 4352 (secure ? '; secure' : '') + 4353 (samesite ? '; samesite=' + samesite : 'lax'); 4354 }, 4355 isWebsiteAllowedToSaveCookie() { 4356 const allowedCookies = this.get('user_allowed_save_cookie'); 4357 if (allowedCookies) { 4358 const allowedWebsites = JSON.parse(unescape(allowedCookies)); 4359 4360 return allowedWebsites[CURRENT_WEBSITE_ID] === 1; 4361 } 4362 return false; 4363 }, 4364 getGroupByCookieName(name) { 4365 const cookieConsentConfig = window.cookie_consent_config || {}; 4366 let group = null; 4367 for (let prop in cookieConsentConfig) { 4368 if (!cookieConsentConfig.hasOwnProperty(prop)) continue; 4369 if (cookieConsentConfig[prop].includes(name)) { 4370 group = prop; 4371 break; 4372 } 4373 } 4374 return group; 4375 }, 4376 isCookieAllowed(name) { 4377 const cookieGroup = this.getGroupByCookieName(name); 4378 return cookieGroup 4379 ? window.cookie_consent_groups[cookieGroup] 4380 : this.isWebsiteAllowedToSaveCookie(); 4381 }, 4382 saveTempStorageCookies() { 4383 for (const [name, data] of Object.entries(cookieTempStorage)) { 4384 if (this.isCookieAllowed(name)) { 4385 this.set(name, data['value'], data['days'], data['skipSetDomain']); 4386 delete cookieTempStorage[name]; 4387 } 4388 } 4389 } 4390 }; 4391 4392 hyva.getCookie = (name) => { 4393 const cookieConfig = window.COOKIE_CONFIG || {}; 4394 4395 if (cookieConfig.cookie_restriction_enabled && ! internalCookie.isCookieAllowed(name)) { 4396 return cookieTempStorage[name] ? cookieTempStorage[name]['value'] : null; 4397 } 4398 4399 return internalCookie.get(name); 4400 } 4401 4402 hyva.setCookie = (name, value, days, skipSetDomain) => { 4403 const cookieConfig = window.COOKIE_CONFIG || {}; 4404 4405 if (cookieConfig.cookie_restriction_enabled && ! internalCookie.isCookieAllowed(name)) { 4406 cookieTempStorage[name] = {value, days, skipSetDomain}; 4407 return; 4408 } 4409 return internalCookie.set(name, value, days, skipSetDomain); 4410 } 4411 4412 4413 hyva.setSessionCookie = (name, value, skipSetDomain) => { 4414 return hyva.setCookie(name, value, sessionCookieMarker, skipSetDomain) 4415 } 4416 4417 hyva.getBrowserStorage = () => { 4418 const browserStorage = window.localStorage || window.sessionStorage; 4419 if (!browserStorage) { 4420 console.warn('Browser Storage is unavailable'); 4421 return false;
4422 } 4423 try { 4424 browserStorage.setItem('storage_test', '1'); 4425 browserStorage.removeItem('storage_test'); 4426 } catch (error) { 4427 console.warn('Browser Storage is not accessible', error); 4428 return false; 4429 } 4430 return browserStorage; 4431 } 4432 4433 hyva.postForm = (postParams) => { 4434 const form = document.createElement("form"); 4435 4436 let data = postParams.data; 4437 4438 if (! postParams.skipUenc && ! data.uenc) { 4439 data.uenc = btoa(window.location.href); 4440 } 4441 form.method = "POST"; 4442 form.action = postParams.action; 4443 4444 Object.keys(postParams.data).map(key => { 4445 const field = document.createElement("input"); 4446 field.type = 'hidden' 4447 field.value = postParams.data[key]; 4448 field.name = key; 4449 form.appendChild(field); 4450 }); 4451 4452 const form_key = document.createElement("input"); 4453 form_key.type = 'hidden'; 4454 form_key.value = hyva.getFormKey(); 4455 form_key.name="form_key"; 4456 form.appendChild(form_key); 4457 4458 document.body.appendChild(form); 4459 4460 form.submit(); 4461 } 4462 4463 hyva.getFormKey = function () { 4464 let formKey = hyva.getCookie('form_key'); 4465 4466 if (!formKey) { 4467 formKey = generateRandomString(); 4468 hyva.setCookie('form_key', formKey); 4469 } 4470 4471 return formKey; 4472 } 4473 4474 hyva.formatPrice = (value, showSign, options = {}) => { 4475 const groupSeparator = options.groupSeparator; 4476 const decimalSeparator = options.decimalSeparator 4477 delete options.groupSeparator; 4478 delete options.decimalSeparator; 4479 const formatter = new Intl.NumberFormat( 4480 'en\u002DGB', 4481 Object.assign({ 4482 style: 'currency', 4483 currency: 'GBP', 4484 signDisplay: showSign ? 'always' : 'auto' 4485 }, options) 4486 ); 4487 return (typeof Intl.NumberFormat.prototype.formatToParts === 'function') ? 4488 formatter.formatToParts(value).map(({type, value}) => { 4489 switch (type) { 4490 case 'currency': 4491 return '\u00A3' || value; 4492 case 'minusSign': 4493 return '- '; 4494 case 'plusSign': 4495 return '+ '; 4496 case 'group': 4497 return groupSeparator !== undefined ? groupSeparator : value; 4498 case 'decimal': 4499 return decimalSeparator !== undefined ? decimalSeparator : value; 4500 default : 4501 return value; 4502 } 4503 }).reduce((string, part) => string + part) : 4504 formatter.format(value); 4505 } 4506 4507 const formatStr = function (str, nStart) { 4508 const args = Array.from(arguments).slice(2); 4509 4510 return str.replace(/(%+)([0-9]+)/g, (m, p, n) => { 4511 const idx = parseInt(n) - nStart; 4512 4513 if (args[idx] === null || args[idx] === void 0) { 4514 return m; 4515 } 4516 return p.length % 2 4517 ? p.slice(0, -1).replace('%%', '%') + args[idx] 4518 : p.replace('%%', '%') + n; 4519 }) 4520 } 4521 4522 hyva.str = function (string) { 4523 const args = Array.from(arguments); 4524 args.splice(1, 0, 1); 4525 4526 return formatStr.apply(undefined, args); 4527 } 4528 4529 hyva.strf = function () { 4530 const args = Array.from(arguments); 4531 args.splice(1, 0, 0); 4532 4533 return formatStr.apply(undefined, args); 4534 } 4535 4536 /** 4537 * Take a html string as `content` parameter and 4538 * extract an element from the DOM to replace in 4539 * the current page under the same selector, 4540 * defined by `targetSelector` 4541 */ 4542 hyva.replaceDomElement = (targetSelector, content) => { 4543 4544 const parser = new DOMParser(); 4545 const doc = parser.parseFromString(content, 'text/html');
4546 const contentNode = doc.querySelector(targetSelector); 4547 4548 4549 if (!contentNode || !document.querySelector(targetSelector)) { 4550 return; 4551 } 4552 4553 hyva.activateScripts(contentNode); 4554 document.querySelector(targetSelector).replaceWith(contentNode); 4555 4556 4557 window.dispatchEvent(new CustomEvent("reload-customer-section-data")); 4558 hyva.initMessages(); 4559 } 4560 4561 hyva.removeScripts = (contentNode) => { 4562 const scripts = contentNode.getElementsByTagName('script'); 4563 for (let i = 0; i < scripts.length; i++) { 4564 scripts[i].parentNode.removeChild(scripts[i]); 4565 } 4566 const templates = contentNode.getElementsByTagName('template'); 4567 for (let i = 0; i < templates.length; i++) { 4568 const container = document.createElement('div'); 4569 container.innerHTML = templates[i].innerHTML; 4570 hyva.removeScripts(container); 4571 templates[i].innerHTML = container.innerHTML; 4572 } 4573 } 4574 4575 hyva.activateScripts = (contentNode) => { 4576 const scripts = Array.from(contentNode.getElementsByTagName('script')); 4577 4578 4579 for (const original of scripts) { 4580 const script = document.createElement('script'); 4581 original.type && (script.type = original.type); 4582 script.innerHTML = original.innerHTML; 4583 4584 4585 original.parentNode.removeChild(original) 4586 4587 4588 document.head.appendChild(script); 4589 } 4590 4591 return contentNode; 4592 } 4593 4594 const replace = {['+']: '-', ['/']: '_', ['=']: ','}; 4595 hyva.getUenc = () => btoa(window.location.href).replace(/[+/=]/g, match => replace[match]); 4596 4597 let currentTrap; 4598 4599 const focusableElements = (rootElement) => { 4600 const selector = 'button, [href], input, select, textarea, details, [tabindex]:not([tabindex="-1"]'; 4601 return Array.from(rootElement.querySelectorAll(selector)) 4602 .filter(el => { 4603 return el.style.display !== 'none' 4604 && !el.disabled 4605 && el.tabIndex !== -1 4606 && (el.offsetWidth || el.offsetHeight || el.getClientRects().length) 4607 }) 4608 } 4609 4610 const focusTrap = (e) => { 4611 const isTabPressed = e.key === 'Tab' || e.keyCode === 9; 4612 if (!isTabPressed) return; 4613 4614 const focusable = focusableElements(currentTrap) 4615 const firstFocusableElement = focusable[0] 4616 const lastFocusableElement = focusable[focusable.length - 1] 4617 4618 e.shiftKey 4619 ? document.activeElement === firstFocusableElement && (lastFocusableElement.focus(), e.preventDefault()) 4620 : document.activeElement === lastFocusableElement && (firstFocusableElement.focus(), e.preventDefault()) 4621 }; 4622 4623 hyva.releaseFocus = (rootElement) => { 4624 if (currentTrap && (!rootElement || rootElement === currentTrap)) { 4625 currentTrap.removeEventListener('keydown', focusTrap) 4626 currentTrap = null 4627 } 4628 } 4629 hyva.trapFocus = (rootElement) => { 4630 if (!rootElement) return; 4631 hyva.releaseFocus() 4632 currentTrap = rootElement 4633 rootElement.addEventListener('keydown', focusTrap) 4634 const firstElement = focusableElements(rootElement)[0] 4635 firstElement && firstElement.focus() 4636 } 4637 4638 hyva.safeParseNumber = (rawValue) => { 4639 const number = rawValue ? parseFloat(rawValue) : null; 4640 4641 return Array.isArray(number) || isNaN(number) ? rawValue : number; 4642 } 4643 4644 const toCamelCase = s => s.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(''); 4645 hyva.createBooleanObject = (name, value = false, additionalMethods = {}) => { 4646 4647 const camelCase = toCamelCase(name); 4648 const key = '__hyva_bool_' + name 4649 4650 return new Proxy(Object.assign( 4651 additionalMethods, 4652 { 4653 [key]: !!value, 4654 [name]() {return !!this[key]}, 4655 ['!' + name]() {return !this[key]}, 4656 ['not' + camelCase]() {return !this[key]}, 4657 ['toggle' + camelCase]() {this[key] = !this[key]}, 4658 [`set${camelCase}True`]() {this[key] = true}, 4659 [`set${camelCase}False`]() {this[key] = false}, 4660 } 4661 ), { 4662 set(target, prop, value) { 4663 return prop === name 4664 ? (target[key] = !!value) 4665 : Reflect.set(...arguments); 4666 } 4667 }) 4668 } 4669 4670 hyva.alpineInitialized = (fn) => window.addEventListener('alpine:initialized', fn, {once: true}) 4671 window.addEventListener('alpine:init', () => Alpine.data('{}', () => ({})), {once: true}); 4672 window.addEventListener('user-allowed-save-cookie', () => internalCookie.saveTempStorageCookies()) 4673 4674 }( window.hyva = window.hyva || {} ));</script>
vendor: 1 bytes, line 4674
4674
4675<script type="speculationrules"> 4676{ 4677 "tag": "Hyva Theme", 4678 "prefetch": [{ 4679 "source": "document", 4680 "where": { 4681 "and": [ 4682 { "href_matches": "/*" }, 4683 { "not": { 4684 "href_matches": ["/customer/*","/search/*","/sales/*","/wishlist/*","/checkout/*","/paypal/*","/newsletter/*","/stores/store/redirect/*"] }}, 4685 { "not": { 4686 "selector_matches": [".no-preload", ".do-not-prerender", "[download]", "[rel~=nofollow]", "[target]"] 4687 }} 4688 ] 4689 }, 4690 "eagerness": "moderate" 4691 }] 4692}</script>
vendor: 1 bytes, line 4692
4692
4693<script> 4694 const viewTransitionToGalleryPageStorage = 'view-transition-to-gallery-page-type'; 4695 const setTransitionNameBetweenGallery = async (url, promise) => { 4696 if (window.matchMedia('(prefers-reduced-motion)').matches) return; 4697 4698 const productGallery = document.getElementById('gallery-main'); 4699 const productListItem = document.querySelector(`.product-item-photo[href="${url}"]`); 4700 const targetElement = productListItem || productGallery; 4701 4702 if (targetElement) { 4703 targetElement.style.viewTransitionName = 'view-transition-to-gallery'; 4704 4705 await promise; 4706 targetElement.style.viewTransitionName = ''; 4707 } 4708 }; 4709 4710 const getViewTransitionToGalleryPageIsProduct = () => { 4711 return document.body.classList.contains('catalog-product-view') ? 'product-page' : 'other' 4712 } 4713 4714 window.addEventListener('pageswap', async (e) => { 4715 if (!e.viewTransition) return; 4716 const pageType = getViewTransitionToGalleryPageIsProduct(); 4717 const target = new URL(e.activation.entry.url); 4718 4719 sessionStorage.setItem(viewTransitionToGalleryPageStorage, pageType); 4720 setTransitionNameBetweenGallery(target.href, e.viewTransition.finished); 4721 }); 4722 4723 window.addEventListener('pagereveal', async (e) => { 4724 if (!e.viewTransition || (window.navigation && !window.navigation.activation.from)) return; 4725 const previousPageType = sessionStorage.getItem(viewTransitionToGalleryPageStorage); 4726 const pageType = getViewTransitionToGalleryPageIsProduct(); 4727 sessionStorage.removeItem(viewTransitionToGalleryPageStorage); 4728 4729 if (previousPageType === pageType) return; 4730 const target = new URL(window.navigation.activation.from.url); 4731 setTransitionNameBetweenGallery(target.href, e.viewTransition.ready); 4732 }); 4733</script>
vendor: 1 bytes, line 4733
4733
4734<script> 4735 (function () { 4736 let amSplideProcessed = false; 4737 4738 window.addEventListener('load-amsplide', () => { 4739 if (amSplideProcessed) { 4740 window?.Splide && window.dispatchEvent(new CustomEvent('amsplide-loaded', {})); 4741 return; 4742 } 4743 4744 const script = document.createElement('script'); 4745 script.src = 'https://www.banham.co.uk/static/version1790667712/frontend/BraveBison/default/en_GB/Amasty_LibSplideJs/4.1.3/js/splide/splide.min.js'; 4746 script.async = true; 4747 document.body.appendChild(script); 4748 4749 amSplideProcessed = true; 4750 4751 script.onload = () => { 4752 window.dispatchEvent(new CustomEvent('amsplide-loaded', {})); 4753 } 4754 4755 const stylesHref = 'https://www.banham.co.uk/static/version1790667712/frontend/BraveBison/default/en_GB/Amasty_LibSplideJs/4.1.3/css/splide/splide.min.css'; 4756 4757 if (document.querySelector(`link[href="${stylesHref}"]`)) { 4758 return; 4759 } 4760 4761 const styles = document.createElement('link'); 4762 styles.rel = 'stylesheet'; 4763 styles.type = 'text/css'; 4764 styles.href = stylesHref; 4765 4766 document.head.appendChild(styles); 4767 }); 4768 }());</script>
vendor: 1 bytes, line 4768
4768
4769<script>"use strict";"object"!=typeof window.MageMe&&(window.MageMe={}),window.MageMe.loader={addScript:(e,r,d)=>{if(!window[e]){let d=[];window[e]={"-isLoading":e=>d.push(e)};var t=document.createElement("script");t.src=r,t.onload=()=>{d.map(e=>e()),d=[]},document.head.append(t)}window[e]["-isLoading"]&&window[e]["-isLoading"](()=>MageMe.loader.addScript(e,r,d)),!window[e]["-isLoading"]&&d&&d()},addScriptWoCb:e=>{var d;document.querySelector('script[src="'+e+'"]')||((d=document.createElement("script")).src=e,document.head.append(d))},addCss:e=>{var d;document.querySelector('link[href="'+e+'"]')||((d=document.createElement("link")).rel="stylesheet",d.href=e,document.head.appendChild(d))},addCssOrdered:(e,d)=>{var r;document.querySelector('link[href="'+e+'"]')||(d=(d=Number(d))||0,(r=document.createElement("link")).rel="stylesheet",r.href=e,r.setAttribute("data-mm-order",d),(e=Array.from(document.head.querySelectorAll("[data-mm-order]")).filter(e=>Number(e.dataset.mmOrder)>d)).length?document.head.insertBefore(r,e[0]):document.head.appendChild(r))}};</script>
vendor: 1 bytes, line 4769
4769
4770<script> 4771 'use strict'; 4772 (function(webfroms) { 4773 const formValidationRules = { 4774 required(value, options, field, context) { 4775 const el = field.element.type === 'hidden' ? createTextInputFrom(field.element) : field.element, 4776 msg = 'This\u0020is\u0020a\u0020required\u0020field.'; 4777 4778 if (el.type === 'radio' || el.type === 'checkbox') { 4779 return (value === undefined || value.length === 0) ? msg : true; 4780 } 4781 4782 el.setAttribute('required', ''); 4783 el.checkValidity(); 4784 4785 return el.validity.valueMissing ? msg : true; 4786 }, 4787 maxlength(value, options, field, context) { 4788 const n = Number(options) 4789 if (value.length > n) { 4790 return n === 1 4791 ? strf('Please\u0020enter\u0020no\u0020more\u0020than\u00201\u0020character.') 4792 : strf('Please\u0020enter\u0020no\u0020more\u0020than\u0020\u00250\u0020characters.', options) 4793 } 4794 return true; 4795 }, 4796 minlength(value, options, field, context) { 4797 const n = Number(options) 4798 if (value.length > 0 && value.length < n) { 4799 return n === 1 4800 ? strf('Please\u0020enter\u0020at\u0020least\u00201\u0020character.') 4801 : strf('Please\u0020enter\u0020at\u0020least\u0020\u00250\u0020characters.', options) 4802 } 4803 return true; 4804 }, 4805 max(value, options, field, context) { 4806 field.element.setAttribute('max', options); 4807 field.element.checkValidity(); 4808 if (field.element.validity.rangeOverflow) { 4809 return strf('Please\u0020enter\u0020a\u0020value\u0020less\u0020than\u0020or\u0020equal\u0020to\u0020\u0022\u00250\u0022.', options); 4810 } 4811 return true; 4812 }, 4813 min(value, options, field, context) { 4814 field.element.setAttribute('min', options); 4815 field.element.checkValidity(); 4816 if (field.element.validity.rangeUnderflow) { 4817 return strf('Please\u0020enter\u0020a\u0020value\u0020greater\u0020than\u0020or\u0020equal\u0020to\u0020\u0022\u00250\u0022.', options); 4818 } 4819 return true; 4820 }, 4821 step(value, options, field, context) { 4822 field.element.setAttribute('step', options); 4823 field.element.checkValidity(); 4824 if (field.element.validity.stepMismatch) { 4825 const val = Number(value); 4826 const step = Number(options); 4827 const msg = 'Please\u0020enter\u0020a\u0020valid\u0020value.\u0020The\u0020two\u0020nearest\u0020valid\u0020values\u0020are\u0020\u0022\u00250\u0022\u0020and\u0020\u0022\u00251\u0022.'; 4828 return strf(msg, Math.floor(val / step) * step, Math.ceil(val / step) * step); 4829 } 4830 return true; 4831 }, 4832 pattern(value, options, field, context) { 4833 field.element.setAttribute('pattern', options); 4834 field.element.checkValidity(); 4835 if (field.element.validity.patternMismatch) { 4836 return field.element.title 4837 ? strf('Please\u0020match\u0020the\u0020requested\u0020format\u003A\u0020\u00250.', field.element.title) 4838 : 'Please\u0020match\u0020the\u0020requested\u0020format.' 4839 } 4840 return true; 4841 }, 4842 email(value, options, field, context) { 4843 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; 4844 if (value.length > 0 && !rule.test(value)) { 4845 return 'Please\u0020enter\u0020a\u0020valid\u0020email\u0020address.'; 4846 } 4847 return true; 4848 }, 4849 password(value, options, field, context) { 4850 const rule = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/; 4851 if (value.length > 0 && !rule.test(value)) { 4852 return 'Please\u0020provide\u0020at\u0020least\u0020one\u0020upper\u0020case,\u0020one\u0020lower\u0020case,\u0020one\u0020digit\u0020and\u0020one\u0020special\u0020character\u0020\u0028\u0023\u003F\u0021\u0040\u0024\u0025\u005E\u0026\u002A\u002D\u0029'; 4853 } 4854 return true; 4855 }, 4856 equalTo(value, options, field, context) { 4857 const dependencyField = context.fields[options].element; 4858 if (value !== dependencyField.value) { 4859 const dependencyFieldName = 4860 dependencyField.label || 4861 dependencyField.title || 4862 (dependencyField.labels && dependencyField.labels[0] && dependencyField.labels[0].innerText) || 4863 dependencyField.name; 4864 return strf('This\u0020field\u0020value\u0020must\u0020be\u0020the\u0020same\u0020as\u0020\u0022\u00250\u0022.', dependencyFieldName); 4865 } 4866 return true; 4867 } 4868 }; 4869 4870 function raceSome(promises, pred) { 4871 return new Promise((resolve, reject) => { 4872 4873 if (promises.length === 0) { 4874 return resolve(); 4875 } 4876 4877 let settled = false, nDone = 0; 4878 4879 const resolveIf = v => { 4880 if (!settled && (pred(v) || ++nDone === promises.length)) { 4881 settled = true; 4882 resolve(v); 4883 } 4884 return v; 4885 } 4886 4887 promises.map(promise => {
4888 promise.then(resolveIf).catch(reason => { 4889 settled = true; 4890 reject(reason) 4891 }); 4892 return promise; 4893 }); 4894 }); 4895 } 4896 4897 const INPUT_ATTRIBUTE_RULES = {min: 'min', max: 'max', required: 'required', minlength: 'minlength', maxlength: 'maxlength', step: 'step', pattern: 'pattern'} 4898 const INPUT_TYPE_RULES = {email: 'email'} 4899 4900 function getRules(element) { 4901 let rules = {}; 4902 Object.keys(INPUT_ATTRIBUTE_RULES).forEach(attrName => { 4903 if (element.hasAttribute(attrName)) { 4904 rules[INPUT_ATTRIBUTE_RULES[attrName]] = element.getAttribute(attrName); 4905 } 4906 }) 4907 if (INPUT_TYPE_RULES[element.type]) { 4908 rules[INPUT_TYPE_RULES[element.type]] = true; 4909 } 4910 4911 if (element.dataset.validate) { 4912 try { 4913 const validateRulesStr = element.dataset.validate.replace(/'/g,"\""); 4914 Object.assign(rules, JSON.parse(validateRulesStr)); 4915 } catch (error) { 4916 console.error('Validator error. Cannot parse data-validate attribute of element:\n', element); 4917 } 4918 } 4919 4920 return rules; 4921 } 4922 4923 function isInvalidRuleResult(ruleState) { 4924 return typeof ruleState === 'string' || !ruleState || (ruleState.type && ruleState.content); 4925 } 4926 4927 async function runValidateFn(rule, options, value, field) { 4928 return formValidationRules[rule](value, options, field, this); 4929 } 4930 4931 function generateId() { 4932 let id; 4933 do { 4934 id = `${this.idPrefix}-${++this.idSeq}`; 4935 } while (document.getElementById(id)); 4936 return id; 4937 } 4938 4939 function isVisible(element) { 4940 const el = element.type !== 'hidden' ? element : (element.parentElement || {}); 4941 return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length) 4942 } 4943 4944 function elementWillValidate(element) { 4945 return (element.willValidate || element.type === 'hidden') 4946 && element.tagName !== 'BUTTON' 4947 && element.disabled === false 4948 && !(element.tagName === 'INPUT' && element.type === 'submit') 4949 && (element.hasAttribute('data-validate-hidden') 4950 || element.classList.contains('validate-hidden') 4951 || isVisible(element)) 4952 } 4953 4954 function createMessageContainer(el, fieldWrapperClassName) { 4955 if (! el.parentElement) { 4956 return; 4957 } 4958 const refocus = document.activeElement === el; 4959 const wrapper = document.createElement('div'); 4960 wrapper.classList.add.apply(wrapper.classList, fieldWrapperClassName.split(' ')); 4961 el.parentElement.insertBefore(wrapper, el); 4962 wrapper.appendChild(el); 4963 refocus && document.activeElement !== el && el.focus(); 4964 return wrapper; 4965 } 4966 4967 function containerNotFound(selector, el) { 4968 const msg = `Cannot find message container element ${selector} of ${el.name}`; 4969 console.error(msg, el); 4970 throw msg; 4971 } 4972 4973 function createTextInputFrom(el) { 4974 const text = document.createElement('INPUT'); 4975 text.type = 'text'; 4976 text.value = el.value; 4977 return text; 4978 } 4979 4980 function classNamesToSelector(classNames) { 4981 return classNames.split(' ') 4982 .filter(className => className.length > 0) 4983 .map(className => `.${className}`) 4984 .join('') 4985 } 4986 4987 function hasMessagesWrapper(field, messagesWrapperClassName) { 4988 return this.getMessageContainer(field).querySelector(classNamesToSelector(messagesWrapperClassName)); 4989 } 4990 4991 function getMessagesWrapper(field, messagesWrapperClassName) { 4992 if (hasMessagesWrapper.call(this, field, messagesWrapperClassName)) { 4993 return this.getMessageContainer(field).querySelector(classNamesToSelector(messagesWrapperClassName)); 4994 } 4995 4996 const msgWrapper = document.createElement('ul'); 4997 const msgId = generateId.call(this); 4998 msgWrapper.id = msgId; 4999 field.element.setAttribute('aria-errormessage', msgId); 5000 field.element.setAttribute('aria-describedby', msgId); 5001 msgWrapper.classList.add.apply(msgWrapper.classList, messagesWrapperClassName.split(' ')); 5002 if (field.validateOnChange) { 5003 msgWrapper.setAttribute('aria-live', 'polite'); 5004 } 5005 this.getMessageContainer(field).appendChild(msgWrapper); 5006 5007 return msgWrapper; 5008 } 5009 5010 function getCheckedValues(field) { 5011 const name = field.element.name.replace(/([\\"])/g, '\\$1'); 5012 const elements = field.element.form.querySelectorAll('input[name="' + name + '"]:checked'); 5013 return Array.from(elements).map(el => el.value); 5014 } 5015 5016 function escapeHtml(s) { 5017 const div = document.createElement('div') 5018 div.innerText = s; 5019 return div.innerHTML; 5020 } 5021 5022 function str(string) { 5023 const args = Array.from(arguments); 5024 args.splice(1, 0, 1); 5025 5026 return formatStr.apply(undefined, args); 5027 } 5028 5029 function strf() { 5030 const args = Array.from(arguments); 5031 args.splice(1, 0, 0); 5032 5033 return formatStr.apply(undefined, args); 5034 } 5035 5036 const formatStr = function (str, nStart) { 5037 const args = Array.from(arguments).slice(2); 5038 5039 return str.replace(/(%+)([0-9]+)/g, (m, p, n) => { 5040 const idx = parseInt(n) - nStart; 5041 5042 if (args[idx] === null || args[idx] === void 0) { 5043 return m; 5044 } 5045 return p.length % 2 5046 ? p.slice(0, -1).replace('%%', '%') + args[idx] 5047 : p.replace('%%', '%') + n; 5048 }) 5049 } 5050 5051 function formValidation(form, options = {}) { 5052 const formElement = form || this.$el; 5053 5054 if (formElement.dataset && formElement.dataset.options) { 5055 try { 5056 options = Object.assign(options, JSON.parse(formElement.dataset.options || '{}')); 5057 } catch (e) { 5058 throw new Error('Cannot read the form options from the data-options attribute: not val
5058id JSON'); 5059 } 5060 } 5061 5062 5063 if (formElement.tagName === 'FORM') { 5064 formElement.setAttribute('novalidate', ''); 5065 } else { 5066 console.error('formValidation can be initialized only on FORM element', formElement); 5067 return; 5068 } 5069 5070 options = Object.assign({ 5071 fieldWrapperClassName: 'field field-reserved', 5072 messagesWrapperClassName: 'messages', 5073 validClassName: 'field-success', 5074 invalidClassName: 'field-error', 5075 pageMessagesWrapperSelector: null, 5076 scrollToFirstError: true, 5077 }, options || {}); 5078 5079 return { 5080 state: { 5081 valid: false, 5082 }, 5083 fields: {}, 5084 idSeq: 0, 5085 idPrefix: formElement.id || 'vld-msg', 5086 setupFields(elements) { 5087 this.fields = {}; 5088 Array.from(elements).forEach(element => { 5089 if (elementWillValidate(element)) { 5090 this.setupField(element); 5091 } 5092 }); 5093 }, 5094 setupField(element) { 5095 if (! element) return; 5096 const onChange = !!element.dataset.onChange; 5097 if (elementWillValidate(element)) { 5098 const rules = getRules(element); 5099 if (Object.keys(rules).length > 0) { 5100 if (this.fields[element.name]) { 5101 Object.assign(this.fields[element.name].rules, rules); 5102 } else { 5103 this.fields[element.name] = { 5104 element, 5105 rules: rules, 5106 validateOnChange: onChange, 5107 state: { 5108 valid: null, 5109 rules: {} 5110 } 5111 } 5112 } 5113 } 5114 } else { 5115 console.error('Element will not validate', element); 5116 } 5117 }, 5118 onSubmit(event) { 5119 if (event.target.tagName === 'FORM') { 5120 event.preventDefault(); 5121 5122 this.validate() 5123 .then(() => event.target.submit()) 5124 .catch(invalidElements => {}); 5125 } 5126 }, 5127 onChange(event) { 5128 event.target.dataset.onChange = 'true'; 5129 if (!Object.keys(this.fields).length) { 5130 this.setupFields(formElement.elements); 5131 } 5132 if (!Object.keys(this.fields).includes(event.target.name)) { 5133 this.setupField(event.target); 5134 } 5135 const field = this.fields[event.target.name]; 5136 5137 this.validateField(field); 5138 field && field.element.removeAttribute('data-on-change') 5139 }, 5140 validateSafe() { 5141 return new Promise(resolve => this.validate().then(() => resolve(true)).catch(() => {})) 5142 }, 5143 validate() { 5144 if (!Object.keys(this.fields).length || !Object.keys(this.fields).length !== formElement.elements.length) { 5145 this.setupFields(formElement.elements); 5146 } 5147 return new Promise(async (resolve, reject) => { 5148 if (formElement.elements) { 5149 await raceSome(this.validateFields(), result => result !== true) 5150 const invalidFields = Object.values(this.fields).filter(field => !field.state.valid); 5151 this.state.valid = invalidFields.length === 0; 5152 if (this.state.valid) { 5153 resolve(); 5154 } else { 5155 if (options.scrollToFirstError && invalidFields.length > 0) { 5156 5157 5158 if (invalidFields[0].element.classList.contains('validate-hidden')) { 5159 const uid = invalidFields[0].element.dataset.uid; 5160 if (uid) { 5161 const parent = document.querySelector("#field_" + uid); 5162 if (parent) { 5163 parent.scrollIntoView(); 5164 } 5165 } 5166 } 5167 invalidFields[0].element.focus() 5168 invalidFields[0].element.select && invalidFields[0].element.select(); 5169 } 5170 reject(invalidFields.map(field => field.element)); 5171 } 5172 } 5173 }); 5174 }, 5175 validateFields() { 5176 const fields = Object.values(this.fields); 5177 5178 fields.forEach(field => { 5179 this.getMessageContainer(field).classList.remove(options.validClassName, options.invalidClassName) 5180 }); 5181 return fields.map(field => this.validateField(field)) 5182 }, 5183 validateField(field) { 5184 if (! field || ! elementWillValidate(field.element)) { 5185 return new Promise(resolve => resolve(true)) 5186 } 5187 5188 const uid = field.element.dataset.uid; 5189 if (uid) { 5190 const parent = document.querySelector("#field_" + uid); 5191 if (parent) { 5192 if (window.getComputedStyle(parent).display == 'none') { 5193 field.state.valid = true; 5194 return new Promise(resolve => resolve(true)); 5195 } 5196 } 5197 } 5198 5199 let value; 5200 if (field.element.type === 'checkbox') { 5201 value = getCheckedValues(field); 5202 } else if (field.element.type === 'radio') { 5203 value = getCheckedValues(field)[0] || undefined; 5204 } else if (field.element.tagName === 'SELECT' && field.element.multiple) { 5205 value = Array.from(field.element.selectedOptions).map(opt => opt.value); 5206 } else { 5207 value = field.element.value; 5208 } 5209 5210 const rules = field.rules || {}; 5211 5212 field.state.valid = true;
5213 this.showFieldState(field); 5214 5215 const fieldValidations = Object.keys(rules).filter(rule => formValidationRules[rule]).map(async rule => { 5216 return runValidateFn.call(this, rule, rules[rule], value, field).then(result => { 5217 field.state.rules[rule] = result; 5218 return result; 5219 }) 5220 }); 5221 5222 return new Promise(resolve => { 5223 Promise.all(fieldValidations).then(results => { 5224 field.state.valid = !elementWillValidate(field.element) || rules.length === 0 || !results.some(isInvalidRuleResult) 5225 this.showFieldState(field); 5226 resolve(field.state.valid); 5227 }) 5228 }); 5229 }, 5230 getMessagesByField(field) { 5231 const messages = []; 5232 const invalidRules = Object.keys(field.state.rules).filter(rule => isInvalidRuleResult(field.state.rules[rule])); 5233 5234 field.rules && Object.keys(field.rules).forEach((rule) => { 5235 if (invalidRules.includes(rule)) { 5236 const customMessage = field.element.getAttribute('data-msg-' + rule); 5237 const message = customMessage ? customMessage : field.state.rules[rule]; 5238 const ruleOptions = JSON.parse(JSON.stringify(field.rules[rule])); 5239 5240 if (typeof message === 'undefined' || message === null || (typeof message !== 'string' && ! message.type)) { 5241 messages.push(strf('Validation rule "%0" failed.', rule)); 5242 } else if (Array.isArray(ruleOptions)) { 5243 ruleOptions.unshift(message.type ? message.content : message); 5244 const content = strf.apply(null, ruleOptions); 5245 messages.push(message.type ? {type: message.type, content} : content); 5246 } else { 5247 const content = strf(message.type ? message.content : message, ruleOptions) 5248 messages.push(message.type ? {type: message.type, content} : content); 5249 } 5250 } 5251 }); 5252 return messages; 5253 }, 5254 /** @deprecated */ 5255 getFieldWrapper(field) { 5256 return this.getMessageContainer(field) 5257 }, 5258 getMessageContainer(field) { 5259 let container; 5260 const pageSelector = field.element.getAttribute('data-validation-container') || options.pageMessagesContainerSelector; 5261 if (pageSelector) { 5262 container = document.querySelector(pageSelector) 5263 || containerNotFound(pageSelector, field.element) 5264 } else { 5265 const containerSelector = classNamesToSelector(options.fieldWrapperClassName); 5266 container = field.element.closest(containerSelector) 5267 || createMessageContainer(field.element, options.fieldWrapperClassName) 5268 || containerNotFound(containerSelector, field.element); 5269 } 5270 5271 return container; 5272 }, 5273 showFieldState(field) { 5274 const container = this.getMessageContainer(field), 5275 hasErrorMessages = hasMessagesWrapper.call(this, field, options.messagesWrapperClassName), 5276 messages = this.getMessagesByField(field).map(m => { 5277 return m.type !== 'html' ? escapeHtml(m.type ? m.content : m) : m.content; 5278 }); 5279 container.classList.toggle(options.validClassName, field.state.valid && ! hasErrorMessages); 5280 container.classList.toggle(options.invalidClassName, !field.state.valid || hasErrorMessages); 5281 this.createHtmlErrorMessage(field, messages); 5282 5283 if (field.state.valid) { 5284 field.element.removeAttribute('aria-invalid'); 5285 } else { 5286 field.element.setAttribute('aria-invalid', 'true'); 5287 if (! document.activeElement) { 5288 field.element.focus(); 5289 } 5290 } 5291 }, 5292 removeMessages(field, messagesClass) { 5293 if (! hasMessagesWrapper.call(this, field, messagesClass || options.messagesWrapperClassName)) { 5294 return; 5295 } 5296 5297 const msgWrapper = getMessagesWrapper.call(this, field, messagesClass || options.messagesWrapperClassName); 5298 const messages = msgWrapper.querySelectorAll(`[data-msg-field='${field.element.name}']`);
5299 Array.from(messages).forEach(msg => msg.remove()); 5300 if (msgWrapper && msgWrapper.childElementCount === 0) { 5301 field.element.removeAttribute('aria-errormessage'); 5302 field.element.removeAttribute('aria-describedby'); 5303 msgWrapper.remove(); 5304 } 5305 }, 5306 createErrorMessage(field, messages) { 5307 const htmlMessages = (Array.isArray(messages) ? messages : [messages]).map(escapeHtml) 5308 this.createHtmlErrorMessage(field, htmlMessages); 5309 }, 5310 createHtmlErrorMessage(field, messages) { 5311 this.removeMessages(field, options.messagesWrapperClassName); 5312 field.element.removeAttribute('aria-errormessage'); 5313 field.element.removeAttribute('aria-describedby'); 5314 5315 if (!field.state.valid) { 5316 const msgWrapper = this.addHtmlMessages(field, options.messagesWrapperClassName, messages); 5317 field.element.setAttribute('aria-errormessage', msgWrapper.id); 5318 field.element.setAttribute('aria-describedby', msgWrapper.id); 5319 } 5320 }, 5321 /** @deprecated */ 5322 createMessage(field, message) { 5323 return this.addMessages(field, options.messagesWrapperClassName, message); 5324 }, 5325 addMessages(field, messagesClass, messages) { 5326 const htmlMessages = (Array.isArray(messages) ? messages : [messages]).map(escapeHtml) 5327 return this.addHtmlMessages(field, messagesClass, htmlMessages); 5328 }, 5329 addHtmlMessages(field, messagesClass, htmlMessages) { 5330 const msgWrapper = getMessagesWrapper.call(this, field, messagesClass); 5331 5332 (Array.isArray(htmlMessages) ? htmlMessages : [htmlMessages]).forEach((htmlMessage) => { 5333 const li = document.createElement('li'); 5334 li.innerHTML = htmlMessage; 5335 li.setAttribute('data-msg-field', field.element.name); 5336 msgWrapper.appendChild(li); 5337 }); 5338 5339 return msgWrapper; 5340 }, 5341 setField(name, value) { 5342 this.fields[name].element.value = value; 5343 this.fields[name].element.dispatchEvent((new Event('input'))); 5344 this.validateField(this.fields[name]); 5345 } 5346 } 5347 } 5348 5349 webfroms.formValidation = formValidation; 5350 webfroms.formValidation.rules = formValidationRules; 5351 webfroms.formValidation.setInputAttributeRuleName = (attrName, ruleName) => INPUT_ATTRIBUTE_RULES[attrName] = ruleName || attrName; 5352 webfroms.formValidation.setInputTypeRuleName = (typeName, ruleName) => INPUT_TYPE_RULES[typeName] = ruleName || typeName; 5353 webfroms.formValidation.addRule = (name, validator) => formValidationRules[name] = validator; 5354 5355 5356 webfroms.formValidation.addRule('dateFromTo', (value, options, field, context) => { 5357 const dateFrom = field.element; 5358 const dateTo = document.getElementById(options); 5359 5360 if (!field || !options || isNaN(dateFrom.valueAsNumber) || isNaN(dateTo.valueAsNumber)) { 5361 return true; 5362 } 5363 5364 const valid = dateFrom.valueAsNumber <= dateTo.valueAsNumber; 5365 5366 return !valid ? 'The\u0020\u0027From\u0027\u0020date\u0020must\u0020precede\u0020the\u0020\u0027To\u0027\u0020date.' : true; 5367 }); 5368 5369 5370 webfroms.formValidation.setInputAttributeRuleName('accept', 'file-type'); webfroms.formValidation.addRule('file-type', function (value, options, field, context) { 5371 if (field.element.type !== 'file' || !field.element.files.length || !field.element.accept) { 5372 return true; 5373 } 5374 5375 for (const file of Object.values(field.element.files)) { 5376 const allowedFileTypesArray = field.element.accept.split(','); 5377 const filenameSplit = file.name.split('.');
5378 const fileExtension = `.${filenameSplit[filenameSplit.length - 1]}`; 5379 5380 if (!allowedFileTypesArray.includes(fileExtension)) { 5381 return `${str('\u0022\u00251\u0022\u0020is\u0020not\u0020a\u0020valid\u0020file\u0020extension.', fileExtension)} Allowed\u0020file\u0020types: ${field.element.accept}`; 5382 } 5383 } 5384 5385 return true; 5386 }); 5387 5388 webfroms.formValidation.addRule('file-max-size', function (value, options, field, context) { 5389 if (!field.element.files.length || !options) { 5390 return true; 5391 } 5392 5393 for (const file of Object.values(field.element.files)) { 5394 const maxFileSize = options; 5395 5396 if (file.size > maxFileSize) { 5397 const maxSizeMb = maxFileSize / (1024 * 1024); 5398 return `Maximum\u0020file\u0020size: ${maxSizeMb} MB.`; 5399 } 5400 } 5401 5402 return true; 5403 }); 5404 5405 webfroms.formValidation.addRule('image-max-dimensions', async function (value, options, field, context) { 5406 if (!field.element.files.length) { 5407 return true; 5408 } 5409 5410 const {width: maxWidth, height: maxHeight} = options; 5411 5412 if ((!maxWidth && !maxHeight) || (maxWidth === 0 && maxHeight === 0)) { 5413 return true; } 5414 5415 try { 5416 const dimensions = await new Promise((resolve, reject) => { 5417 const reader = new FileReader(); 5418 reader.onload = function (e) { 5419 const img = new Image(); 5420 5421 img.onload = function () { 5422 resolve({ 5423 width: this.width, 5424 height: this.height 5425 }); 5426 }; 5427 5428 img.onerror = function (e) { 5429 reject(new Error('Failed to load the image.')); 5430 }; 5431 5432 img.src = e.target.result; 5433 }; 5434 5435 reader.onerror = function (e) { 5436 reject(new Error('Failed to read the file.')); 5437 }; 5438 5439 reader.readAsDataURL(field.element.files[0]); 5440 }); 5441 5442 const errorMessages = []; 5443 if (maxWidth && dimensions.width > maxWidth) { 5444 errorMessages.push( 5445 str( 5446 '\u0022\u00251\u0022\u0020width\u0020exceeds\u0020allowed\u0020value\u0020of\u0020\u00252\u0020px.', 5447 `${dimensions.width} px`, 5448 maxWidth 5449 ) 5450 ); 5451 } 5452 if (maxHeight && dimensions.height > maxHeight) { 5453 errorMessages.push( 5454 str( 5455 '\u0022\u00251\u0022\u0020height\u0020exceeds\u0020allowed\u0020value\u0020of\u0020\u00252\u0020px.', 5456 `${dimensions.height} px`, 5457 maxHeight 5458 ) 5459 ); 5460 } 5461 5462 if (errorMessages.length > 0) { 5463 return errorMessages.join(' '); 5464 } 5465 5466 return true; 5467 } catch (error) { 5468 console.error('Error:', error); 5469 return false; 5470 } 5471 }); 5472 5473 5474 webfroms.formValidation.addRule('alpha', function (value, options, field, context) { 5475 const rule = /^[a-zA-Z]+$/; 5476 if (value.length > 0 && !rule.test(value)) { 5477 return 'Please\u0020use\u0020letters\u0020only\u0020\u0028a\u002Dz\u0020or\u0020A\u002DZ\u0029\u0020in\u0020this\u0020field.'; 5478 } 5479 5480 return true; 5481 }); 5482 5483 webfroms.formValidation.addRule('alphanum', function (value, options, field, context) { 5484 const rule = /^[a-zA-Z0-9]+$/; 5485 if (value.length > 0 && !rule.test(value)) { 5486 return 'Please\u0020use\u0020only\u0020letters\u0020\u0028a\u002Dz\u0020or\u0020A\u002DZ\u0029\u0020
5486or\u0020numbers\u0020\u00280\u002D9\u0029\u0020in\u0020this\u0020field.\u0020No\u0020spaces\u0020or\u0020other\u0020characters\u0020are\u0020allowed.'; 5487 } 5488 5489 return true; 5490 }); 5491 5492 webfroms.formValidation.addRule('alphanum-with-spaces', function (value, options, field, context) { 5493 const rule = /^[a-zA-Z0-9 ]+$/; 5494 if (value.length > 0 && !rule.test(value)) { 5495 return 'Please\u0020use\u0020only\u0020letters\u0020\u0028a\u002Dz\u0020or\u0020A\u002DZ\u0029,\u0020numbers\u0020\u00280\u002D9\u0029\u0020or\u0020spaces\u0020only\u0020in\u0020this\u0020field.'; 5496 } 5497 5498 return true; 5499 }); 5500 5501 webfroms.formValidation.setInputTypeRuleName('url'); webfroms.formValidation.addRule('url', function (value, options, field, context) { 5502 const rule = /^(http|https|ftp):\/\/(([A-Z0-9]([A-Z0-9_-]*[A-Z0-9]|))(\.[A-Z0-9]([A-Z0-9_-]*[A-Z0-9]|))*)(:(\d+))?(\/[A-Z0-9~](([A-Z0-9_~-]|\.)*[A-Z0-9~]|))*\/?(.*)?$/i; 5503 if (value.length > 0 && !rule.test(value)) { 5504 return 'Please\u0020enter\u0020a\u0020valid\u0020URL.'; 5505 } 5506 5507 return true; 5508 }); 5509 5510 5511 webfroms.formValidation.addRule('minMax', (value, options, field, context) => { 5512 const min = field.element; 5513 const max = document.getElementById(options); 5514 5515 if (!min || !max || min.value === '' || max.value === '') { 5516 return true; 5517 } 5518 5519 const valid = !(Number(min.value) > Number(max.value)); 5520 5521 return !valid ? 'The\u0020\u0027Min\u0027\u0020value\u0020must\u0020be\u0020lower\u0020than\u0020the\u0020\u0027Max\u0027\u0020value.' : true; 5522 }); 5523 }((() => { 5524 if (!window.MageMe) window.MageMe = {}; 5525 if (!window.MageMe.WebForms) window.MageMe.WebForms = {} 5526 return window.MageMe.WebForms 5527 })()));</script>
vendor: 1 bytes, line 5527
5527
5528<script id="mm_validation"> 5529 'use strict'; 5530 (() => { 5531 const messages = { 5532 "validate-match-value": "Please make sure your input matches the <i>%1</i>.", 5533 "required-dropzone-file": "Please select a file.", 5534 "validate-options-checkbox-min": "Please check at least %1 options", 5535 "validate-options-checkbox-max": "Please check not more than %1", 5536 "validate-length-min": "The minimum length for this field is %1 characters.", 5537 "validate-length-max": "The maximum length for this field is %1 characters.", 5538 "required-entry": "This is a required field.", 5539 "pattern": "Invalid format.", 5540 "validate-email": "Please enter a valid email address.", 5541 "validate-number": "Please enter a valid number in this field.", 5542 "validate-number-range": "The value is not within the specified range.", 5543 "validate-customer-password": "Minimum length of this field must be equal or greater than %1 symbols. Leading and trailing spaces will be ignored.", 5544 "validate-customer-password-cl": "Minimum of different classes of characters in password is %1. Classes of characters: Lower Case, Upper Case, Digits, Special Characters.", 5545 "validate-select": "Please select an option.", 5546 "validate-one-required-by-name": "Please select one of the options.", 5547 "time": "Please enter a valid time, between 00:00 and 23:59", 5548 "validate-url": "Please enter a valid URL. Protocol is required (http://, https:// or ftp://).", 5549 }; 5550 5551 5552 MageMe.WebForms.formValidation.addRule( 5553 'validate-match-value', function (value, options, field, context) { 5554 const matchInputId = field.element.getAttribute('data-match-id'); 5555 let validateMessage = "Incorrect match validation data, please check validation rules."; 5556 if (!matchInputId) { 5557 return validateMessage; 5558 } 5559 const matchInput = document.getElementById(matchInputId); 5560 if (!matchInput) { 5561 return validateMessage; 5562 } 5563 const label = document.querySelector("label[for='" + matchInputId + "']"); 5564 const fieldLabel = label.value ? label.value : matchInput.getAttribute('placeholder'); 5565 validateMessage = messages["validate-match-value"].replace('%1', fieldLabel); 5566 return matchInput.value === value ? true : validateMessage; 5567 } 5568 ); 5569 MageMe.WebForms.formValidation.addRule( 5570 'validate-dropzone', function (value, options, field, context) { 5571 const uid = field.element.dataset.uid; 5572 if (!uid) { 5573 return messages["validate-dropzone"]; 5574 } 5575 return !document.getElementById('field' + uid + '_preview').querySelectorAll('.drop-zone-error').length ? 5576 true : messages["validate-dropzone"]; 5577 } 5578 ); 5579 MageMe.WebForms.formValidation.addRule( 5580 'validate-dropzone-required', function (value, options, field, context) { 5581 let result = false;
5582 const uid = field.element.dataset.uid; 5583 if (!uid) { 5584 return messages["validate-dropzone-required"]; 5585 } 5586 if (document.getElementById('field' + uid + '_preview').querySelectorAll('.drop-zone-preview-file').length) { 5587 result = true; 5588 } 5589 if (!result) { 5590 if (getDropzoneUploadedCount(uid)) { 5591 result = true; 5592 } 5593 } 5594 return result ? true : messages["validate-dropzone-required"]; 5595 } 5596 ); 5597 MageMe.WebForms.formValidation.addRule( 5598 'validate-dropzone-max-files', function (value, options, field, context) { 5599 const uid = field.element.dataset.uid, 5600 max = field.element.getAttribute('data-max-files'); 5601 if (!uid) { 5602 return messages["validate-dropzone-max-files"]; 5603 } 5604 if (!max) { 5605 return true; 5606 } 5607 const newFiles = document.getElementById('field' + uid + '_preview').querySelectorAll('.drop-zone-preview-file').length; 5608 const uploaded = getDropzoneUploadedCount(uid); 5609 return (newFiles + uploaded <= max) ? true : messages["validate-dropzone-max-files"]; 5610 } 5611 ); 5612 MageMe.WebForms.formValidation.addRule( 5613 'required-dropzone-file', function (value, options, field, context) { 5614 let result = isEmptyNoTrim(value), 5615 ovId, 5616 uid = field.element.dataset.uid; 5617 5618 if (!result) { 5619 ovId = document.getElementById(field.element.getAttribute('id') + '_value'); 5620 5621 if (ovId) { 5622 result = isEmptyNoTrim(ovId.value); 5623 } 5624 } 5625 5626 if (!result && uid) { 5627 if (getDropzoneUploadedCount(uid)) { 5628 result = true; 5629 } 5630 } 5631 5632 return result ? true : messages["required-dropzone-file"]; 5633 } 5634 ); 5635 MageMe.WebForms.formValidation.addRule( 5636 'validate-options-checkbox-min', 5637 function (value, options, field, context) { 5638 const uid = field.element.dataset.uid, 5639 count = parseInt(options); 5640 if (isNaN(count)) { 5641 return "Incorrect min value, please check validation rules."; 5642 } 5643 const counter = document.getElementById('field_'+uid).querySelectorAll('input:checked').length; 5644 return (counter === 0 || counter >= count) ? true : messages["validate-options-checkbox-min"].replace('%1', count); 5645 } 5646 ); 5647 MageMe.WebForms.formValidation.addRule( 5648 'validate-options-checkbox-max', 5649 function (value, options, field, context) { 5650 const uid = field.element.dataset.uid, 5651 count = parseInt(options); 5652 if (isNaN(count)) { 5653 return "Incorrect max value, please check validation rules."; 5654 } 5655 const counter = document.getElementById('field_'+uid).querySelectorAll('input:checked').length; 5656 return (counter === 0 || counter <= count) ? true : messages["validate-options-checkbox-max"].replace('%1', count); 5657 } 5658 ); 5659 MageMe.WebForms.formValidation.addRule( 5660 'validate-length-min', 5661 function (value, options, field, context) { 5662 const length = parseInt(options); 5663 if (isNaN(length)) { 5664 return "Incorrect min value, please check validation rules."; 5665 } 5666 if (length <= 0 || !value) { 5667 return true; 5668 } 5669 return value.length >= length ? true : messages["validate-length-min"].replace('%1', length); 5670 } 5671 ); 5672 MageMe.WebForms.formValidation.addRule( 5673 'validate-length-max', 5674 function (value, options, field, context) { 5675 const length = parseInt(options); 5676 if (isNaN(length)) { 5677 return "Incorrect max value, please check validation rules."; 5678 } 5679 if (!value) { 5680 return true; 5681 } 5682 return value.length <= length ? true : messages["validate-length-max"].replace('%1', length); 5683 } 5684 ); 5685 MageMe.WebForms.formValidation.addRule( 5686 'validate-intl-phone-number', 5687 function (value, options, field, context) { 5688 if (!window.intlTelInput) { 5689 return true; 5690 } 5691 if (!value) { 5692 return true; 5693 } 5694 const phone = window.intlTelInputGlobals.getInstance(field.element); 5695 return phone.isValidNumber() ? true : "Incorrect number."; 5696 } 5697 ); 5698 MageMe.WebForms.formValidation.addRule( 5699 'validate-input-mask-complete', 5700 function (value, options, field, context) { 5701 if (!window.Inputmask) { 5702 return true; 5703 } 5704 if (!value) { 5705 return true; 5706 } 5707 return field.element.inputmask.isComplete() ? true : "Incorrect value."; 5708 } 5709 ); 5710 MageMe.WebForms.formValidation.addRule( 5711 'mm-pattern', 5712 function (value, options, field, context) { 5713 const pattern = options; 5714 const flags = field.element.getAttribute('data-mm-pattern-flags') ?? ''; 5715 if (!value) { 5716 return true; 5717 } 5718 return new RegExp(pattern, flags).test(value) ? true : "Invalid format."; 5719 } 5720 ); 5721 5722 5723 5724 MageMe.WebForms.formValidation.addRule( 5725 'required-entry', function (value, options, field, context) { 5726 return !isEmpty(value) ? true : messages["required-entry"]; 5727 } 5728 ); 5729 MageMe.WebForms.formValidation.addRule( 5730 'pattern', function (value, options, field, context) { 5731 if (isEmptyNoTrim(value)) { 5732 return true; 5733 } 5734 return new RegExp(options).test(value) ? true : messages["pattern"]; 5735 } 5736 ); 5737 MageMe.WebForms.formValidation.addRule( 5738 'validate-email', function (value, options, field, context) { 5739 if (isEmptyNoTrim(value)) { 5740 return true; 5741 } 5742 return /^([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.test(value) ? 5743 true : messages["validate-email"]; 5744 5745 } 5746 ); 5747 MageMe.WebForms.formValidation.addRule( 5748 'validate-number', function (value, options, field, context) { 5749 if (isEmptyNoTrim(value)) { 5750 return true; 5751 } 5752 return (!isNaN(parseNumber(value)) && /^\s*-?\d*(\.\d*)?\s*$/.test(value)) ? true : messages["validate-number"]; 5753 5754 } 5755 ); 5756 MageMe.WebForms.formValidation.addRule( 5757 'validate-number-range', function (value, options, field, context) { 5758 let numValue, dataAttrRange, classNameRange, result, range, m, classes, ii; 5759 5760 if (isEmptyNoTrim(value)) { 5761 return true; 5762 } 5763 5764 numValue = parseNumber(value); 5765 5766 if (isNaN(numValue)) { 5767 return messages["validate-number-range"]; 5768 } 5769 5770 dataAttrRange = /^(-?[\d.,]+)?-(-?[\d.,]+)?$/; 5771 classNameRange = /^number-range-(-?[\d.,]+)?-(-?[\d.,]+)?$/; 5772 result = true; 5773 range = options; 5774 5775 if (typeof range === 'string') { 5776 m = dataAttrRange.exec(range); 5777 5778 if (m) { 5779 result = result && isBetween(numValue, m[1], m[2]); 5780 } else { 5781 result = false;
5782 } 5783 } else if (field.element && field.element.className) { 5784 classes = field.element.className.split(' '); 5785 ii = classes.length; 5786 5787 while (ii--) { 5788 range = classes[ii]; 5789 m = classNameRange.exec(range); 5790 5791 if (m) { 5792 result = result && isBetween(numValue, m[1], m[2]); 5793 break; 5794 } 5795 } 5796 } 5797 5798 return result ? true : messages["validate-number-range"]; 5799 } 5800 ); 5801 MageMe.WebForms.formValidation.addRule( 5802 'validate-customer-password', function (value, options, field, context) { 5803 let counter = 0; 5804 const passwordMinLength = field.element.getAttribute('data-password-min-length'), 5805 passwordMinCharacterSets = field.element.getAttribute('data-password-min-character-sets'), 5806 pass = value.trim(); 5807 5808 if (pass.length < passwordMinLength) { 5809 return messages["validate-customer-password"].replace('%1', passwordMinLength); 5810 } 5811 5812 if (pass.match(/\d+/)) { 5813 counter++; 5814 } 5815 5816 if (pass.match(/[a-z]+/)) { 5817 counter++; 5818 } 5819 5820 if (pass.match(/[A-Z]+/)) { 5821 counter++; 5822 } 5823 5824 if (pass.match(/[^a-zA-Z0-9]+/)) { 5825 counter++; 5826 } 5827 5828 if (counter < passwordMinCharacterSets) { 5829 return messages["validate-customer-password-cl"].replace('%1', passwordMinCharacterSets); 5830 } 5831 5832 return true; 5833 } 5834 ); 5835 MageMe.WebForms.formValidation.addRule( 5836 'validate-select', function (value, options, field, context) { 5837 return (value !== 'none' && value != null && value.length !== 0) ? true : messages["validate-select"]; 5838 } 5839 ); 5840 MageMe.WebForms.formValidation.addRule( 5841 'validate-one-required-by-name', function (value, options, field, context) { 5842 const name = field.element.name.replace(/([\\"])/g, '\\$1'); 5843 options = options === true ? 'input[name="' + name + '"]:checked' : options; 5844 return field.element.form.querySelectorAll(options).length ? true : messages["validate-one-required-by-name"]; 5845 } 5846 ); 5847 MageMe.WebForms.formValidation.addRule( 5848 'time', function (value, options, field, context) { 5849 if (isEmptyNoTrim(value)) { 5850 return true; 5851 } 5852 return /^([01]\d|2[0-3])(:[0-5]\d){0,2}$/.test(value) ? true : messages["time"]; 5853 } 5854 ); 5855 MageMe.WebForms.formValidation.addRule( 5856 'validate-url', function (value, options, field, context) { 5857 if (isEmptyNoTrim(value)) { 5858 return true; 5859 } 5860 value = (value || '').replace(/^\s+/, '').replace(/\s+$/, ''); 5861 5862 return (/^(http|https|ftp):\/\/(([A-Z0-9]([A-Z0-9_-]*[A-Z0-9]|))(\.[A-Z0-9]([A-Z0-9_-]*[A-Z0-9]|))*)(:(\d+))?(\/[A-Z0-9~](([A-Z0-9_~-]|\.)*[A-Z0-9~]|))*\/?(.*)?$/i).test(value) ? 5863 true : messages["validate-url"]; 5864 } 5865 ); 5866 5867 /** 5868 * Uploaded count 5869 * 5870 * @param uid - From uid 5871 * @returns {number} 5872 */ 5873 function getDropzoneUploadedCount(uid) { 5874 const selectAll = document.getElementById(uid + 'selectall'); 5875 if (selectAll && selectAll.checked) { 5876 return 0; 5877 } 5878 const filePool = document.getElementById(uid + 'filepool'); 5879 return filePool ? filePool.querySelectorAll("input[name^='delete_file_']:not(:checked)").length : 0; 5880 } 5881 5882 /** 5883 * Check if string is empty with trim. 5884 * 5885 * @param {String} value 5886 * @return {Boolean} 5887 */ 5888 function isEmpty(value) { 5889 return value === '' || value == null || value.length === 0 || /^\s+$/.test(value); 5890 } 5891 5892 /** 5893 * Check if string is empty no trim. 5894 * 5895 * @param {String} value 5896 * @return {Boolean} 5897 */ 5898 function isEmptyNoTrim(value) { 5899 return value === '' || value == null || value.length === 0; 5900 } 5901 5902 /** 5903 * Checks if {value} is between numbers {from} and {to}. 5904 * 5905 * @param {String} value 5906 * @param {String} from 5907 * @param {String} to 5908 * @return {Boolean} 5909 */ 5910 function isBetween(value, from, to) { 5911 return (from === null || from === '' || value >= parseNumber(from)) && 5912 (to === null || to === '' || value <= parseNumber(to)); 5913 } 5914 5915 /** 5916 * Parse price string. 5917 * 5918 * @param {String} value 5919 * @return {Number} 5920 */ 5921 function parseNumber(value) { 5922 let isDot, isComa; 5923 5924 if (typeof value !== 'string') { 5925 return parseFloat(value); 5926 } 5927 isDot = value.indexOf('.'); 5928 isComa = value.indexOf(','); 5929 5930 if (isDot !== -1 && isComa !== -1) { 5931 if (isComa > isDot) { 5932 value = value.replace('.', '').replace(',', '.'); 5933 } else { 5934 value = value.replace(',', ''); 5935 } 5936 } else if (isComa !== -1) { 5937 value = value.replace(',', '.'); 5938 } 5939 5940 return parseFloat(value); 5941 } 5942 5943 /** 5944 * Removes HTML tags and space characters, numbers and punctuation. 5945 * 5946 * @param {String} value - Value being stripped. 5947 * @return {String} 5948 */ 5949 function stripHtml(value) { 5950 return value.replace(/<.[^<>]*?>/g, ' ').replace(/ | /gi, ' ') 5951 .replace(/[0-9.(),;:!?%#$'"_+=\/-]*/g, ''); 5952 } 5953 5954 MageMe.WebForms.formValidation.utils = { 5955 getDropzoneUploadedCount: getDropzoneUploadedCount, 5956 isEmpty: isEmpty, 5957 isEmptyNoTrim: isEmptyNoTrim, 5958 isBetween: isBetween, 5959 parseNumber: parseNumber,
5960 stripHtml: stripHtml 5961 }; 5962 })();</script>
vendor: 1 bytes, line 5962
5962
5963<script> 5964 'use strict'; 5965 5966 (() => { 5967 if (typeof window.MageMe !== 'object') { 5968 window.MageMe = {}; 5969 } 5970 if (typeof window.MageMe.WebForms !== 'object') { 5971 window.MageMe.WebForms = {}; 5972 } 5973 window.MageMe.WebForms.loadValidation = () => { 5974 if (!document.getElementById("mm_validation")) { 5975 return fetch("https://www.banham.co.uk/webforms/form/validation/").then( 5976 response => response.text() 5977 ).then(html => { 5978 if (!document.getElementById("mm_validation")) 5979 document.head.append(document.createRange().createContextualFragment(html)); 5980 }); 5981 } else { 5982 return Promise.resolve(); 5983 } 5984 }; 5985 window.MageMe.WebForms.customerData = () => { 5986 return { 5987 customer: false, 5988 receiveCustomerData(data) { 5989 if (data.webforms) { 5990 this.customer = data.webforms; 5991 } 5992 }, 5993 } 5994 }; 5995 })();</script>
vendor: 1 bytes, line 5995
5995
5996<script> 5997 window.loqateFindUrl = "https:\/\/www.banham.co.uk\/loqate\/capture\/find\/"; 5998 window.loqateRetrieveUrl = "https:\/\/www.banham.co.uk\/loqate\/capture\/retrieve\/"; 5999</script>
vendor: 1 bytes, line 5999
5999
6000<script type="module"> 6001 6002 import { bbPCAForm } from 'https://www.banham.co.uk/static/version1790667712/frontend/BraveBison/default/en_GB/Hyva_MageMeWebForms/js/alpine-components/bb-pca-form.js'; 6003 6004 if (!window.bbPCAFormRegistered) { 6005 console.log('Loading bbPCAForm component'); 6006 window.bbPCAFormRegistered = true; 6007 if (window.Alpine) { 6008 window.Alpine.data('bbPCAForm', bbPCAForm); 6009 } else { 6010 document.addEventListener('alpine:init', () => { 6011 window.Alpine.data('bbPCAForm', bbPCAForm); 6012 }); 6013 } 6014 } 6015</script>
vendor: 1 bytes, line 6015
6015
6016<script> 6017 'use strict'; 6018 (() => { 6019 document.querySelectorAll('input[name="field[1778][]"]').forEach((el) => { 6020 el.initValidation = function (form) { 6021 el.parentElement.parentElement.parentElement.addEventListener('focusout', (e) => { 6022 if ( 6023 e.relatedTarget !== e.currentTarget && 6024 !e.currentTarget.contains(e.relatedTarget) 6025 ) { 6026 form.validateField(el); 6027 } 6028 }); 6029 el.parentElement.parentElement.parentElement.tabIndex = -1; 6030 } 6031 }); 6032 })(); 6033</script>
vendor: 1 bytes, line 6033
6033
6034<script> 6035 function mmWebformsTextareaDatafieldyk8I5y8() { 6036 return Object.assign( 6037 MageMe.WebForms.customerData(), 6038 { 6039 setCustomerData(event) { 6040 this.receiveCustomerData(event.detail.data) 6041 if (this.$el) { 6042 this.$el.value = this.customer[this.$el.dataset.hyvacspCustomerData] ?? ''; 6043 } 6044 }, 6045 } 6046 ); 6047 } 6048 window.addEventListener('alpine:init', () => Alpine.data('mmWebformsTextareaDatafieldyk8I5y8', mmWebformsTextareaDatafieldyk8I5y8), {once: true});</script>
vendor: 1 bytes, line 6048
6048
6049<script> 6050 function mmWebformsEmailDatafieldyk8I5y2() { 6051 return Object.assign( 6052 MageMe.WebForms.customerData(), 6053 { 6054 trimSpace() { 6055 if (this.$el.value.search(' ') >= 0) { 6056 this.$el.value =Â this.$el.value.replace(' ',''); 6057 } 6058 }, 6059 setCustomerData(event) { 6060 this.receiveCustomerData(event.detail.data) 6061 if (this.$el) { 6062 this.$el.value = this.customer['customer_email'] ?? ''; 6063 } 6064 }, 6065 } 6066 ); 6067 } 6068 window.addEventListener('alpine:init', () => Alpine.data('mmWebformsEmailDatafieldyk8I5y2', mmWebformsEmailDatafieldyk8I5y2), {once: true});</script>
vendor: 1 bytes, line 6068
6068
6069<script> 6070 'use strict'; 6071 6072 (() => { 6073 if (!window.MageMe || !MageMe.loader) { 6074 return; 6075 } 6076 MageMe.loader.addCssOrdered( 6077 'https://www.banham.co.uk/static/version1790667712/frontend/BraveBison/default/en_GB/Hyva_MageMeWebForms/css/field/datepicker.min.css', 6078 '40' 6079 ); 6080 MageMe.loader.addScript("Datepicker", 6081 "https\u003A\u002F\u002Fwww.banham.co.uk\u002Fstatic\u002Fversion1790667712\u002Ffrontend\u002FBraveBison\u002Fdefault\u002Fen_GB\u002FHyva_MageMeWebForms\u002Fjs\u002Fdatepicker.min.js", 6082 () => { 6083 const isPastDisabled = true; 6084 const isFutureDisabled = false; 6085 const isTodayDisabled = false; 6086 const isWeekNumbersShowed = false; 6087 const pastOffset = 0; 6088 const futureOffset = 0; 6089 const disabledWeekDays = [6,0]; 6090 const format = convertFormat('d/M/Y', 'date'); 6091 const today = new Date(); 6092 const elm = document.getElementById('fieldyk8I5y1032'); 6093 6094 let config = { 6095 autohide: true, 6096 datesDisabled: [], 6097 showWeekNumbers: isWeekNumbersShowed, 6098 daysOfWeekDisabled: [], 6099 weekStart: 1, 6100 format: format 6101 }; 6102 if (config.rtl) { 6103 $('#fieldyk8I5y1032_calendar').addClass('rtl'); 6104 } 6105 if (isPastDisabled) { 6106 config.minDate = new Date(); 6107 if (pastOffset) { 6108 config.minDate.setDate(config.minDate.getDate() + pastOffset) 6109 } 6110 } 6111 if (isFutureDisabled) { 6112 config.maxDate = new Date(); 6113 if (futureOffset) { 6114 config.maxDate.setDate(config.maxDate.getDate() + futureOffset) 6115 } 6116 } 6117 if (isTodayDisabled) { 6118 config.datesDisabled.push(today); 6119 } 6120 for (let i = 0; i < disabledWeekDays.length; i++) { 6121 config.daysOfWeekDisabled.push(disabledWeekDays[i]); 6122 } 6123 Datepicker.locales.en = { 6124 days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], 6125 daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], 6126 daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"], 6127 months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "
6127November", "December"], 6128 monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], 6129 today: "Today", 6130 clear: "Clear", 6131 titleFormat: "MM y", 6132 format: "d/M/Y", 6133 weekStart: 0 6134 }; 6135 6136 new Datepicker(elm, config); 6137 }); 6138 })(); 6139</script>
vendor: 1 bytes, line 6139
6139
6140<script> 6141 'use strict'; 6142 (() => { 6143 const input = document.getElementById('fieldyk8I5y380'); 6144 if (input) { 6145 input.initValidation = function (form) { 6146 input.parentElement.addEventListener('focusout', (e) => { 6147 if ( 6148 e.relatedTarget !== e.currentTarget && 6149 !e.currentTarget.contains(e.relatedTarget) 6150 ) { 6151 form.validateField(input); 6152 } 6153 }); 6154 input.parentElement.tabIndex = -1; 6155 } 6156 } 6157 })();</script>
vendor: 1 bytes, line 6157
6157
6158<script> 6159function stepProgress() { 6160 return { 6161 totalSteps: 1, 6162 currentStep: 1, 6163 items: [], 6164 6165 init() { 6166 const list = this.$refs.stepList; 6167 if (!list) return; 6168 6169 // Initialize items (exclude logically hidden) 6170 const computeItems = () => Array.from(list.querySelectorAll('li')) 6171 .filter(li => !li.classList.contains('mm-logic-hidden')); 6172 6173 const detectCurrentIndex = (arr) => { 6174 // Try common active markers that your wizard might use 6175 let idx = arr.findIndex(li => 6176 li.classList.contains('is-active') || 6177 li.classList.contains('active') || 6178 li.getAttribute('aria-current') === 'step' 6179 ); 6180 // Fallback: if none marked, assume first visible step 6181 if (idx < 0) idx = 0; 6182 return idx + 1; // 1-based 6183 }; 6184 6185 // Set initial state 6186 this.items = computeItems(); 6187 this.totalSteps = this.items.length || 1; 6188 this.currentStep = detectCurrentIndex(this.items); 6189 6190 // Watch for step visibility or active class changes 6191 const updateState = () => { 6192 this.items = computeItems(); 6193 this.totalSteps = this.items.length || 1; 6194 this.currentStep = Math.min(this.currentStep, this.totalSteps) || 1; 6195 6196 // If the wizard toggled which item is active, respect it 6197 const activeIdx = this.items.findIndex(li => 6198 li.classList.contains('is-active') || 6199 li.classList.contains('active') || 6200 li.getAttribute('aria-current') === 'step' 6201 ); 6202 if (activeIdx >= 0) this.currentStep = activeIdx + 1; 6203 }; 6204 6205 const mo = new MutationObserver(updateState); 6206 mo.observe(list, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'aria-current'] }); 6207 6208 // Also sync on anchor clicks (in case wizard uses hash navigation) 6209 Array.from(list.querySelectorAll('li > a')).forEach((a, i) => { 6210 a.addEventListener('click', () => { 6211 // Update current step optimistically;
6211 observer will correct if needed 6212 const visibleIndex = computeItems().indexOf(a.closest('li')); 6213 if (visibleIndex >= 0) this.currentStep = visibleIndex + 1; 6214 }); 6215 }); 6216 }, 6217 6218 percent() { 6219 return (this.currentStep / this.totalSteps) * 100; 6220 } 6221 } 6222} 6223// data layer for survey 6224window.surveyTypeValue = ''; 6225window.selectedCategories = []; 6226 6227function updateSurveyType() { 6228 let currentStepNumber = document.querySelector('span[x-text="currentStep"]')?.textContent || ''; 6229 if (currentStepNumber === '1') { 6230 window.selectedCategories = Array.from( 6231 document.querySelectorAll('.mm-swatches input[type=checkbox]:checked') 6232 ).map(el => el.value); 6233 window.surveyTypeValue = window.selectedCategories.length > 0 ? window.selectedCategories.join(', ') : ''; 6234 } 6235} 6236 6237document.querySelectorAll('.mm-swatches input[type=checkbox]').forEach(cb => { 6238 cb.addEventListener('change', updateSurveyType); 6239}); 6240 6241updateSurveyType(); 6242 6243document.addEventListener('change', function(e) { 6244 if (e.target && e.target.matches('.webforms-fields-enquiry-type select.hyva-select')) { 6245 e.target.dataset.userChanged = 'true'; 6246 } 6247}); 6248 6249function handleNextStep() {
6250 let currentStepNumber = parseInt(document.querySelector('span[x-text="currentStep"]')?.textContent || '', 10) + 1; 6251 console.log('Handling next step, current step:', currentStepNumber); 6252 window.dataLayer = window.dataLayer || []; 6253 const enquiryTopic = document.getElementById('enquiry-topic'); 6254 var selectEl = document.querySelector('.webforms-fields-enquiry-type select.hyva-select'); 6255 var survey_client_type = selectEl ? selectEl.value : ''; 6256 var enquiryTopicVal = (enquiryTopic && enquiryTopic.value) ? (window.surveyTypeVal || enquiryTopic.value) : (window.surveyTypeVal || ''); 6257 var onProductPage = !!window.product_name; 6258 var _cats = window.selectedCategories.length > 0 6259 ? [...window.selectedCategories] 6260 : (!onProductPage && enquiryTopicVal ? [enquiryTopicVal] : []); 6261 if (!onProductPage && enquiryTopicVal && !_cats.includes(enquiryTopicVal)) { 6262 _cats.unshift(enquiryTopicVal); 6263 } 6264 if (selectEl && selectEl.value && selectEl.dataset.userChanged === 'true' && !_cats.includes(selectEl.value)) { 6265 _cats.push(selectEl.value); 6266 } 6267 if (_cats.length === 0 && selectEl && selectEl.value) { 6268 _cats.push(selectEl.value); 6269 } 6270 const categoriesToUse = _cats; 6271 if (categoriesToUse.length > 0) { 6272 window.dataLayer.pushedSteps = window.dataLayer.pushedSteps || []; 6273 6274 categoriesToUse.forEach(category => { 6275 const eventKey = `step_${currentStepNumber}_${category}`; 6276 6277 if (!window.dataLayer.pushedSteps.includes(eventKey)) { 6278 const dataLayerEvent ={ 6279 event: 'survey_screen', 6280 site_type: window.site_type, 6281 logged_in_status: false, 6282 journey_type: 'product', 6283 screen_number: currentStepNumber, 6284 page_name: document.querySelector('.step-tab-panel.form-step.active .hyva-fieldset-label span')?.textContent || '', 6285 survey_opening: window.surveyOpening, 6286 survey_type: category, 6287 }; 6288 if (survey_client_type){ 6289 dataLayerEvent.survey_client_type = survey_client_type; 6290 } 6291 window.dataLayer.push(dataLayerEvent); 6292 window.dataLayer.pushedSteps.push(eventKey); 6293 } 6294 }); 6295 } 6296 6297 if (currentStepNumber === 2 && categoriesToUse.length > 0) { 6298 categoriesToUse.forEach(category => { 6299 const eventKey = `step_${currentStepNumber}_${category}_category_selected`; 6300 6301 if (!window.dataLayer.pushedSteps.includes(eventKey)) { 6302 window.dataLayer.push({ 6303 event: 'survey_category_selected', 6304 site_type: window.site_type, 6305 logged_in_status: false, 6306 journey_type: 'product', 6307 screen_number: currentStepNumber, 6308 survey_opening: window.surveyOpening, 6309 survey_type: category, 6310 6311 }); 6312 window.dataLayer.pushedSteps.push(eventKey); 6313 } 6314 }); 6315 } 6316} 6317 6318window.user_id = ''; 6319window.logged_in_status = false; 6320window.hasProduct = false; 6321window.product_id = ''; 6322window.product_name = ''; 6323window.surveyType = ''; 6324window.surveyOpening = window.surveyOpening || 'Opened from CTA'; 6325</script>
vendor: 1 bytes, line 6325
6325
6326<script> 6327 'use strict'; 6328 6329 window.addEventListener('private-content-loaded', () => { 6330 MageMe.loader.addScript('swal', "https\u003A\u002F\u002Fwww.banham.co.uk\u002Fstatic\u002Fversion1790667712\u002Ffrontend\u002FBraveBison\u002Fdefault\u002Fen_GB\u002FMageMe_Core\u002Fjs\u002Fsweetalert2.min.js", () => { 6331 MageMe.loader.addScript('tingle', "https\u003A\u002F\u002Fwww.banham.co.uk\u002Fstatic\u002Fversion1790667712\u002Ffrontend\u002FBraveBison\u002Fdefault\u002Fen_GB\u002FMageMe_Core\u002Fjs\u002Ftingle.min.js", () => { 6332 const options = { 6333 "submitUrl": "https://www.banham.co.uk/webforms/form/submit/ajax/1/", 6334 "uid": "yk8I5y", 6335 "formId": "2", 6336 "ajax": true, 6337 "success": false, 6338 "displayAfterSubmission": false, 6339 "scrollAfterSubmission": true, 6340 "showReview": false, 6341 "saveDataLocal": false, 6342 "useCaptcha": true, 6343 "captchaResponseName": "g-recaptcha-response", 6344 "messages": { 6345 "success": "Success!", 6346 "redirecting": "Redirecting", 6347 "complete": "Complete", 6348 "error": "Error", 6349 "captchaTxt": "Please click on the Captcha", 6350 "errorTxt": "Error(s) occurred. Please try again.", 6351 "unknownTxt": "Unknown error(s) occurred" 6352 } 6353 } 6354 6355 options.afterInit = (form) => { 6356 document.body.addEventListener('touchstart', MageMe.WebForms.loadValidation, {once: true}); 6357 document.body.addEventListener('mouseover', MageMe.WebForms.loadValidation, {once: true}); 6358 } 6359 6360 options.validationCb = (form, valid) => new Promise(async (resolve, reject) => { 6361 if (!valid) { 6362 resolve(false); 6363 } 6364 MageMe.WebForms.loadValidation().then(() => { 6365 return MageMe.WebForms.formValidation(form.controls.form, { 6366 fieldWrapperClassName: 'control', 6367 messagesWrapperClassName: 'hyva-validation' 6368 }); 6369 }).then(validator => { 6370 return validator.validate(); 6371 }).then(() => { 6372 resolve(true); 6373 }).catch((er) => { 6374 resolve(false); 6375 }); 6376 }); 6377 options.fieldValidationCb = (field, form, valid) => new Promise(async (resolve, reject) => { 6378 if (!valid) { 6379 resolve(false); 6380 } 6381 MageMe.WebForms.loadValidation().then(() => { 6382 return MageMe.WebForms.formValidation(form.controls.form, { 6383 fieldWrapperClassName: 'control', 6384 messagesWrapperClassName: 'hyva-validation' 6385 }); 6386 }).then(validator => { 6387 validator.setupField(field); 6388 const fieldObj = validator.fields[field.name]; 6389 return validator.validateField(fieldObj); 6390 }).then((isValid) => { 6391 resolve(isValid); 6392 }).catch((er) => { 6393 resolve(false); 6394 }); 6395 }); 6396 6397 options.getFormKey = () => { 6398 return hyva.getCookie('form_key'); 6399 }; 6400 options.swal = swal; 6401 options.tingle = tingle; 6402 6403 MageMe.loader.addScript('mmForm', "https\u003A\u002F\u002Fwww.banham.co.uk\u002Fstatic\u002Fversion1790667712\u002Ffrontend\u002FBraveBison\u002Fdefault\u002Fen_GB\u002FHyva_MageMeWebForms\u002Fjs\u002Fform.min.js", () => { 6404 new mmForm(options); 6405 }); 6406 }); 6407 }); 6408 });</script>
vendor: 1 bytes, line 6408
6408
6409<script type="text/javascript"> 6410 let logicRules_yk8I5y = []; 6411 const targets_yk8I5y = [{"id":"field_yk8I5y23","logic_visibility":"hidden","required":true}]; 6412 const fieldMap_yk8I5y = {"fieldset_56":["626","1778"],"fieldset_59":["8"],"fieldset_62":["5","23","11","14","2","17"],"fieldset_65":["1781","20","629","632","26","29","32"],"fieldset_68":["1032","38"],"fieldset_0":["submit"]}; 6413 logicRules_yk8I5y.push({ 6414 'field_id': '5', 6415 'action': 'show', 6416 'logic_condition': 'equal', 6417 'aggregation': 'any', 6418 'value': ["Business"], 6419 'target': ["field_yk8I5y23"] }); 6420 window.addEventListener("mm_webform_loadedyk8I5y", () => { 6421 MageMe.loader.addScript('mmLogic', "https\u003A\u002F\u002Fwww.banham.co.uk\u002Fstatic\u002Fversion1790667712\u002Ffrontend\u002FBraveBison\u002Fdefault\u002Fen_GB\u002FHyva_MageMeWebForms\u002Fjs\u002Flogic.min.js", () => { 6422 new mmLogic({ 6423 targets: targets_yk8I5y, 6424 logicRules: logicRules_yk8I5y, 6425 fieldMap: fieldMap_yk8I5y, 6426 uid: 'yk8I5y', 6427 containerId: "webform_yk8I5y_form" 6428 }); 6429 }); 6430 });</script>
vendor: 1 bytes, line 6430
6430
6431<script> 6432 'use strict'; 6433 (() => { 6434 document.getElementById("captcha-container-yk8I5y").innerHTML = '<div class="captcha-wrapper" x-data="mmWebformsCaptchaDatayk8I5y" x-init="initCaptcha"><input type="hidden" name="g-recaptcha-response" class="required-entry"/><div class="recaptcha-position-bottomright"><div class="recaptcha-container"></div></div></div>'; 6435 })();</script>
vendor: 1 bytes, line 6435
6435
6436<script> 6437 function mmWebformsCaptchaDatayk8I5y() { 6438 return { 6439 initCaptcha() { 6440 const options = { 6441 globalOnLoadCallback: 'webformsCaptchaOnload', 6442 captchaReadyEvent: 'webformsCaptchaApiReady', 6443 languageCode: 'en', 6444 publicKey: '6LfHDCMrAAAAACnDJl581eICSW3VnauItgp_hR2S', 6445 scriptId: 'mm_captcha', 6446 scriptExists: false, 6447 responseFieldName: 'g-recaptcha-response', 6448 version: '3', 6449 position: 'bottomright', 6450 theme: 'standard', 6451 action: '', 6452 checkboxChallenge: false, 6453 render: 'explicit' 6454 } 6455 6456 addCaptchaScriptTag(); 6457 let captcha; 6458 try { 6459 captcha = getCaptchaObject(); 6460 } catch { 6461 captcha = false; 6462 } 6463 if (captcha && captcha.render) { 6464 initCaptcha(); 6465 } else { 6466 window.addEventListener(options.captchaReadyEvent, function () { 6467 initCaptcha(); 6468 }); 6469 } 6470 6471 function initCaptcha() { 6472 const captcha = getCaptchaObject(); 6473 const params = {theme: options.theme, sitekey: options.publicKey}; 6474 6475 6476 if (isEnterprise() && options.checkboxChallenge) { 6477 params.size = 'normal'; 6478 params.callback = saveToken; 6479 } else if (isEnterprise() && !options.checkboxChallenge) { 6480 6481 params.size = 'invisible'; 6482 } else if (getIsInvisibleRecaptcha()) { 6483 params.size = 'invisible'; 6484 } else { 6485 params.callback = saveToken; 6486 } 6487 6488 const self = this; 6489 captcha.ready(function () { 6490 const captchaDivs = document.querySelectorAll('[class="recaptcha-container"]'); 6491 for (let i = 0; i < captchaDivs.length; i++) { 6492 try { 6493 const cid = captcha.render(captchaDivs[i], params); 6494 captchaDivs[i].setAttribute('cid', cid); 6495 } catch (e) { /* recaptcha was already rendered */ } 6496 } 6497 6498 window.addEventListener('mm-form-captcha-promise', (e) => { 6499 let cid = null; 6500 let form = null; 6501 if (e && e.detail) { 6502 cid = e.detail.cid; 6503 form = e.detail.form; 6504 } 6505 if (typeof cid === "undefined" || cid === null) { 6506 return; 6507 } 6508 if (typeof form === "undefined" || form === null) { 6509 return; 6510 } 6511 captcha.reset(cid); 6512 form.setCaptchaPromise(getToken.bind(self), self); 6513 }); 6514 6515 window.addEventListener('mm-form-captcha-reload', (e) => { 6516 let cid = null; 6517 if (e && e.detail) { 6518 cid = e.detail.cid; 6519 } 6520 if (typeof cid === "undefined" || cid === null) { 6521 return; 6522 } 6523 captcha.reset(cid); 6524 }); 6525 }) 6526 6527 function getToken(cid, form) { 6528 const captcha = getCaptchaObject(); 6529 switch (options.version) { 6530 case '3': { 6531 return captcha.execute(cid); 6532 } 6533 case 'enterprise': { 6534 6535 if (options.checkboxChallenge) { 6536 return new Promise(async (resolve, reject) => { 6537 const token = form.controls.form.querySelector(`[name="${options.responseFieldName}"]`); 6538 if (token) { 6539 resolve(token.value); 6540 } 6541 resolve(''); 6542 }); 6543 } 6544 return captcha.execute(cid, {action: options.action}); 6545 } 6546 default: { 6547 return new Promise(async (resolve, reject) => { 6548 const token = form.controls.form.querySelector(`[name="${options.responseFieldName}"]`); 6549 if (token) { 6550 resolve(token.value); 6551 } 6552 resolve(''); 6553 }); 6554 } 6555 } 6556 } 6557 6558 function saveToken(token) { 6559 const rFields = document.querySelectorAll(`[name="${options.responseFieldName}"]`); 6560 for (let i = 0; i < rFields.length; i++) { 6561 rFields[i].value = token; 6562 } 6563 } 6564 } 6565
6566 function getIsInvisibleRecaptcha() { 6567 return options.version === '3'; 6568 } 6569 6570 function isEnterprise() { 6571 return options.version === 'enterprise'; 6572 } 6573 6574 function addCaptchaScriptTag() { 6575 if (!document.getElementById(options.scriptId)) { 6576 const script = document.createElement('script'); 6577 const firstScriptTag = document.getElementsByTagName('script')[0]; 6578 const api = isEnterprise() ? 'enterprise' : 'api'; 6579 6580 script.async = true; 6581 script.defer = true; 6582 6583 script.src = `https://www.google.com/recaptcha/${api}.js?render=${options.render}`; 6584 6585 if (options.languageCode) { 6586 script.src += `&hl=${options.languageCode}` 6587 } 6588 if (options.position) { 6589 script.src += `&badge=${options.position}` 6590 } 6591 script.onload = () => { 6592 window.dispatchEvent(new CustomEvent(options.captchaReadyEvent)); 6593 } 6594 firstScriptTag.parentNode.insertBefore(script, firstScriptTag); 6595 } 6596 options.scriptExists = true; 6597 } 6598 6599 function getCaptchaObject() { 6600 return isEnterprise() ? window.grecaptcha.enterprise : window.grecaptcha; 6601 } 6602 } 6603 } 6604 } 6605 6606 window.addEventListener('alpine:init', () => Alpine.data('mmWebformsCaptchaDatayk8I5y', mmWebformsCaptchaDatayk8I5y), {once: true});</script>
vendor: 1 bytes, line 6606
6606
6607<script> 6608 (() => { 6609 if (!window.MageMe || !MageMe.loader) { 6610 return; 6611 } 6612 MageMe.loader.addCssOrdered( 6613 'https://www.banham.co.uk/static/version1790667712/frontend/BraveBison/default/en_GB/MageMe_WebForms/css/recaptcha.min.css', 6614 '50' 6615 ); 6616 })();</script>
vendor: 1 bytes, line 6616
6616
6617<script> 6618 6619 [...document.getElementById('webform_yk8I5y_form').querySelectorAll("input")].forEach((input) => { 6620 input.onkeydown = function(e) { 6621 const key = e.charCode || e.keyCode || 0; 6622 if (key === 13) { 6623 const index = [...input.form].indexOf(this); 6624 if (input.form.elements[index + 1] != null) { 6625 input.form.elements[index + 1].focus(); 6626 } 6627 e.preventDefault(); 6628 return false; 6629 } 6630 } 6631 }); 6632 6633 6634 document.addEventListener('alpine:init', () => { 6635 const enquiryTopic = window.enquiryTopic; 6636 const enquiryTopicProduct = window.enquiryTopicProduct; 6637 if (window.Alpine) { 6638 Alpine.store('CRMForm', window.CRMForm); 6639 } 6640 const topicField = document.querySelector('#enquiry-topic'); 6641 if (!topicField) return; 6642 topicField.setAttribute('autocomplete', 'off'); 6643 document.querySelectorAll('.type-swatches input').forEach(input => { 6644 input.setAttribute('autocomplete', 'off'); 6645 }); 6646 if (enquiryTopic !== '' && enquiryTopicProduct !== '') { 6647 topicField.setAttribute('required', 'true'); 6648 topicField.closest('.form-group')?.classList.remove('!hidden'); 6649 topicField.value = enquiryTopic; 6650 document.querySelectorAll('.type-swatches input').forEach(input => { 6651 const inputValue = input.value.toLowerCase(); 6652 const topicLower = enquiryTopic.toLowerCase(); 6653 6654 if (topicLower.includes(inputValue) || inputValue.includes(topicLower)) { 6655 input.closest('.field.choice')?.remove(); 6656 return; 6657 } 6658 if (enquiryTopicProduct.toLowerCase().includes(input.value.toLowerCase())) { 6659 input.checked = true; 6660 } 6661 input.removeAttribute('data-validate'); 6662 }); 6663 window.selectedCategories = [enquiryTopicProduct]; 6664 } else if (enquiryTopicProduct !== '') { 6665 topicField.removeAttribute('required'); 6666 topicField.closest('.webforms-fields-enquiry-topic')?.classList.add('!hidden'); 6667 document.querySelectorAll('.type-swatches input').forEach(input => { 6668 const inputValue = input.value.toLowerCase(); 6669 const topicLower = enquiryTopic.toLowerCase(); 6670 if (topicLower.includes(inputValue) || inputValue.includes(topicLower)) { 6671 input.closest('.field.choice')?.remove(); 6672 return; 6673 } 6674 if (enquiryTopicProduct.toLowerCase().includes(input.value.toLowerCase())) { 6675 input.checked = true; 6676 } 6677 input.removeAttribute('data-validate'); 6678 }); 6679 window.selectedCategories = [enquiryTopicProduct]; 6680 } else if (enquiryTopic !== '') { 6681 topicField.setAttribute('required', 'true'); 6682 topicField.closest('.form-group')?.classList.remove('!hidden'); 6683 topicField.value = enquiryTopic; 6684 document.querySelectorAll('.type-swatches input').forEach(input => { 6685 const inputValue = input.value.toLowerCase(); 6686 const topicLower = enquiryTopic.toLowerCase(); 6687 6688 if (topicLower.includes(inputValue) || inputValue.includes(topicLower)) { 6689 input.closest('.field.choice')?.remove(); 6690 return; 6691 } 6692 input.removeAttribute('data-validate'); 6693 }); 6694 } else { 6695 topicField.removeAttribute('required'); 6696 topicField.closest('.webforms-fields-enquiry-topic')?.classList.add('!hidden'); 6697 document.querySelectorAll('.type-swatches input').forEach(input => { 6698 input.setAttribute('data-validate', JSON.stringify({ 6699 'validate-one-required-by-name': true, 6700 'validate-options-checkbox-min': '1' 6701 })); 6702 }); 6703 } 6704}); </script>
vendor: 1 bytes, line 6704
6704
6705<script> 6706 (() => { 6707 if (!window.MageMe || !MageMe.loader) { 6708 return; 6709 } 6710 })();</script>
vendor: 1 bytes, line 6710
6710
6711<script type="text/javascript"> 6712 'use strict'; 6713 6714 (() => { 6715 if (!window.MageMe || !MageMe.loader) { 6716 return; 6717 } 6718 MageMe.loader.addScript('mmWebformsMultistep',"https\u003A\u002F\u002Fwww.banham.co.uk\u002Fstatic\u002Fversion1790667712\u002Ffrontend\u002FBraveBison\u002Fdefault\u002Fen_GB\u002FHyva_MageMeWebForms\u002Fjs\u002Fmultistep.min.js", () => { 6719 const formId = 'webform_yk8I5y'; 6720 const uid = 'yk8I5y'; 6721 const showReview = false; 6722 const controls = { 6723 form: document.getElementById(formId), 6724 wizard: document.getElementById('wizardyk8I5y') 6725 }; 6726 function isIntersecting(el) { 6727 return new Promise(resolve => { 6728 const o = new IntersectionObserver(([entry]) => { 6729 resolve(entry.intersectionRatio === 1); 6730 o.disconnect(); 6731 }); 6732 o.observe(el); 6733 }); 6734 } 6735 6736 MageMe.WebForms.loadValidation().then(() => { 6737 return MageMe.WebForms.formValidation(controls.form, {fieldWrapperClassName: 'control', messagesWrapperClassName: 'hyva-validation'}); 6738 }).then(validator => { 6739 return { 6740 uid: 'yk8I5y', 6741 onFinish: () => { 6742 validator.validate().then(() => { 6743 if (window.mmForms && window.mmForms[uid]) { 6744 if (showReview) { 6745 window.mmForms[uid].review(); 6746 } else { 6747 window.mmForms[uid].submit(); 6748 } 6749 } 6750 }).catch(fields => { 6751 if (!controls.wizard) { 6752 console.log('No wizard'); 6753 return; 6754 } 6755 const fieldset = controls.wizard.querySelector('.form-step.active fieldset'); 6756 if (!fieldset || fieldset.style.display === 'none') { 6757 return; 6758 } 6759 for (let i = 0; i < fields.length; i++) { 6760 if (fieldset.querySelector('[name="' + fields[i].name + '"]')) { 6761 throw new Error('jqstps'); 6762 } 6763 } 6764 if (window.mmForms && window.mmForms[uid]) { 6765 if (showReview) { 6766 window.mmForms[uid].review(); 6767 } else { 6768 window.mmForms[uid].submit(); 6769 } 6770 } 6771 }); 6772 }, 6773 onChange: (currentIndex, newIndex, stepDirection) => { 6774 if(stepDirection === 'forward') { 6775 return validator.validate().then(() => { 6776 isIntersecting(controls.wizard).then(result => { 6777 if (!result) { 6778 window.scrollTo({top: controls.wizard.offsetTop - 20, behavior: 'smooth'}); 6779 } 6780 }); 6781 }).catch(fields => { 6782 if (!controls.wizard) { 6783 console.log('No wizard'); 6784 return; 6785 } 6786 const fieldset = controls.wizard.querySelector('.form-step.active fieldset'); 6787 if (!fieldset) { 6788 console.log('No fieldset'); 6789 return; 6790 } 6791 if (fieldset.style.display === 'none') { 6792 return; 6793 } 6794 if (fieldset.classList.contains('mm-logic-hidden')) { 6795 return; 6796 } 6797 for (let i = 0; i < fields.length; i++) { 6798 if (fieldset.querySelector('[name="' + fields[i].name + '"]')) { 6799 throw new Error('jqstps'); 6800 } 6801 const errorMessage = controls.wizard.querySelector('[data-msg-field="' + fields[i].name + '"]'); 6802 if (errorMessage) { 6803 errorMessage.remove(); 6804 } 6805 } 6806 }); 6807 6808 } else return new Promise(async (resolve, reject) => { 6809 resolve(); 6810 }); 6811 }, 6812 } 6813 }).then(config => { 6814 window.addEventListener('unhandledrejection', function(event) { 6815 if (event.reason.message === 'jqstps') { 6816 event.preventDefault(); 6817 } 6818 }); 6819 const mmSteps = new mmWebformsMultistep(document.getElementById('wizardyk8I5y'),config); 6820 mmSteps.init(); 6821 }).catch((er) => { 6822 console.log(er); 6823 }); 6824 }); 6825 })();</script>
vendor: 1 bytes, line 6825
6825
6826<script defer> 6827 (() => { 6828 window.dispatchEvent(new CustomEvent("mm_webform_loadedyk8I5y")); 6829 })();</script>
vendor: 1 bytes, line 6829
6829
6830<script> 6831 window.surveyTypeVal = ''; 6832 document.addEventListener("DOMContentLoaded", function () { 6833 document.querySelectorAll('a[href="#open_enquiry_form"]').forEach(function(el){ 6834 el.addEventListener("click", function (e) { 6835 e.preventDefault(); 6836 window.surveyOpeningSource = 'Opened from CTA'; 6837 document.querySelector(".open-slide-form-button")?.click(); 6838 }); 6839 }); 6840 const idEl = document.getElementById("open_enquiry_form"); 6841 if (idEl) { 6842 idEl.addEventListener("click", function (e) { 6843 e.preventDefault(); 6844 window.surveyOpeningSource = 'Opened from CTA'; 6845 document.querySelector(".open-slide-form-button")?.click(); 6846 }); 6847 } 6848 6849 if (window.location.hash === '#open_enquiry_form' || window.location.search.includes('open=enquiry')) { 6850 setTimeout(function() { 6851 window.surveyOpeningSource = 'Opened from CTA'; 6852 document.querySelector(".open-slide-form-button")?.click(); 6853 }, 300); 6854 } 6855 }); 6856 6857 (function () { 6858 var successDiv = document.getElementById('webform_yk8I5y_success_text'); 6859 var headingDiv = document.getElementById('webform_yk8I5y_heading'); 6860 var closeBtn = document.querySelector('#webform-slide-out-yk8I5y .close-btn'); 6861 if (successDiv && headingDiv) { 6862 new MutationObserver(function () { 6863 var isSuccess = successDiv.style.display !== 'none'; 6864 headingDiv.classList.toggle('hidden', isSuccess); 6865 if (closeBtn) closeBtn.classList.toggle('success-close', isSuccess); 6866 }).observe(successDiv, { attributes: true, attributeFilter: ['style'] }); 6867 } 6868 })(); 6869</script>
vendor: 1 bytes, line 6869
6869
6870<script> 6871 window.site_type = "Retail"; 6872 6873 function ctaLabel(el) { 6874 return (el.innerText || el.value || '').trim().split('\n').pop().trim(); 6875 } 6876 6877 function ctaHeading(start) { 6878 const inside = start.querySelector('h1, h2, h3, h4'); 6879 if (inside) return inside.textContent.replace(/\s+/g, ' ').trim(); 6880 6881 for (let el = start.parentElement; el && !el.matches('body, header, nav, footer'); el = el.parentElement) { 6882 const sibling = [...el.children].find(child => /^H[1-4]$/.test(child.tagName)); 6883 if (sibling) return sibling.textContent.replace(/\s+/g, ' ').trim(); 6884 } 6885 return ''; 6886 } 6887 6888 function ctaSection(start, itemTitle, clickPath) { 6889 if (start.closest('#header, [role="banner"], header, nav, .pre-footer, [role="contentinfo"]')) { 6890 return ''; 6891 } 6892 6893 for (let el = start.parentElement; el && !el.matches('body, header, nav, footer, main, #header, [role="banner"], [role="contentinfo"]'); el = el.parentElement) { 6894 if (el.querySelector('main, #header, [role="contentinfo"]')) break; 6895 6896 const repeats = [...el.querySelectorAll('.button, .btn, button, .underline-link, .pre-footer a')] 6897 .filter(cta => ctaLabel(cta) === clickPath).length > 1; 6898 if (!repeats) continue; 6899 6900 const section = [...el.querySelectorAll('h1, h2, h3, h4')] 6901 .find(h => h.textContent.replace(/\s+/g, ' ').trim() !== itemTitle && !h.closest('a, button, .btn, .underline-link')); 6902 if (section) return section.textContent.replace(/\s+/g, ' ').trim(); 6903 } 6904 return ''; 6905 } 6906 6907 document.addEventListener('click', function (event) { 6908 const target = event.target.closest('.button, .btn, button, .underline-link, .pre-footer a'); 6909 if (!target) return; 6910 6911 const customer = JSON.parse(localStorage.getItem('mage-cache-storage') || '{}').customer || {}; 6912 const clickPath = ctaLabel(target); 6913 const itemTitle = ctaHeading(target); 6914 const section = itemTitle && ctaSection(target, itemTitle, clickPath); 6915 const pageName = (section ? section + ' - ' + itemTitle : itemTitle) || document.title; 6916 6917 window.dataLayer = window.dataLayer || []; 6918 window.dataLayer.push({ 6919 event: 'cta', 6920 site_type: window.site_type, 6921 logged_in_status: !!customer.id, 6922 page_name: pageName, 6923 click_path: clickPath, 6924 ...(customer.id && { user_id: customer.id }) 6925 }); 6926 }); 6927 window.trackContentAccordion = function (clickPath, isOpen) { 6928 if (window.site_type !== 'Retail' || !clickPath) return; 6929 const customer = JSON.parse(localStorage.getItem('mage-cache-storage') || '{}').customer || {}; 6930 window.dataLayer = window.dataLayer || []; 6931 window.dataLayer.push({ 6932 event: 'content_accordion', 6933 site_type: window.site_type, 6934 logged_in_status: !!customer.id, 6935 page_name: document.title, 6936 click_path: String(clickPath).trim(), 6937 action: isOpen ? 'Expand' : 'Collapse', 6938 ...(customer.id && { user_id: customer.id }) 6939 }); 6940 }; 6941</script>
vendor: 1 bytes, line 6941
6941
6942<script> 6943 document.body.addEventListener('touchstart', () => {}, {passive: true}) 6944</script>
vendor: 1 bytes, line 6944
6944
6945<script> 6946 'use strict'; 6947 6948 (function () { 6949 /** 6950 * @typedef amPickupLocalStorage 6951 * @property {Object} scheduleData 6952 * @property {Object} store 6953 * @property {boolean} isPickupSelected 6954 * @property {boolean} isCurbsideChecked 6955 * @property {string} curbsideComment 6956 * @property {Object} dateValue 6957 * @property {string} timeValue 6958 */ 6959 window.amPickupLocalStorage = window.amPickupLocalStorage || { 6960 getObject: function () { 6961 return hyva.getBrowserStorage().getItem('amPickupLocation') 6962 ? JSON.parse(hyva.getBrowserStorage().getItem('amPickupLocation')) 6963 : {}; 6964 }, 6965 setObject: function (newObject) { 6966 hyva.getBrowserStorage().setItem('amPickupLocation', JSON.stringify(newObject)); 6967 }, 6968 remove: function () { 6969 hyva.getBrowserStorage().removeItem('amPickupLocation'); 6970 }, 6971 setIsPickupSelected: function (value) { 6972 this.setObject({ 6973 ...this.getObject(), 6974 isPickupSelected: value 6975 }); 6976 }, 6977 isPickupSelected: function () { 6978 return (this.getObject().isPickupSelected && this.getObject().isPickupSelected !== undefined) 6979 ? Boolean(Number(this.getObject().isPickupSelected)) 6980 : null; 6981 }, 6982 setScheduleData: function (value) { 6983 this.setObject({ 6984 ...this.getObject(), 6985 scheduleData: value 6986 }); 6987 }, 6988 getScheduleData: function () { 6989 return (this.getObject().scheduleData && this.getObject().scheduleData !== undefined) 6990 ? this.getObject().scheduleData 6991 : null; 6992 }, 6993 setStore: function (value) { 6994 this.setObject({ 6995 ...this.getObject(), 6996 store: value 6997 }); 6998 }, 6999 getStore: function () { 7000 return (this.getObject().store && this.getObject().store !== undefined) 7001 ? this.getObject().store 7002 : null; 7003 }, 7004 setCurbsideChecked: function (value) { 7005 this.setObject({ 7006 ...this.getObject(), 7007 isCurbsideChecked: value 7008 }); 7009 }, 7010 getCurbsideChecked: function () { 7011 return (this.getObject().isCurbsideChecked && this.getObject().isCurbsideChecked !== undefined) 7012 ? this.getObject().isCurbsideChecked 7013 : false; 7014 }, 7015 setCurbsideComment: function (value) { 7016 this.setObject({ 7017 ...this.getObject(), 7018 curbsideComment: value 7019 }); 7020 }, 7021 getCurbsideComment: function () { 7022 return (this.getObject().curbsideComment && this.getObject().curbsideComment !== undefined) 7023 ? this.getObject().curbsideComment 7024 : null; 7025 }, 7026 setDateValue: function (value) { 7027 this.setObject({ 7028 ...this.getObject(), 7029 dateValue: value 7030 }); 7031 }, 7032 getDateValue: function () { 7033 return (this.getObject().dateValue && this.getObject().dateValue !== undefined) 7034 ? new Date(Number(this.getObject().dateValue)) 7035 : null; 7036 }, 7037 setTimeValue: function (value) { 7038 this.setObject({ 7039 ...this.getObject(), 7040 timeValue: value 7041 }); 7042 }, 7043 getTimeValue: function () { 7044 return (this.getObject().timeValue && this.getObject().timeValue !== undefined) 7045 ? this.getObject().timeValue 7046 : null; 7047 } 7048 }; 7049 })();</script>
vendor: 1 bytes, line 7049
7049
7050<script> 7051 function initHeader () { 7052 return { 7053 searchOpen: false, 7054 cart: {}, 7055 isCartOpen: false, 7056 getData(data) { 7057 if (data.cart) { this.cart = data.cart } 7058 }, 7059 isCartEmpty() { 7060 return !this.cart.summary_count 7061 }, 7062 toggleCart(event) { 7063 if (event.detail && event.detail.isOpen !== undefined) { 7064 this.isCartOpen = event.detail.isOpen 7065 if (!this.isCartOpen && this.$refs && this.$refs.cartButton) { 7066 this.$refs.cartButton.focus() 7067 } 7068 } else { 7069 this.isCartOpen = true 7070 } 7071 } 7072 } 7073 } 7074 function initCompareHeader() { 7075 return { 7076 compareProducts: null, 7077 itemCount: 0, 7078 receiveCompareData(data) { 7079 if (data['compare-products']) { 7080 this.compareProducts = data['compare-products']; 7081 this.itemCount = this.compareProducts.count; 7082 } 7083 } 7084 } 7085 } 7086</script>
vendor: 1 bytes, line 7086
7086
7087<script> 7088 'use strict'; 7089 7090 const initMenuMobile_6abf56c059c8f = () => { 7091 return { 7092 mobilePanelActiveId: null, 7093 open: false, 7094 isLoggedIn: false, 7095 signInText: 'Sign\u0020In', 7096 accountText: 'My\u0020Account', 7097 accountUrl: 'https\u003A\u002F\u002Fwww.banham.co.uk\u002Fcustomer\u002Faccount\u002F', 7098 signOutText: 'Sign\u0020Out', 7099 signOutUrl: 'https\u003A\u002F\u002Fwww.banham.co.uk\u002Fcustomer\u002Faccount\u002Flogout\u002F', 7100 init() { 7101 this.setActiveMenu(this.$root); 7102 this.setCustomerStateFromStorage(); 7103 }, 7104 setCustomerState(data) { 7105 this.isLoggedIn = !!(data && data.customer && (data.customer.firstname || data.customer.id)); 7106 }, 7107 setCustomerStateFromStorage() { 7108 try { 7109 const cached = JSON.parse(localStorage.getItem('mage-cache-storage') || '{}'); 7110 this.isLoggedIn = !!(cached.customer && (cached.customer.firstname || cached.customer.id)); 7111 } catch (e) { 7112 this.isLoggedIn = false; 7113 } 7114 },
7115 setActiveMenu(menuNode) { 7116 Array.from(menuNode.querySelectorAll('a')).filter(link => { 7117 return link.href === window.location.href.split('?')[0]; 7118 }).map(item => { 7119 item.classList.add('active'); 7120 item.closest('li.level-0')?.classList.add('active'); 7121 item.closest('li.level-0') && 7122 item.closest('li.level-0').querySelector('a.level-0').classList.add('active'); 7123 }); 7124 }, 7125 openMenu() { 7126 this.open = true 7127 this.$nextTick(() => hyva.trapFocus(this.$refs['mobileMenuNavLinks'])); 7128 // Prevent from body scrolling while mobile menu opened 7129 document.body.style.overflow = 'hidden'; 7130 }, 7131 closeMenu() { 7132 document.body.style.overflow = ''; 7133 7134 if (this.open) { 7135 this.$nextTick(() => this.$refs['mobileMenuTrigger'].focus() || hyva.releaseFocus()); 7136 } 7137 7138 this.open = false 7139 this.mobilePanelActiveId = null 7140 }, 7141 openSubcategory(index) { 7142 const menuNodeRef = document.querySelector('[data-child-id=' + index + ']') 7143 this.mobilePanelActiveId = this.mobilePanelActiveId === index ? 0 : index 7144 this.$nextTick(() => hyva.trapFocus(menuNodeRef)) 7145 }, 7146 backToMainCategories(index) { 7147 const menuNodeRef = document.querySelector('[data-child-id=' + index + ']') 7148 this.mobilePanelActiveId = 0 7149 this.$nextTick(() => { 7150 hyva.trapFocus(this.$refs['mobileMenuNavLinks']) 7151 menuNodeRef.querySelector('a').focus() 7152 }) 7153 } 7154 } 7155 } 7156</script>
vendor: 1 bytes, line 7156
7156
7157<script> 7158 'use strict'; 7159 7160 function initMiniSearch() { 7161 return { 7162 minSearchLength: 3, 7163 search(term) { 7164 const search = this.$refs.searchInput; 7165 term = term || search.value; 7166 if (term.length < this.minSearchLength) { 7167 search.setCustomValidity('Minimum\u0020Search\u0020query\u0020length\u0020is\u00203'); 7168 search.reportValidity(); 7169 } else { 7170 search.setCustomValidity(''); 7171 search.value = term; 7172 this.$refs.form.submit(); 7173 } 7174 }, 7175 focusElement(element) { 7176 if (element && element.nodeName === "DIV") { 7177 element.focus(); 7178 return true; 7179 } else { 7180 return false; 7181 } 7182 } 7183 } 7184 } 7185</script>
vendor: 1 bytes, line 7185
7185
7186<script> 7187 function initCartDrawer() { 7188 return { 7189 open: false, 7190 isLoading: false, 7191 cart: {}, 7192 isLoggedIn: false, 7193 customerId: null, 7194 maxItemsToDisplay: 10, 7195 itemsCount: 0, 7196 extraProducts: [], 7197 extraProductQtys: {}, 7198 extraSplideInstance: null, 7199 getData(data) { 7200 if (data.customer) { 7201 this.isLoggedIn = !!(data.customer.firstname); 7202 this.customerId = data.customer.id || null; 7203 } 7204 if (data.cart) { 7205 this.cart = data.cart; 7206 //this.itemsCount = data.cart.items && data.cart.items.length || 0; 7207 this.itemsCount = data.cart.items ? data.cart.items.reduce((total, item) => total + parseInt(item.qty), 0) : 0; 7208 this.setCartItems(); 7209 this.loadExtraProducts(); 7210 } 7211 this.isLoading = false; 7212 }, 7213 cartItems: [], 7214 getItemCountTitle() { 7215 return hyva.strf('\u00250\u0020of\u0020\u00251\u0020products\u0020in\u0020cart\u0020displayed', this.maxItemsToDisplay, this.itemsCount) 7216 }, 7217 setCartItems() { 7218 this.cartItems = this.cart.items && this.cart.items.sort((a, b) => b.item_id - a.item_id) || []; 7219 7220 if (this.maxItemsToDisplay > 0) { 7221 this.cartItems = this.cartItems.slice(0, parseInt(this.maxItemsToDisplay, 10)); 7222 } 7223 }, 7224 deleteItemFromCart(itemId) { 7225 this.isLoading = true; 7226 7227 const itemData = this.cart.items.filter((item) => item['item_id'] === itemId); 7228 const formKey = hyva.getFormKey(); 7229 const postUrl = BASE_URL + 'checkout/sidebar/removeItem/'; 7230 7231 fetch(postUrl, { 7232 "headers": { 7233 "content-type": "application/x-www-form-urlencoded; charset=UTF-8", 7234 }, 7235 "body": "form_key=" + formKey + "&item_id=" + itemId, 7236 "method": "POST", 7237 "mode": "cors", 7238 "credentials": "include" 7239 }).then(response => { 7240 if (response.redirected) { 7241 window.location.href = response.url; 7242 } else if (response.ok) { 7243 return response.json(); 7244 } else { 7245 window.dispatchMessages && window.dispatchMessages([{ 7246 type: 'warning', 7247 text: 'Could\u0020not\u0020remove\u0020item\u0020from\u0020quote.' 7248 }]); 7249 this.isLoading = false; 7250 } 7251 }).then(result => { 7252 window.dispatchMessages && window.dispatchMessages([{ 7253 type: result.success ? 'success' : 'error', 7254 text: result.success 7255 ? 'You\u0020removed\u0020the\u0020item.' 7256 : result.error_message 7257 }], result.success ? 5000 : 0); 7258 7259 if (result.success && itemData) { 7260 const removedItem = itemData[0]; 7261 if (removedItem) { 7262 this.pushCartEvent('remove_from_cart', removedItem, parseInt(removedItem.qty) || 1); 7263 } 7264 window.dispatchEvent(new CustomEvent('cart-item-removed', { detail: itemData })); 7265 } 7266 7267 window.dispatchEvent(new CustomEvent('reload-customer-section-data')); 7268 }); 7269 }, 7270 scrollLock(use = true) { 7271 document.body.style.overflow = use ? "hidden" : ""; 7272 }, 7273 toggleCartDrawer(event) { 7274 if (event.detail && event.detail.isOpen !== undefined) { 7275 if (event.detail.isOpen) { 7276 this.openCartDrawer(); 7277 } else { 7278 this.open = false;
7279 this.scrollLock(false); 7280 this.$refs && this.$refs.cartDialogContent && hyva.releaseFocus(this.$refs.cartDialogContent); 7281 } 7282 } else { 7283 this.openCartDrawer() 7284 } 7285 }, 7286 openCartDrawer() { 7287 this.open = true; 7288 this.scrollLock(true); 7289 this.$nextTick(() => { 7290 this.$refs && this.$refs.cartDialogContent && hyva.trapFocus(this.$refs.cartDialogContent) 7291 this.initExtraProductsSplide(); 7292 }); 7293 // Fire view_cart GTM event when cart opens 7294 if (this.cart && this.cart.items) { 7295 this.pushViewCartEvent(); 7296 } 7297 }, 7298 pushViewCartEvent() { 7299 window.dataLayer = window.dataLayer || []; 7300 window.dataLayer.push({ ecommerce: null }); 7301 window.dataLayer.push({ 7302 event: 'view_cart', 7303 site_type: this.cart.storeId == 1 ? 'Retail' : 'Trade', 7304 journey_type: 'product', 7305 logged_in_status: this.isLoggedIn, 7306 ...(this.customerId ? { 'user_id': this.customerId } : {}), 7307 page_type: 'cart_modal', 7308 ecommerce: { 7309 currency: 'GBP', 7310 value: this.cart.subtotalAmount || 0, 7311 items: (this.cart.items || []).map((item, index) => ({ 7312 item_id: item.product_sku, 7313 item_name: item.product_name || '', 7314 index: index, 7315 item_category: 'product', 7316 item_category2: item.item_category2 || '', 7317 item_category3: item.item_category3 || '', 7318 cta_type: 'add to cart', 7319 price: parseFloat(item.product_price_value) || 0, 7320 discount: item.discount_amount || 0, 7321 quantity: item.qty 7322 })) 7323 } 7324 }); 7325 }, 7326 closeCartDrawer() { 7327 this.$dispatch('toggle-cart', { isOpen: false }) 7328 }, 7329 getSectionDataExtraActions() { 7330 if (!this.cart.extra_actions) { 7331 return ''; 7332 } 7333 7334 const contentNode = document.createElement('div'); 7335 contentNode.innerHTML = this.cart.extra_actions; 7336 7337 hyva.activateScripts(contentNode); 7338 7339 return contentNode.innerHTML; 7340 }, 7341 incrementQty(item) { 7342 item.qty = parseInt(item.qty) + 1; 7343 this.updateItemQty(item); 7344 this.pushCartEvent('add_to_cart', item, 1); 7345 }, 7346 decrementQty(item) { 7347 if (parseInt(item.qty) > 1) { 7348 item.qty = parseInt(item.qty) - 1; 7349 this.updateItemQty(item); 7350 this.pushCartEvent('remove_from_cart', item, 1); 7351 } 7352 }, 7353 pushCartEvent(eventType, item, quantity) { 7354 window.dataLayer = window.dataLayer || []; 7355 window.dataLayer.push({ ecommerce: null }); 7356 window.dataLayer.push({ 7357 event: eventType, 7358 site_type: this.cart.storeId == 1 ? 'Retail' : 'Trade', 7359 journey_type: 'product', 7360 logged_in_status: this.isLoggedIn, 7361 ...(this.customerId ? { 'user_id': this.customerId } : {}), 7362 product_category: item.item_category2 || '', 7363 product_id: item.product_sku, 7364 product_name: item.product_name || '', 7365 page_type: 'cart_modal', 7366 ecommerce: { 7367 currency: 'GBP', 7368 value: parseFloat(item.product_price_value) * quantity, 7369 items: [{ 7370 item_id: item.product_sku, 7371 item_name: item.product_name || '', 7372 index: -1, 7373 item_category: 'product', 7374 item_category2: item.item_category2 || '', 7375 item_category3: item.item_category3 || '', 7376 cta_type: 'cart', 7377 price: parseFloat(item.product_price_value) || 0, 7378 discount: item.discount_amount || 0, 7379 quantity: quantity 7380 }] 7381 } 7382 }); 7383 }, 7384 updateItemQty(item) { 7385 this.isLoading = true; 7386 const formKey = hyva.getFormKey(); 7387 const postUrl = BASE_URL + 'checkout/cart/updatePost/'; 7388 7389 const body = new URLSearchParams(); 7390 body.append("form_key", formKey); 7391 body.append("cart[" + item.item_id + "][qty]", item.qty); 7392 7393 fetch(postUrl, { 7394 method: "POST", 7395 credentials: "include", 7396 headers: { 7397 "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", 7398 "X-Requested-With": "XMLHttpRequest" 7399 }, 7400 body: body.toString(), 7401 redirect: 'manual' 7402 }).then(response => { 7403 if (response.type === 'opaqueredirect' || response.ok) { 7404 // Refresh minicart data without following redirect 7405 window.dispatchEvent(new CustomEvent('reload-customer-section-data')); 7406 this.isLoading = false;
7407 } else { 7408 this.isLoading = false; 7409 throw new Error("Failed to update cart"); 7410 } 7411 }).catch((error) => { 7412 this.isLoading = false; 7413 window.dispatchMessages && window.dispatchMessages([{ 7414 type: 'error', 7415 text: 'Could\u0020not\u0020update\u0020cart.' 7416 }]); 7417 }); 7418 }, 7419 loadExtraProducts() { 7420 fetch(BASE_URL + 'extracartproduct/minicart/product/') 7421 .then(response => response.json()) 7422 .then(data => { 7423 if (data.products && Array.isArray(data.products)) { 7424 this.extraProducts = data.products; 7425 data.products.forEach((p, i) => { 7426 this.extraProductQtys[i] = 1; 7427 }); 7428 this.$nextTick(() => this.initExtraProductsSplide()); 7429 } else { 7430 this.extraProducts = []; 7431 if (this.extraSplideInstance) { 7432 this.extraSplideInstance.destroy(); 7433 this.extraSplideInstance = null; 7434 } 7435 } 7436 }) 7437 .catch(() => { 7438 this.extraProducts = []; 7439 if (this.extraSplideInstance) { 7440 this.extraSplideInstance.destroy(); 7441 this.extraSplideInstance = null; 7442 } 7443 }); 7444 }, 7445 initExtraProductsSplide() { 7446 if (!this.$refs || !this.$refs.extraProductsSplide) { 7447 return; 7448 } 7449 7450 if (this.extraSplideInstance) { 7451 this.extraSplideInstance.destroy(); 7452 this.extraSplideInstance = null; 7453 } 7454 7455 if (this.extraProducts.length <= 1) { 7456 return; 7457 } 7458 7459 if (typeof Splide === 'undefined') { 7460 return; 7461 } 7462 7463 this.extraSplideInstance = new Splide(this.$refs.extraProductsSplide, { 7464 type: 'fade', 7465 pagination: false, 7466 autoplay: false, 7467 arrows: true 7468 }); 7469 this.extraSplideInstance.mount(); 7470 }, 7471 incrementExtraQty(index) { 7472 if (this.extraProductQtys[index] < 10000) { 7473 this.extraProductQtys[index]++; 7474 } 7475 }, 7476 decrementExtraQty(index) { 7477 if (this.extraProductQtys[index] > 1) { 7478 this.extraProductQtys[index]--; 7479 } 7480 }, 7481 addExtraProduct(product, index) { 7482 this.isLoading = true; 7483 const formKey = hyva.getFormKey(); 7484 const postUrl = BASE_URL + 'checkout/cart/add/'; 7485 const qty = this.extraProductQtys[index] || 1; 7486 7487 const body = new URLSearchParams(); 7488 body.append('form_key', formKey); 7489 body.append('product', product.id); 7490 body.append('qty', qty); 7491 7492 fetch(postUrl, { 7493 method: 'POST', 7494 credentials: 'include', 7495 headers: { 7496 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 7497 'X-Requested-With': 'XMLHttpRequest' 7498 }, 7499 body: body.toString() 7500 }).then(response => { 7501 if (response.ok) { 7502 window.dispatchEvent(new CustomEvent('reload-customer-section-data')); 7503 } 7504 this.isLoading = false; 7505 }).catch(() => { 7506 this.isLoading = false;
7507 window.dispatchMessages && window.dispatchMessages([{ 7508 type: 'error', 7509 text: 'Could\u0020not\u0020add\u0020product\u0020to\u0020cart.' 7510 }]); 7511 }); 7512 } 7513 } 7514 } 7515</script>
vendor: 1 bytes, line 7515
7515
7516<script> 7517 function initAuthentication() { 7518 return { 7519 open: false, 7520 close() { 7521 this.open = false; 7522 }, 7523 forceAuthentication: false, 7524 checkoutUrl: 'https://www.banham.co.uk/checkout/index/', 7525 errors: 0, 7526 hasCaptchaToken: 0, 7527 displayErrorMessage: false, 7528 errorMessages: [], 7529 setErrorMessages(message) { 7530 this.errorMessages = [message]; 7531 this.displayErrorMessage = true; 7532 }, 7533 submitForm() { 7534 // Do not rename $form, the variable is expected to be declared in the recaptcha output 7535 const $form = document.querySelector('#login-form'); 7536 if (!this.hasCaptchaToken) { 7537 this.errors = 1; 7538 7539 let retryCount = 0; 7540 const executeGreCaptcha = () => { 7541 grecaptcha.ready(() => { 7542 try { 7543 grecaptcha.execute( 7544 '6LfHDCMrAAAAACnDJl581eICSW3VnauItgp_hR2S', 7545 {action: 'submit'} 7546 ).then((token) => { 7547 const tokenElement = $form['g\u002Drecaptcha\u002Dresponse']; 7548 7549 if (tokenElement && token && token.length > 0) { 7550 if (tokenElement.length !== undefined && !tokenElement.tagName) { 7551 Array.from(tokenElement).forEach(el => el.value = token); 7552 } else { 7553 tokenElement.value = token; 7554 } 7555 this.errors = 0; 7556 this.hasCaptchaToken = 1; 7557 (this.submitForm || $form.submit).call(this); 7558 } else { 7559 this.errors = 1; 7560 const error = tokenElement 7561 ? 'ReCaptcha\u0020validation\u0020failed,\u0020please\u0020try\u0020again' 7562 : 'ReCaptcha\u0020token\u0020element\u0020is\u0020missing.\u0020Please\u0020refresh\u0020the\u0020page\u0020and\u0020trying\u0020again.'; 7563 if (this.setErrorMessages) { 7564 this.setErrorMessages([error]) 7565 this.displayErrorMessage = true; 7566 } else { 7567 window.dispatchMessages && window.dispatchMessages([{type: "error", text: error}], 5000); 7568 } 7569 } 7570 }).catch(exception => { 7571 console.error(exception || `An unknown error occurred during ReCaptcha validation. Are the tokens and the domain correctly set up in the Google console?`) 7572 }); 7573 } catch (e) { 7574 if (e && e.message && e.message.includes('Invalid site key or not loaded in api.js') && retryCount < 20) { 7575 retryCount++; 7576 setTimeout(executeGreCaptcha, 250); 7577 } else { 7578 console.error('ReCaptcha validation failed: ', e); 7579 } 7580 } 7581 }) 7582 } 7583 7584 const recaptchaUrl = "https://www.google.com/recaptcha/api.js?render=6LfHDCMrAAAAACnDJl581eICSW3VnauItgp_hR2S"; 7585 let existingScript = document.querySelector(`script[src="${recaptchaUrl}"]`); 7586 7587 if (!existingScript) { 7588 existingScript = document.createElement('script'); 7589 existingScript.setAttribute('type', 'text/javascript'); 7590 existingScript.setAttribute('src', recaptchaUrl); 7591 document.head.appendChild(existingScript); 7592 } 7593 7594 if (window.grecaptcha) { 7595 executeGreCaptcha(); 7596 } else { 7597 existingScript.addEventListener("load", executeGreCaptcha, false); 7598 } 7599 } 7600 7601 if (this.errors === 0) { 7602 this.dispatchLoginRequest($form); 7603 } 7604 }, 7605 onPrivateContentLoaded() { 7606 const data = this.$event.detail.data; 7607 const isLoggedIn = data.customer && data.customer.firstname; 7608 if (data.cart && !isLoggedIn) {
7609 this.forceAuthentication = !data.cart.isGuestCheckoutAllowed; 7610 } 7611 }, 7612 redirectIfAuthenticated() { 7613 const event = this.$event; 7614 this.open = this.forceAuthentication; 7615 7616 if (event.detail && event.detail.url) { 7617 this.checkoutUrl = event.detail.url; 7618 } 7619 if (!this.forceAuthentication) { 7620 window.location.href = this.checkoutUrl; 7621 } 7622 }, 7623 resetErrors() { 7624 this.errors = 0; 7625 }, 7626 dispatchLoginRequest(form) { 7627 this.isLoading = true; 7628 const username = this.$refs['customer-email'].value; 7629 const password = this.$refs['customer-password'].value; 7630 const formKey = hyva.getFormKey(); 7631 const bodyFields = { 7632 'username': username, 7633 'password': password, 7634 'formKey': formKey 7635 }; 7636 const fieldName = 'g\u002Drecaptcha\u002Dresponse'; 7637 const recaptchaField = fieldName && form[fieldName]; 7638 if (recaptchaField) { 7639 bodyFields[fieldName] = recaptchaField.value; 7640 } 7641 fetch('https://www.banham.co.uk/customer/ajax/login/', { 7642 method: 'POST', 7643 headers: { 7644 'Content-Type': 'application/json', 7645 'X-Requested-With': 'XMLHttpRequest' 7646 }, 7647 body: JSON.stringify(bodyFields) 7648 } 7649 ).then(response => { 7650 return response.json() 7651 } 7652 ).then(data => { 7653 this.isLoading = false; 7654 if (data.errors) { 7655 this.setErrorMessages(data.message); 7656 this.errors = 1; 7657 this.hasCaptchaToken = 0; 7658 } else { 7659 window.location.href = this.checkoutUrl; 7660 } 7661 }); 7662 }, 7663 eventListeners: { 7664 ['@private-content-loaded.window']() { 7665 this.onPrivateContentLoaded(); 7666 }, 7667 ['@toggle-authentication.window']() { 7668 this.redirectIfAuthenticated(); 7669 }, 7670 ['@pageshow.window'](event) { 7671 if (event.persisted) { 7672 this.close(); 7673 }; 7674 } 7675 } 7676 } 7677 } 7678 7679 window.addEventListener('alpine:init', () => Alpine.data('initAuthentication', initAuthentication), {once: true}) 7680</script>
vendor: 1 bytes, line 7680
7680
7681<script type="application/ld+json"> 7682 { "@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{"@type":"ListItem","position":1,"item":{"@id":"https:\/\/www.banham.co.uk\/","name":"Home"}}] } 7683 </script>
vendor: 1 bytes, line 7683
7683
7684<script> 7685 function initMessages() { 7686 "use strict"; 7687 return { 7688 isSticky: false, 7689 messages: window.mageMessages || [], 7690 init() { 7691 window.addEventListener('scroll', () => { 7692 this.isSticky = window.scrollY > 50; 7693 }); 7694 }, 7695 sectionClass() { 7696 return this.isSticky 7697 ? 'fixed top-[60px] lg:top-[84px] left-0 z-[99] w-full' 7698 : 'fixed\u0020w\u002Dfull\u0020top\u002D\u005B84px\u005D\u0020z\u002D\u005B99\u005D relative w-full'; 7699 }, 7700 isEmpty() { 7701 return this.messages.reduce( 7702 function (isEmpty, message) { 7703 return isEmpty && message === undefined 7704 }, true 7705 ) 7706 }, 7707 hasMessages() { 7708 return !this.isEmpty(); 7709 }, 7710 hasMessage() { 7711 return !!this.message; 7712 }, 7713 removeMessage() { 7714 this.messages[this.index] = undefined; 7715 }, 7716 addMessages(messages, hideAfter) { 7717 messages.map((message) => { 7718 this.messages = this.messages.concat(message); 7719 if (hideAfter === undefined && message.type === 'success' && window.defaultSuccessMessageTimeout) { 7720 hideAfter = window.defaultSuccessMessageTimeout; 7721 } 7722 if (hideAfter) { 7723 this.setHideTimeOut(this.messages.length -1, hideAfter); 7724 } 7725 }); 7726 }, 7727 setHideTimeOut(messageIndex, hideAfter) { 7728 setTimeout((messageIndex) => { 7729 this.messages[messageIndex] = undefined; 7730 }, hideAfter, messageIndex); 7731 }, 7732 eventListeners: { 7733 ['@messages-loaded.window'](event) { 7734 this.addMessages(event.detail.messages, event.detail.hideAfter) 7735 }, 7736 ['@private-content-loaded.window'](event) { 7737 const data = event.detail.data; 7738 if ( 7739 data.messages && 7740 data.messages.messages && 7741 data.messages.messages.length 7742 ) { 7743 this.addMessages(data.messages.messages); 7744 } 7745 }, 7746 ['@clear-messages.window']() { 7747 this.messages = []; 7748 } 7749 }, 7750 getMessageUiId() { 7751 return 'message-' + this.message.type; 7752 } 7753 } 7754 } 7755 7756 window.addEventListener('alpine:init', () => Alpine.data('initMessages', initMessages), {once: true}) 7757</script>
vendor: 1 bytes, line 7757
7757
7758<script> 7759document.addEventListener('DOMContentLoaded', function () { 7760 let $carouselSettings = {"_uid":"1f4ec4e1-2e59-4398-b951-7d2158fe8fa5","dots":false,"arrows":false,"autoplay":false,"interval":"","component":"carousel","transition":"fade"}; 7761 let $container = '.hero-carousel-167baedb-527e-4e20-95f7-f8f91ddbb9c4 .splide'; 7762 let $slides = [{"_uid":"299bf8e1-0b4a-4f78-a5be-d01af54ed0bf","body":"To celebrate 100 years of Banham, we went behind closed doors to discover what you keep under lock and key. Meet Keith & Sue and their collection of 8,500 teapots.","link":[{"url":{"id":"","url":"\/journal\/keith-and-sue-teapot-collection","linktype":"url","fieldtype":"multilink","cached_url":"\/journal\/keith-and-sue-teapot-collection"},"_uid":"7d8177f5-d478-4178-acff-93591ed811a0","type":true,"title":"Read More","component":"link","text_color":"default","button_size":"default","button_style":"btn-lighter"}],"title":"Protect What You Cherish","component":"hero_slide","title_tag":"h1","vimeo_url":"https:\/\/vimeo.com\/1215733291","overlay_opacity":{"value":0.3,"plugin":"storyblok-slider"},"background_asset":[{"_uid":"81dd85be-c05f-408c-bbaa-63c24005065d","mobile":{"id":null,"alt":null,"name":"","focus":null,"title":null,"filename":null,"copyright":null,"fieldtype":"asset","meta_data":[],"is_external_url":false},"tablet":{"id":null,"alt":null,"name":"","focus":null,"title":null,"filename":null,"copyright":null,"fieldtype":"asset","meta_data":[],"is_external_url":false},"desktop":{"id":null,"alt":null,"name":"","focus":null,"title":null,"filename":null,"copyright":null,"fieldtype":"asset","meta_data":[],"is_external_url":false},"component":"image_block"}],"content_position":[{"_uid":"f19544e0-fd95-4d93-836e-938749c22649","vertical":"bottom","component":"text_position","horizontal":"center"}]}]; 7763 7764 if ($slides.length > 1) { 7765 new Splide($container, { 7766 type: $carouselSettings['transition'] ?? 'slide', 7767 pagination: $carouselSettings['dots'] ?? true, 7768 autoplay: $carouselSettings['autoplay'] ?? false, 7769 interval: $carouselSettings['interval'] ? Number($carouselSettings['interval']) : 3000, 7770 perPage: 1, 7771 arrows: false, 7772 mediaQuery: 'min', 7773 breakpoints: { 7774 1024: { 7775 arrows: $carouselSettings['arrows'] ?? false, 7776 }, 7777 } 7778 }).mount(); 7779 } 7780}); 7781</script>
vendor: 1 bytes, line 7781
7781
7782<script> 7783 function responsiveMedia(desktop, tablet, mobile, fallback, isVideoFlag) { 7784 return { 7785 src: fallback, 7786 isVideo: isVideoFlag === '1', 7787 isPlaying: true, 7788 isMuted: true, 7789 updateSrc() { 7790 const w = window.innerWidth; 7791 let newSrc = fallback; 7792 if (w <= 767 && mobile) { 7793 newSrc = mobile; 7794 } else if (w <= 1023 && tablet) { 7795 newSrc = tablet; 7796 } else { 7797 newSrc = desktop || tablet || mobile; 7798 } 7799 this.src = newSrc; 7800 this.isVideo = /\.(mp4|webm|ogg)$/i.test(newSrc); 7801 }, 7802 init() { 7803 this.updateSrc(); 7804 window.addEventListener('resize', () => this.updateSrc()); 7805 this.$nextTick(() => { 7806 if (this.$refs.videoEl) { 7807 this.$refs.videoEl.muted = this.isMuted; 7808 } 7809 }); 7810 }, 7811 togglePlay() { 7812 const video = this.$refs.videoEl; 7813 if (!video) return; 7814 if (video.paused) { 7815 video.play(); 7816 this.isPlaying = true; 7817 } else { 7818 video.pause(); 7819 this.isPlaying = false; 7820 } 7821 }, 7822 toggleMute() { 7823 const video = this.$refs.videoEl; 7824 if (!video) return; 7825 this.isMuted = !this.isMuted; 7826 video.muted = this.isMuted; 7827 } 7828 } 7829 } 7830 </script>
vendor: 1 bytes, line 7830
7830
7831<script> 7832document.addEventListener('DOMContentLoaded', function () { 7833 let $container = '.image-card-grid-1d86e92d-ab20-4142-b0cc-277bc945a160 .splide'; 7834 7835 new Splide($container, { 7836 type: 'slide', 7837 pagination: false, 7838 autoplay: false, 7839 interval: 3000, 7840 arrows: false, 7841 mediaQuery: 'min', 7842 breakpoints: { 7843 1024: { 7844 destroy: true, 7845 }, 7846 768: { 7847 gap: '2.5rem', 7848 padding: { gap: '26rem' }, 7849 }, 7850 7851 0: { 7852 gap: '1.25rem', 7853 padding: { right: '6rem' } 7854 } 7855 } 7856 }).mount(); 7857}); 7858</script>
vendor: 1 bytes, line 7858
7858
7859<script> 7860document.addEventListener('DOMContentLoaded', function () { 7861 let $container = '.image-card-grid-c56b845a-9bdb-4f24-a123-cabcd9cef9b0 .splide'; 7862 7863 new Splide($container, { 7864 type: 'slide', 7865 pagination: false, 7866 autoplay: false, 7867 interval: 3000, 7868 arrows: false, 7869 mediaQuery: 'min', 7870 breakpoints: { 7871 1024: { 7872 destroy: true, 7873 }, 7874 768: { 7875 gap: '2.5rem', 7876 padding: { gap: '26rem' }, 7877 }, 7878 7879 0: { 7880 gap: '1.25rem', 7881 padding: { right: '6rem' } 7882 } 7883 } 7884 }).mount(); 7885}); 7886</script>
vendor: 1 bytes, line 7886
7886
7887<script> 7888document.addEventListener('DOMContentLoaded', function () { 7889 let $container = '.image-card-grid-101c06c1-0443-420c-ace6-4f1b76dbf01e .splide'; 7890 7891 new Splide($container, { 7892 type: 'slide', 7893 pagination: false, 7894 autoplay: false, 7895 interval: 3000, 7896 arrows: false, 7897 mediaQuery: 'min', 7898 breakpoints: { 7899 1024: { 7900 destroy: true, 7901 }, 7902 768: { 7903 gap: '2.5rem', 7904 padding: { gap: '26rem' }, 7905 }, 7906 7907 0: { 7908 gap: '1.25rem', 7909 padding: { right: '6rem' } 7910 } 7911 } 7912 }).mount(); 7913}); 7914</script>
vendor: 1 bytes, line 7914
7914
7915<script> 7916document.addEventListener('DOMContentLoaded', function () { 7917 const totalTabs = 2; 7918 const $container = '.tabs-box .tab-links.splide'; 7919 let splide = null; 7920 7921 // Initialize Splide only if more than 2 tabs 7922 if (totalTabs > 2) { 7923 splide = new Splide($container, { 7924 type: 'slide', 7925 autoplay: false, 7926 pagination: false, 7927 arrows: true, 7928 interval: 3000, 7929 mediaQuery: 'min', 7930 drag: false, 7931 breakpoints: { 7932 1024: { destroy: true }, 7933 768: { destroy: true } 7934 } 7935 }); 7936 7937 splide.on('move', function (newIndex) { 7938 const slides = document.querySelectorAll($container + ' .splide__slide'); 7939 const target = slides[newIndex]; 7940 if (target) target.click(); 7941 }); 7942 7943 splide.mount(); 7944 } 7945 7946 // --- Always handle hash navigation --- 7947 function activateTabFromHash() { 7948 const hash = window.location.hash.substring(1); 7949 if (!hash) return; 7950 7951 const allTabs = document.querySelectorAll('.tabs-box [data-id]'); 7952 const target = document.querySelector('[data-id="' + hash + '"]'); 7953 if (!target) return; 7954 7955 // If Splide exists, move to that slide 7956 if (splide) { 7957 const index = Array.from(allTabs).indexOf(target); 7958 if (index >= 0) splide.go(index); 7959 } 7960 7961 target.click(); 7962 7963 const tabSection = document.querySelector('.tabs-box .tab-section'); 7964 if (tabSection) { 7965 tabSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); 7966 } 7967 } 7968 7969 activateTabFromHash(); 7970 window.addEventListener('hashchange', activateTabFromHash); 7971 7972 // --- Keyboard navigation (desktop only, if Splide active) --- 7973 if (splide && window.innerWidth > 1024) { 7974 const tabSection = document.querySelector('.tabs-box .tab-section'); 7975 tabSection.setAttribute('tabindex', '0'); 7976 7977 tabSection.addEventListener('keydown', function (event) { 7978 if (!['ArrowLeft', 'ArrowRight'].includes(event.key)) return; 7979 event.preventDefault(); 7980 7981 const slides = document.querySelectorAll($container + ' .splide__slide'); 7982 const active = document.querySelector($container + ' .splide__slide.active'); 7983 const activeIndex = Array.from(slides).indexOf(active); 7984 7985 if (event.key === 'ArrowRight' && activeIndex < slides.length - 1) { 7986 slides[activeIndex + 1].click(); 7987 splide.go(activeIndex + 1); 7988 } else if (event.key === 'ArrowLeft' && activeIndex > 0) { 7989 slides[activeIndex - 1].click(); 7990 splide.go(activeIndex - 1); 7991 } 7992 }); 7993 } 7994}); 7995</script>
vendor: 1 bytes, line 7995
7995
7996<script> 7997document.addEventListener('DOMContentLoaded', function () { 7998 let $carouselSettings = {"_uid":"ea93e016-cd35-44ef-b860-c10531eb902b","dots":false,"arrows":false,"autoplay":false,"interval":"","component":"carousel","transition":"fade"}; 7999 let $container = '.hero-carousel-f984a7ff-5d45-49c3-9473-9cca511cfa65 .splide'; 8000 let $slides = [{"_uid":"9a04c72a-376c-48ac-9d25-7f6a554be009","body":"Meet Liz West and her record-breaking collection. \n","link":[{"url":{"id":"","url":"\/journal\/liz-west-spice-girls-collection","linktype":"url","fieldtype":"multilink","cached_url":"\/journal\/liz-west-spice-girls-collection"},"_uid":"508ea678-b82d-4568-a627-eef14cdc28bb","type":true,"title":"Read More","component":"link","text_color":"default","button_size":"default","button_style":"btn-lighter"}],"title":"The Spice Girls Hall of Fame","component":"hero_slide","title_tag":"h1","vimeo_url":"https:\/\/vimeo.com\/1215113358","overlay_opacity":{"value":0.3,"plugin":"storyblok-slider"},"background_asset":[{"_uid":"ae1f8069-6678-4852-80f7-522c2a2ad1eb","mobile":{"id":null,"alt":null,"name":"","focus":null,"title":null,"filename":null,"copyright":null,"fieldtype":"asset","meta_data":[],"is_external_url":false},"tablet":{"id":null,"alt":null,"name":"","focus":null,"title":null,"filename":null,"copyright":null,"fieldtype":"asset","meta_data":[],"is_external_url":false},"desktop":{"id":null,"alt":null,"name":"","focus":null,"title":null,"filename":null,"copyright":null,"fieldtype":"asset","meta_data":[],"is_external_url":false},"component":"image_block"}],"content_position":[{"_uid":"54ee8a90-4c16-4075-ae2b-dcdd0cb29b1a","vertical":"bottom","component":"text_position","horizontal":"center"}]}]; 8001 8002 if ($slides.length > 1) { 8003 new Splide($container, { 8004 type: $carouselSettings['transition'] ?? 'slide', 8005 pagination: $carouselSettings['dots'] ?? true, 8006 autoplay: $carouselSettings['autoplay'] ?? false, 8007 interval: $carouselSettings['interval'] ? Number($carouselSettings['interval']) : 3000, 8008 perPage: 1, 8009 arrows: false, 8010 mediaQuery: 'min', 8011 breakpoints: { 8012 1024: { 8013 arrows: $carouselSettings['arrows'] ?? false, 8014 }, 8015 } 8016 }).mount(); 8017 } 8018}); 8019</script>
vendor: 1 bytes, line 8019
8019
8020<script> 8021document.addEventListener('DOMContentLoaded', function () { 8022 let $container = '.image-card-grid-5266d2bf-33d3-4ffc-9f52-7e13d43c1af9 .splide'; 8023 8024 new Splide($container, { 8025 type: 'slide', 8026 pagination: false, 8027 autoplay: false, 8028 interval: 3000, 8029 arrows: false, 8030 mediaQuery: 'min', 8031 breakpoints: { 8032 1024: { 8033 destroy: true, 8034 }, 8035 768: { 8036 gap: '2.5rem', 8037 padding: { gap: '26rem' }, 8038 }, 8039 8040 0: { 8041 gap: '1.25rem', 8042 padding: { right: '6rem' } 8043 } 8044 } 8045 }).mount(); 8046}); 8047</script>
vendor: 1 bytes, line 8047
8047
8048<script type="application/ld+json"> 8049 { 8050 "microdata": null, 8051 "jsonld": [ 8052 { 8053 "@context": "https://schema.org/", 8054 "@type": "Service", 8055 "serviceType": "Security services", 8056 "provider": { 8057 "@type": "LocalBusiness", 8058 "name": "Banham Security", 8059 "address": { 8060 "@type": "PostalAddress", 8061 "streetAddress": "20 Thornsett Road", 8062 "addressLocality": "Earlsfield, London", 8063 "addressRegion": "SW18 4EF", 8064 "postalCode": "SW18 4EF", 8065 "addressCountry": "GB" 8066 }, 8067 "telephone": "+44-20-7622-5151", 8068 "email": "[email protected]", 8069 "url": "https://www.banham.co.uk/", 8070 "knowsAbout": [ 8071 "burglar alarms", 8072 "CCTV", 8073 "alarm monitoring" 8074 ], 8075 "priceRange": "1000 GBP", 8076 "image": "https://www.banham.co.uk/media/catalog/category/burglar_alarms_header.jpg" 8077 }, 8078 "areaServed": { 8079 "@type": "Place", 8080 "name": "London and the Southeast" 8081 } 8082 }, 8083 { 8084 "@context": "https://schema.org/", 8085 "@type": "Organization", 8086 "name": "Banham Security", 8087 "alternateName": "Banham", 8088 "url": "https://www.banham.co.uk/", 8089 "logo": "https://www.banham.co.uk/static/version1639663510/frontend/bh/bh/en_GB/images/banham-logo.svg", 8090 "address": { 8091 "@type": "PostalAddress", 8092 "addressLocality": "London, United Kingdom", 8093 "postalCode": "SW18 4EF", 8094 "streetAddress": "20 Thornsett Road, Earlsfield", 8095 "addressCountry": "GB" 8096 }, 8097 "email": "[email protected]", 8098 "contactPoint": { 8099 "@type": "ContactPoint", 8100 "telephone": "+4402076225151", 8101 "contactType": "customer service", 8102 "areaServed": "GB", 8103 "availableLanguage": "en" 8104 }, 8105 "sameAs": [ 8106 "https://twitter.com/banhamsecurity", 8107 "https://www.facebook.com/Banham.Group/", 8108 "https://www.instagram.com/banhamgroup/", 8109 "https://www.pinterest.co.uk/banhamgroup/" 8110 ] 8111 } 8112 ], 8113 "graph": null 8114} </script>
vendor: 1 bytes, line 8114
8114
8115<script> 8116 function initNewsletterForm() { 8117 return { 8118 errors: 0, 8119 hasCaptchaToken: 0, 8120 displayErrorMessage: false, 8121 errorMessages: [], 8122 setErrorMessages(messages) { 8123 this.errorMessages = [messages] 8124 this.displayErrorMessage = this.errorMessages.length 8125 }, 8126 submitForm() { 8127 // Do not rename $form, the variable is expected to be declared in the recaptcha output 8128 const $form = document.querySelector('#newsletter-validate-detail'); 8129 8130 if (this.errors === 0) { 8131 $form.submit(); 8132 } 8133 } 8134 } 8135 } 8136 8137 window.addEventListener('alpine:init', () => Alpine.data('initNewsletterForm', initNewsletterForm), {once: true}) 8138 </script>
vendor: 1 bytes, line 8138
8138
8139<script> 8140document.addEventListener('alpine:init', () => { 8141 Alpine.store('footerResponsive', { 8142 isMobile: window.matchMedia('(max-width: 767px)').matches, 8143 openIndex: null, 8144 init() { 8145 const matchMedia = window.matchMedia('(max-width: 767px)'); 8146 this.isMobile = matchMedia.matches; 8147 8148 const handleChange = (event) => { 8149 this.isMobile = event.matches; 8150 if (!this.isMobile) this.openIndex = null; 8151 }; 8152 8153 if (typeof matchMedia.onchange !== 'object') { 8154 matchMedia.addListener(handleChange); 8155 } else { 8156 matchMedia.addEventListener('change', handleChange); 8157 } 8158 }, 8159 toggle(index) { 8160 this.openIndex = this.openIndex === index ? null : index; 8161 }, 8162 isOpen(index) { 8163 return this.openIndex === index; 8164 } 8165 }); 8166 8167 // FIX: run store init manually (stores donât auto-run init) 8168 Alpine.store('footerResponsive').init(); 8169}); 8170 8171document.addEventListener('DOMContentLoaded', function () { 8172 const el = document.querySelector('.pre-footer'); 8173 if (el) { 8174 new Splide(el, { 8175 perPage: 1, 8176 type: 'loop', 8177 pagination: true, 8178 arrows: false, 8179 mediaQuery: 'min', 8180 autoplay: true, 8181 interval: 3000, 8182 breakpoints: { 8183 1024: { destroy: true }, 8184 } 8185 }).mount(); 8186 } 8187}); 8188</script>
vendor: 1 bytes, line 8188
8188
8189<script> 8190function initNewsletterFormModal() { 8191 const showPopup = 1; 8192 const closedCookieName = 'newsletter-modal-closed'; 8193 const trigger = "on_load_delay"; 8194 const waitForShowingTime = 40000; // ms 8195 const scrollThreshold = 80; 8196 8197 if (showPopup == 1) { 8198 window.cookie_consent_config['necessary'] = [].concat( 8199 window.cookie_consent_config['necessary'] || [], 8200 [closedCookieName] 8201 ); 8202 8203 return { 8204 open: false, 8205 closedCookieName: closedCookieName, 8206 8207 firstname: '', 8208 lastname: '', 8209 email: '', 8210 errors: {}, 8211 8212 validateField(field) { 8213 this.errors[field] = ''; 8214 8215 if (field === 'firstname' && !this.firstname.trim()) { 8216 this.errors.firstname = 'First\u0020Name\u0020is\u0020required.'; 8217 } 8218 if (field === 'lastname' && !this.lastname.trim()) { 8219 this.errors.lastname = 'Last\u0020Name\u0020is\u0020required.'; 8220 } 8221 if (field === 'email') { 8222 if (!this.email.trim()) { 8223 this.errors.email = 'Email\u0020is\u0020required.'; 8224 } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.email)) { 8225 this.errors.email = 'Please\u0020enter\u0020a\u0020valid\u0020email\u0020address.'; 8226 } 8227 } 8228 }, 8229 8230 validateForm() { 8231 this.validateField('firstname'); 8232 this.validateField('lastname'); 8233 this.validateField('email'); 8234 return Object.values(this.errors).every(v => v === ''); 8235 }, 8236 8237 submitForm() { 8238 const $form = document.querySelector('#newsletter-validate-modal'); 8239 8240 if (this.validateForm()) { 8241 this.setCloseCookie(); 8242 $form.submit(); 8243 } 8244 }, 8245 8246 init() { 8247 if (!this.shouldShowModal()) return; 8248 8249 switch (trigger) { 8250 case 'on_load_delay': 8251 setTimeout(() => { this.open = true; }, waitForShowingTime); 8252 break; 8253 8254 case 'scroll_percent': 8255 window.addEventListener('scroll', () => { 8256 const percentScrolled = (window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100; 8257 if (percentScrolled > scrollThreshold && !this.open) { 8258 this.open = true; 8259 } 8260 }, { once: false }); 8261 break; 8262 8263 case 'exit_intent': 8264 document.addEventListener('mousemove', (e) => { 8265 if (e.clientY <= 10 && !this.open && !hyva.getCookie(this.closedCookieName)) { 8266 this.open = true; 8267 } 8268 }); 8269 break; 8270 } 8271 }, 8272 8273 closeModal() { 8274 this.setCloseCookie(); 8275 }, 8276 8277 setCloseCookie() { 8278 hyva.setCookie(this.closedCookieName, true, 365); 8279 this.open = false; 8280 }, 8281 8282 shouldShowModal() { 8283 const isInStoryblokPreview = window.location !== window.parent.location; 8284 const currentUrl = window.location.href; 8285 if(isInStoryblokPreview && !currentUrl.includes('newsletter-popup')) { 8286 return false; 8287 } 8288 return !hyva.getCookie(this.closedCookieName); 8289 } 8290 } 8291 } 8292 8293 window.addEventListener('alpine:init', () => Alpine.data('initNewsletterFormModal', initNewsletterFormModal), { once: true }); 8294} 8295</script>
vendor: 1 bytes, line 8295
8295
8296<script> 8297 window.enquiryTopic = ""; 8298 window.enquiryTopicProduct = ""; 8299 window.CRMForm = {enquiryTopic, enquiryTopicProduct}; 8300 8301</script>
vendor: 1 bytes, line 8301
8301
8302<script type="text/javascript" async> 8303 var w = document.createElement("script"); 8304 w.type = "text/javascript"; 8305 w.src = "//widget.trustpilot.com/bootstrap/v5/tp.widget.bootstrap.min.js"; 8306 w.async = true; 8307 document.head.appendChild(w); 8308</script>
vendor: 1 bytes, line 8308
8308
8309<script type="text/javascript"> 8310 (function(w,d,s,r,n){w.TrustpilotObject=n;w[n]=w[n]||function(){(w[n].q=w[n].q||[]).push(arguments)}; 8311 a=d.createElement(s);a.async=1;a.src=r;a.type='text/java'+s;f=d.getElementsByTagName(s)[0]; 8312 f.parentNode.insertBefore(a,f)})(window,document,'script', 'https://invitejs.trustpilot.com/tp.min.js', 'tp'); 8313 tp('register',''); 8314</script>
vendor: 1 bytes, line 8314
8314
8315<script type="text/javascript"> 8316 function inIframe () { 8317 try { 8318 return window.self !== window.top; 8319 } catch (e) { 8320 return false; 8321 } 8322 } 8323 8324 function tryParseJson(str) { 8325 if (typeof str === 'string') { 8326 try { 8327 return JSON.parse(str); 8328 } catch (e) { 8329 return false; 8330 } 8331 } 8332 return false; 8333 } 8334 8335 if (inIframe()) { 8336 window.addEventListener('message', function(e) { 8337 var adminOrign = new URL(window.location).hostname; 8338 var eventOriginHostname = new URL(e.origin).hostname; 8339 if (!e.data || adminOrign !== eventOriginHostname) { 8340 return; 8341 } 8342 if (typeof TrustpilotPreview !== 'undefined') { 8343 if (typeof e.data === 'string' && e.data === 'submit') { 8344 TrustpilotPreview.sendTrustboxes(); 8345 } else { 8346 jsonData = tryParseJson(e.data); 8347 if (jsonData.trustbox) { 8348 TrustpilotPreview.setSettings(jsonData.trustbox); 8349 } else if (jsonData.customised) { 8350 TrustpilotPreview.updateActive(jsonData.customised); 8351 } 8352 } 8353 } else { 8354 var settings = tryParseJson(e.data); 8355 if (settings) { 8356 var p = document.createElement("script"); 8357 p.type = "text/javascript"; 8358 p.onload = function () { 8359 const iFrame = e.source.parent.document.getElementById('configuration_iframe').contentWindow; 8360 TrustpilotPreview.init(['//ecommplugins-scripts.trustpilot.com/v2.1/css/preview.min.css'], settings, iFrame, e.source); 8361 }; 8362 p.src = '//ecommplugins-scripts.trustpilot.com/v2.1/js/preview.min.js'; 8363 document.head.appendChild(p); 8364 } 8365 } 8366 }); 8367 } 8368</script>
vendor: 1 bytes, line 8368
8368
8369<script type="text/javascript" async> 8370 const trustpilot_trustbox_settings = {"trustboxes":[]}; 8371 if (trustpilot_trustbox_settings) { 8372 document.addEventListener('DOMContentLoaded', function() { 8373 tp('trustBox', trustpilot_trustbox_settings); 8374 }); 8375 } 8376</script>
vendor: 1 bytes, line 8376
8376
8377<script> 8378 let SnapSlider = class { 8379 constructor(el, { 8380 labelSepparator = 'of', 8381 autoPager, 8382 groupPager 8383 } = {}) { 8384 this.el = el; 8385 this.track = this.el.querySelector("[data-track]"); 8386 if (!this.track) { 8387 console.warn( 8388 "No Slider track defined, reverting back to CSS slider.\nPlease create a wrapper for your slides with the attribute data-track", 8389 this.el 8390 ); 8391 return; 8392 } 8393 this.initialLoad = true; 8394 this.slides = []; 8395 this.inViewObserver; 8396 this.mutationObserver; 8397 this.resizeObserver; 8398 this.pager = this.el.querySelector("[data-pager]"); 8399 this.navBtns = Array.from( 8400 this.el.querySelectorAll("[data-next], [data-prev]") 8401 ); 8402 this.slideLabelSepparator = this.el.dataset.slideLabelSepparator || labelSepparator; 8403 this.useAutoPager = autoPager || this.el.hasAttribute("data-auto-pager") || false; 8404 this.useGroupPager = groupPager || this.el.hasAttribute("data-group-pager") || false; 8405 this.sliderLabel = this.el.hasAttribute("aria-label") && this.el.getAttribute("aria-label").toLowerCase().trim().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, ""); 8406 this.sliderId = this.el.id || this.sliderLabel || "slider"; 8407 this.markerIdName = "data-target-id"; 8408 this.pagerClasses = this.el.dataset.pagerClass || "snap-pager"; 8409 this.markerClasses = this.el.dataset.markerClass || "snap-marker"; 8410 this.init(); 8411 } 8412 init() { 8413 this.setupSlides(); 8414 this.setupNav(); 8415 if (this.useAutoPager) { 8416 this.createPager(); 8417 } else { 8418 this.setupPager(); 8419 } 8420 this.setupObservers(); 8421 this.setupMutationObserver(); 8422 this.setupResizeObserver(); 8423 this.el.addEventListener("click", this.eventHandler.bind(this)); 8424 this.el.addEventListener("keydown", this.eventHandler.bind(this)); 8425 } 8426 destroy() { 8427 this.inViewObserver?.disconnect(); 8428 this.mutationObserver?.disconnect(); 8429 this.resizeObserver?.disconnect(); 8430 this.el.removeEventListener("click", this.eventHandler.bind(this)); 8431 this.el.removeEventListener("keydown", this.eventHandler.bind(this)); 8432 } 8433 roundUpIfGreaterThan(number, min = 8) { 8434 const decimalPart = number - Math.floor(number); 8435 return decimalPart >= min ? Math.ceil(number) : Math.floor(number); 8436 } 8437 getInViewItems() { 8438 const inViewSlides = this.track.querySelectorAll("[data-in-view]"); 8439 const firstInViewSlide = inViewSlides[0] || null; 8440 const lastInViewSlide = inViewSlides[inViewSlides.length - 1] || null; 8441 const isAtStart = firstInViewSlide === this.slides[0]; 8442 const isAtEnd = lastInViewSlide === this.slides[this.slides.length - 1]; 8443 return { 8444 inViewSlides, 8445 totalInViewSlides: inViewSlides.length, 8446 firstInViewSlide, 8447 lastInViewSlide, 8448 isAtStart, 8449 isAtEnd, 8450 hasNoOverflow: isAtStart && isAtEnd 8451 }; 8452 } 8453 groupPagerMarkers() { 8454 if (this.slides.length === 0 || !this.pager || !this.useGroupPager) return; 8455 const totalVisibleSlides = this.roundUpIfGreaterThan( 8456 this.track.offsetWidth / this.slides[0].offsetWidth 8457 ); 8458 const markers = Array.from(this.pager.querySelectorAll("a, button")); 8459 markers.forEach((marker, index) => { 8460 marker.style.display = index % totalVisibleSlides === 0 ? null : "none"; 8461 }); 8462 } 8463 handleInView(entries) { 8464 entries.forEach((entry) => { 8465 const marker = this.pager && (this.pager.querySelector(`[href="#${entry.target.id}"]`) || this.pager.querySelector( 8466 `[${this.markerIdName}="${entry.target.id}"]` 8467 )); 8468 entry.target.toggleAttribute("data-in-view", entry.isIntersecting); 8469 entry.target.toggleAttribute("inert", !entry.isIntersecting); 8470 marker?.setAttribute("aria-current", entry.isIntersecting); 8471 marker?.setAttribute("tabindex", entry.isIntersecting ? "0" : "-1"); 8472 }); 8473 const { 8474 isAtStart, 8475 isAtEnd, 8476 hasNoOverflow 8477 } = this.getInViewItems(); 8478 this.navBtns.forEach((btn) => { 8479 if (btn.hasAttribute("data-next")) { 8480 btn.style.visibility = hasNoOverflow ? "hidden" : null; 8481 isAtEnd ? btn.setAttribute("disabled", "") : btn.removeAttribute("disabled"); 8482 } 8483 if (btn.hasAttribute("data-prev")) { 8484 btn.style.visibility = hasNoOverflow ? "hidden" : null; 8485 isAtStart ? btn.setAttribute("disabled", "") : btn.removeAttribute("disabled"); 8486 } 8487 }); 8488 if (this.pager) { 8489 this.pager.style.visibility = hasNoOverflow ? "hidden" : null; 8490 } 8491 if (!this.initialLoad) { 8492 this.el.dispatchEvent( 8493 new CustomEvent("slideChange", { 8494 detail: this.getInViewItems() 8495 }) 8496 ); 8497 } else { 8498 this.initialLoad = false;
8499 } 8500 if (document.activeElement.parentElement.hasAttribute("data-pager")) { 8501 const activeItems = this.pager.querySelectorAll('[tabindex="0"]'); 8502 if (activeItems.length) { 8503 activeItems[0].focus({ preventScroll: true }); 8504 } 8505 } 8506 } 8507 getSlides() { 8508 if (!this.track) return []; 8509 return Array.from(this.track.children).filter( 8510 (child) => child.tagName.toLowerCase() !== "template" && child.tagName.toLowerCase() !== "style" && child.tagName.toLowerCase() !== "script" 8511 ); 8512 } 8513 refreshSlides() { 8514 this.initialLoad = true; 8515 this.slides = this.getSlides(); 8516 this.setupSlides(); 8517 if (this.useAutoPager) { 8518 this.createPager(); 8519 } else { 8520 this.setupPager(); 8521 } 8522 this.groupPagerMarkers(); 8523 this.setupObservers(); 8524 } 8525 setupSlides() { 8526 this.slides = this.getSlides(); 8527 this.slides.forEach((slide, index) => { 8528 const totalSlides = this.slides.length; 8529 const currentSlide = index + 1; 8530 const existingLabel = slide.getAttribute("aria-label") || ""; 8531 const isImage = slide.tagName.toLowerCase() === "img" || slide.tagName.toLowerCase() === "picture"; 8532 const slideLabelTag = isImage ? "alt" : "aria-label"; 8533 const hasAutoLabel = existingLabel.startsWith( 8534 `${currentSlide} ${this.slideLabelSepparator} ` 8535 ); 8536 if (!slide.hasAttribute("id")) { 8537 slide.setAttribute( 8538 "id", 8539 `${this.sliderId}-item-${currentSlide}` 8540 ); 8541 } 8542 if (!slide.hasAttribute(slideLabelTag) || slide.getAttribute(slideLabelTag) === "" || hasAutoLabel) { 8543 slide.setAttribute( 8544 slideLabelTag, 8545 `${currentSlide} ${this.slideLabelSepparator} ${totalSlides}` 8546 ); 8547 } 8548 if (slide.tagName.toLowerCase() === "div" && !slide.hasAttribute("role")) { 8549 slide.setAttribute("role", "group"); 8550 } 8551 if (this.pager && !isImage) { 8552 slide.setAttribute("role", "tabpanel"); 8553 } 8554 if (slide.getAttribute("role") === "group") { 8555 slide.setAttribute("aria-roledescription", "item"); 8556 } 8557 }); 8558 if (!this.el.hasAttribute("role") && this.el.tagName !== "SECTION") { 8559 this.el.setAttribute("role", "region"); 8560 } 8561 if (!this.el.hasAttribute("aria-roledescription")) { 8562 this.el.setAttribute("aria-roledescription", "carousel"); 8563 } 8564 this.track.setAttribute("tabindex", 0); 8565 this.track.setAttribute("aria-live", "polite"); 8566 } 8567 setupNav() { 8568 this.navBtns.forEach((btn) => { 8569 btn.setAttribute("disabled", ""); 8570 btn.removeAttribute("hidden"); 8571 btn.classList.remove("invisible"); 8572 btn.style.visibility = null; 8573 }); 8574 } 8575 createPager() { 8576 const newPager = document.createElement("nav"); 8577 newPager.setAttribute("data-pager", ""); 8578 newPager.setAttribute("role", "tablist"); 8579 newPager.classList.add(...this.pagerClasses.split(" ")); 8580 this.slides.forEach((slide, index) => { 8581 const marker = document.createElement("button"); 8582 const slideId = slide.id || `${this.sliderId}-item-${index + 1}`; 8583 marker.setAttribute(this.markerIdName, slideId); 8584 marker.classList.add(...this.markerClasses.split(" ")); 8585 this.setupPagerMarker(marker, index); 8586 newPager.appendChild(marker); 8587 if (slide.tagName.toLowerCase() !== "img" && slide.tagName.toLowerCase() !== "picture") { 8588 slide.removeAttribute("aria-roledescription"); 8589 slide.setAttribute("role", "tabpanel"); 8590 } 8591 }); 8592 if (this.pager) { 8593 this.pager.setAttribute("role", "tablist"); 8594 this.pager.replaceChildren(...newPager.children); 8595 } else { 8596 this.track.after(newPager); 8597 this.pager = this.el.querySelector("[data-pager]"); 8598 } 8599 } 8600 setupPager() { 8601 if (!this.pager) return; 8602 this.pager.setAttribute("role", "tablist"); 8603 const items = Array.from(this.pager.querySelectorAll("a, button"));
8604 items.forEach((marker, index) => this.setupPagerMarker(marker, index)); 8605 } 8606 setupPagerMarker(marker, index) { 8607 const markerId = marker.getAttribute("href")?.slice(1) || marker.getAttribute(this.markerIdName); 8608 const slideId = markerId || `${this.sliderId}-item-${index + 1}`; 8609 if (!markerId) { 8610 marker.setAttribute(this.markerIdName, slideId); 8611 } 8612 marker.setAttribute("role", "tab"); 8613 marker.setAttribute("aria-controls", slideId); 8614 marker.setAttribute("aria-posinset", index + 1); 8615 marker.setAttribute("aria-setsize", this.slides.length); 8616 marker.setAttribute("tabindex", "-1"); 8617 if (!marker.hasAttribute("aria-label")) { 8618 marker.setAttribute("aria-label", `Slide ${index + 1}`); 8619 } 8620 } 8621 setupObservers() { 8622 if (this.inViewObserver) { 8623 this.inViewObserver.disconnect(); 8624 } 8625 this.inViewObserver = new IntersectionObserver( 8626 this.handleInView.bind(this), { 8627 root: this.track, 8628 threshold: 0.8 8629 } 8630 ); 8631 this.slides.forEach((slide) => this.inViewObserver.observe(slide)); 8632 } 8633 setupMutationObserver() { 8634 this.mutationObserver = new MutationObserver( 8635 this.refreshSlides.bind(this) 8636 ); 8637 this.mutationObserver.observe(this.track, { 8638 childList: true, 8639 subtree: false 8640 }); 8641 } 8642 setupResizeObserver() { 8643 this.resizeObserver = new ResizeObserver( 8644 this.groupPagerMarkers.bind(this) 8645 ); 8646 this.resizeObserver.observe(this.el); 8647 } 8648 goToSlideDir(dir = "next") { 8649 const { 8650 firstInViewSlide, 8651 lastInViewSlide 8652 } = this.getInViewItems(); 8653 const isPrev = dir === "prev"; 8654 let targetSlide = isPrev ? firstInViewSlide?.previousElementSibling : lastInViewSlide?.nextElementSibling; 8655 if (!targetSlide) return; 8656 if (targetSlide.tagName.toLowerCase() === "template") { 8657 targetSlide = isPrev ? targetSlide?.previousElementSibling : targetSlide?.nextElementSibling; 8658 } 8659 targetSlide.scrollIntoView({ block: "nearest", inline: isPrev ? "end" : "start" }); 8660 } 8661 pagerToSlide(event) { 8662 if (!event.target.closest("[data-pager]")) return; 8663 const marker = event.target.closest("a, button"); 8664 if (!marker) return; 8665 event.preventDefault(); 8666 const slideId = marker.getAttribute("href")?.slice(1) || marker.getAttribute(this.markerIdName); 8667 const targetSlide = this.track.querySelector(`#${slideId}`); 8668 targetSlide?.scrollIntoView({ block: "nearest", inline: "start" }); 8669 } 8670 eventHandler(event) { 8671 const target = event.target.closest( 8672 "[data-next], [data-prev], [data-pager]" 8673 ); 8674 if (!target) return; 8675 if (event.type === "click") { 8676 if (target.hasAttribute("data-next")) { 8677 this.goToSlideDir("next"); 8678 } else if (target.hasAttribute("data-prev")) { 8679 this.goToSlideDir("prev"); 8680 } else if (target.closest("[data-pager]")) { 8681 this.pagerToSlide(event); 8682 } 8683 } 8684 if (event.type === "keydown" && target.closest("[data-pager]")) { 8685 if (event.key === "ArrowRight") { 8686 this.goToSlideDir("next"); 8687 } else if (event.key === "ArrowLeft") { 8688 this.goToSlideDir("prev"); 8689 } 8690 } 8691 } 8692 }; 8693 8694 (() => { 8695 function alpineSnapSlider(Alpine) { 8696 Alpine.directive("snap-slider", (el, { 8697 modifiers 8698 }, { 8699 cleanup 8700 }) => { 8701 const autoPager = modifiers.includes("auto-pager");
8702 const groupPager = modifiers.includes("group-pager"); 8703 const slider = new SnapSlider(el, { 8704 autoPager, 8705 groupPager 8706 }); 8707 cleanup(() => { 8708 slider.destroy(); 8709 }); 8710 }); 8711 } 8712 8713 document.addEventListener("alpine:init", () => { 8714 window.Alpine.plugin(alpineSnapSlider); 8715 }); 8716 })();</script>
vendor: 1 bytes, line 8716
8716
8717<script> 8718 (() => { 8719 // src/dialog.js 8720 function dialog_default(Alpine) { 8721 Alpine.directive("htmldialog", fylgjaAlpineDialog); 8722 8723 function fylgjaAlpineDialog(el, { 8724 expression, 8725 modifiers 8726 }, { 8727 evaluateLater, 8728 cleanup 8729 }) { 8730 const evaluate = expression.length ? evaluateLater(expression) : () => {}; 8731 const lockPageScroll = modifiers.includes("noscroll"); 8732 const closeby = modifierValue(modifiers, "closeby", "any"); 8733 el.style.display = null; 8734 el.hidden = false; 8735 el.style.length === 0 && el.removeAttribute("style"); 8736 el.addEventListener("keydown", escapeDialog); 8737 el.addEventListener("click", backdropDialog); 8738 el.addEventListener("submit", methodDialog); 8739 8740 function scrollLock(use = true) { 8741 document.body.style.overflow = use ? "hidden" : ""; 8742 } 8743 8744 function methodDialog(event) { 8745 if (event.target.getAttribute("method") === "dialog" || event.submitter && event.submitter.getAttribute("formmethod") === "dialog") { 8746 evaluate(); 8747 } 8748 } 8749 8750 function escapeDialog(event) { 8751 if (event.key !== "Escape") 8752 return; 8753 event.preventDefault(); 8754 if (closeby !== "any" && closeby !== "closerequest") 8755 return; 8756 evaluate(); 8757 } 8758 8759 function backdropDialog(event) { 8760 if (event.target !== el || closeby !== "any") 8761 return; 8762 const rect = el.getBoundingClientRect(); 8763 const isInDialog = rect.top <= event.clientY && event.clientY <= rect.top + rect.height && rect.left <= event.clientX && event.clientX <= rect.left + rect.width; 8764 if (isInDialog) 8765 return; 8766 evaluate(); 8767 } 8768 el._x_doShow = () => { 8769 if (el.hasAttribute("open")) 8770 return; 8771 el.showModal(); 8772 scrollLock(lockPageScroll); 8773 }; 8774 el._x_doHide = () => { 8775 if (!el.hasAttribute("open")) 8776 return; 8777 el.close(); 8778 scrollLock(false); 8779 }; 8780 cleanup(() => { 8781 el.removeEventListener("keydown", escapeDialog); 8782 el.removeEventListener("click", backdropDialog); 8783 el.removeEventListener("submit", methodDialog); 8784 scrollLock(false); 8785 }); 8786 } 8787 } 8788 8789 function modifierValue(modifiers, key, fallback) { 8790 if (modifiers.indexOf(key) === -1) 8791 return fallback; 8792 const rawValue = modifiers[modifiers.indexOf(key) + 1]; 8793 if (!rawValue) 8794 return fallback; 8795 if (key === "closeby") { 8796 const allowedValues = ["auto", "none", "closerequest", "any"]; 8797 const options = allowedValues.join(", "); 8798 if (!allowedValues.includes(rawValue)) { 8799 console.warn( 8800 `"${rawValue}" is not one of the allowed values for closeby: ${options}` 8801 ); 8802 } 8803 return fallback; 8804 } 8805 return rawValue; 8806 } 8807 8808 // src/cdn.js 8809 document.addEventListener("alpine:init", () => { 8810 window.Alpine.plugin(dialog_default); 8811 }); 8812 })(); 8813</script>
vendor: 1 bytes, line 8813
8813
8814<script> 8815 (() => { 8816 function src_default(Alpine) { 8817 Alpine.directive("intersect", Alpine.skipDuringClone((el, { value, expression, modifiers }, { evaluateLater, cleanup }) => { 8818 let evaluate = evaluateLater(expression); 8819 let options = { 8820 rootMargin: getRootMargin(modifiers), 8821 threshold: getThreshold(modifiers) 8822 }; 8823 let observer = new IntersectionObserver((entries) => { 8824 entries.forEach((entry) => { 8825 if (entry.isIntersecting === (value === "leave")) 8826 return; 8827 evaluate(); 8828 modifiers.includes("once") && observer.disconnect(); 8829 }); 8830 }, options); 8831 observer.observe(el); 8832 cleanup(() => { 8833 observer.disconnect(); 8834 }); 8835 })); 8836 } 8837 function getThreshold(modifiers) { 8838 if (modifiers.includes("full")) 8839 return 0.99; 8840 if (modifiers.includes("half")) 8841 return 0.5; 8842 if (!modifiers.includes("threshold")) 8843 return 0; 8844 let threshold = modifiers[modifiers.indexOf("threshold") + 1]; 8845 if (threshold === "100") 8846 return 1; 8847 if (threshold === "0") 8848 return 0; 8849 return Number(`.${threshold}`); 8850 } 8851 function getLengthValue(rawValue) { 8852 let match = rawValue.match(/^(-?[0-9]+)(px|%)?$/); 8853 return match ? match[1] + (match[2] || "px") : void 0; 8854 } 8855 function getRootMargin(modifiers) { 8856 const key = "margin"; 8857 const fallback = "0px 0px 0px 0px"; 8858 const index = modifiers.indexOf(key); 8859 if (index === -1) 8860 return fallback; 8861 let values = []; 8862 for (let i = 1; i < 5; i++) { 8863 values.push(getLengthValue(modifiers[index + i] || "")); 8864 } 8865 values = values.filter((v) => v !== void 0); 8866 return values.length ? values.join(" ").trim() : fallback; 8867 } 8868 8869 document.addEventListener("alpine:init", () => { 8870 window.Alpine.plugin(src_default); 8871 }); 8872 })();</script>
vendor: 1 bytes, line 8872
8872
8873<script> 8874 for (const [selector, deferUntil] of Object.entries({".product-slider section[x-data]:not([x-data] [x-data])":"intersect",".product-info [x-data]:not([x-data] [x-data])":"intersect","#filters-content [x-data]:not([x-data] [x-data])":"intersect","#review_form":"intersect","section[x-data^=initRecentlyViewedProductsComponent]":"intersect","div[x-data^=initBundleOptions]":"intersect","#product_addtocart_form [x-data]:not([x-data] [x-data])":"intersect","#notice-cookie-block":"idle"})) { 8875 document.querySelectorAll(selector).forEach(el => el.setAttribute('x-defer', `${deferUntil}`)); 8876 }</script>
vendor: 1 bytes, line 8876
8876
8877<script> 8878 (() => { 8879 const hasAlpine = new Promise(resolve => { 8880 window.addEventListener('alpine:initialized', resolve, {once: true, passive: true}); 8881 }); 8882 8883 const hasInteract = new Promise(resolve => { 8884 (events => { 8885 const onInteract = () => { 8886 resolve(); 8887 events.forEach(type => window.removeEventListener(type, onInteract)); 8888 } 8889 events.forEach(type => window.addEventListener(type, onInteract, {once: true, passive: true})) 8890 })(['touchstart', 'mouseover', 'wheel', 'scroll', 'keydown']) 8891 }); 8892 8893 const onIntersect = (el) => { 8894 return new Promise(resolve => { 8895 const observer = new IntersectionObserver(entries => { 8896 for (const entry of entries) { 8897 if (entry.isIntersecting) { 8898 observer.disconnect() 8899 resolve(); 8900 } 8901 } 8902 }, {}); 8903 observer.observe(el); 8904 }); 8905 } 8906 8907 function runComponent(el) { 8908 hasAlpine.then(() => { 8909 el.removeAttribute('x-ignore'); 8910 queueMicrotask(() => Alpine.initTree(el)); 8911 }); 8912 } 8913 8914 function initDeferredComponents() { 8915 document.querySelectorAll('[x-data][x-defer]').forEach(el => { 8916 el.setAttribute('x-ignore', ''); 8917 const deferUntil = (el.getAttribute('x-defer') || '').trim(); 8918 switch (deferUntil) { 8919 case 'interact': 8920 hasInteract.then(() => runComponent(el)); 8921 break; 8922 case 'intersect': 8923 onIntersect(el).then(() => runComponent(el)) 8924 break; 8925 case 'idle': 8926 window.requestIdleCallback 8927 ? window.requestIdleCallback(() => runComponent(el), {timeout: 4000}) 8928 : setTimeout(() => runComponent(el), 4000); 8929 break; 8930 case 'eager': 8931 runComponent(el); 8932 break; 8933 default: 8934 if (deferUntil.startsWith('event:') && deferUntil.length > 6) { 8935 window.addEventListener(deferUntil.substring(6), () => runComponent(el), {once: true, passive: true}); 8936 } 8937 } 8938 }); 8939 } 8940 8941 window.addEventListener('alpine:init', initDeferredComponents, {once: true, passive: true}); 8942 })();</script>
vendor: 1 bytes, line 8942
8942
vendor: 4,405 bytes, lines 8943-9050
8943<script> 8944 (() => { 8945 // packages/collapse/src/index.js 8946 function src_default(Alpine) { 8947 Alpine.directive("collapse", collapse); 8948 collapse.inline = (el, { 8949 modifiers 8950 }) => { 8951 if (!modifiers.includes("min")) 8952 return; 8953 el._x_doShow = () => {}; 8954 el._x_doHide = () => {}; 8955 }; 8956 8957 function collapse(el, { 8958 modifiers 8959 }) { 8960 let duration = modifierValue(modifiers, "duration", 250) / 1e3; 8961 let floor = modifierValue(modifiers, "min", 0); 8962 let fullyHide = !modifiers.includes("min"); 8963 if (!el._x_isShown) 8964 el.style.height = `${floor}px`; 8965 if (!el._x_isShown && fullyHide) 8966 el.hidden = true; 8967 if (!el._x_isShown) 8968 el.style.overflow = "hidden"; 8969 let setFunction = (el2, styles) => { 8970 let revertFunction = Alpine.setStyles(el2, styles); 8971 return styles.height ? () => {} : revertFunction; 8972 }; 8973 let transitionStyles = { 8974 transitionProperty: "height", 8975 transitionDuration: `${duration}s`, 8976 transitionTimingFunction: "cubic-bezier(0.4, 0.0, 0.2, 1)" 8977 }; 8978 el._x_transition = { 8979 in(before = () => {}, after = () => {}) { 8980 if (fullyHide) 8981 el.hidden = false; 8982 if (fullyHide) 8983 el.style.display = null; 8984 let current = el.getBoundingClientRect().height; 8985 el.style.height = "auto"; 8986 let full = el.getBoundingClientRect().height; 8987 if (current === full) { 8988 current = floor; 8989 } 8990 Alpine.transition(el, Alpine.setStyles, { 8991 during: transitionStyles, 8992 start: { 8993 height: current + "px" 8994 }, 8995 end: { 8996 height: full + "px" 8997 } 8998 }, () => el._x_isShown = true, () => { 8999 if (Math.abs(el.getBoundingClientRect().height - full) < 1) { 9000 el.style.overflow = null; 9001 } 9002 }); 9003 }, 9004 out(before = () => {}, after = () => {}) { 9005 let full = el.getBoundingClientRect().height; 9006 Alpine.transition(el, setFunction, { 9007 during: transitionStyles, 9008 start: { 9009 height: full + "px" 9010 }, 9011 end: { 9012 height: floor + "px" 9013 } 9014 }, () => el.style.overflow = "hidden", () => { 9015 el._x_isShown = false; 9016 if (el.style.height == `${floor}px` && fullyHide) { 9017 el.style.display = "none"; 9018 el.hidden = true; 9019 } 9020 }); 9021 } 9022 }; 9023 } 9024 } 9025 9026 function modifierValue(modifiers, key, fallback) { 9027 if (modifiers.indexOf(key) === -1) 9028 return fallback; 9029 const rawValue = modifiers[modifiers.indexOf(key) + 1]; 9030 if (!rawValue) 9031 return fallback; 9032 if (key === "duration") { 9033 let match = rawValue.match(/([0-9]+)ms/); 9034 if (match) 9035 return match[1]; 9036 } 9037 if (key === "min") { 9038 let match = rawValue.match(/([0-9]+)px/); 9039 if (match) 9040 return match[1]; 9041 } 9042 return rawValue; 9043 } 9044 9045 // packages/collapse/builds/cdn.js 9046 document.addEventListener("alpine:init", () => { 9047 window.Alpine.plugin(src_default); 9048 }); 9049 })(); 9050</script>
vendor: 1 bytes, line 9050
9050
9051<script> 9052 function r(n,t){for(var i=0;i<t.length;i++){var r=t[i];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(n,r.key,r)}}function Jt(n,t,i){t&&r(n.prototype,t),i&&r(n,i),Object.defineProperty(n,"prototype",{writable:!1})} 9053
vendor: 29,403 bytes, line 9053
9053var n,t;n=this,t=function(){"use strict";var v="(prefers-reduced-motion: reduce)",G=4,rn=5,r={CREATED:1,MOUNTED:2,IDLE:3,MOVING:G,SCROLLING:rn,DRAGGING:6,DESTROYED:7};function D(n){n.length=0}function o(n,t,i){return Array.prototype.slice.call(n,t,i)}function R(n){return n.bind.apply(n,[null].concat(o(arguments,1)))}function on(){}var p=setTimeout;function h(n){return requestAnimationFrame(n)}function u(n,t){return typeof t===n}function un(n){return!c(n)&&u("object",n)}var e=Array.isArray,x=R(u,"function"),C=R(u,"string"),en=R(u,"undefined");function c(n){return null===n}function m(n){try{return n instanceof(n.ownerDocument.defaultView||window).HTMLElement}catch(n){return!1}}function y(n){return e(n)?n:[n]}function g(n,t){y(n).forEach(t)}function b(n,t){return-1<n.indexOf(t)}function k(n,t){return n.push.apply(n,y(t)),n}function A(t,n,i){t&&g(n,function(n){n&&t.classList[i?"add":"remove"](n)})}function M(n,t){A(n,C(t)?t.split(" "):t,!0)}function L(n,t){g(t,n.appendChild.bind(n))}function O(n,i){g(n,function(n){var t=(i||n).parentNode;t&&t.insertBefore(n,i)})}function cn(n,t){return m(n)&&(n.msMatchesSelector||n.matches).call(n,t)}function S(n,t){n=n?o(n.children):[];return t?n.filter(function(n){return cn(n,t)}):n}function fn(n,t){return t?S(n,t)[0]:n.firstElementChild}var E=Object.keys;function w(t,i,n){t&&(n?E(t).reverse():E(t)).forEach(function(n){"__proto__"!==n&&i(t[n],n)})}function an(r){return o(arguments,1).forEach(function(i){w(i,function(n,t){r[t]=i[t]})}),r}function d(i){return o(arguments,1).forEach(function(n){w(n,function(n,t){e(n)?i[t]=n.slice():un(n)?i[t]=d({},un(i[t])?i[t]:{},n):i[t]=n})}),i}function sn(t,n){g(n||E(t),function(n){delete t[n]})}function P(n,i){g(n,function(t){g(i,function(n){t&&t.removeAttribute(n)})})}function I(i,t,r){un(t)?w(t,function(n,t){I(i,t,n)}):g(i,function(n){c(r)||""===r?P(n,t):n.setAttribute(t,String(r))})}function j(n,t,i){n=document.createElement(n);return t&&(C(t)?M:I)(n,t),i&&L(i,n),n}function _(n,t,i){if(en(i))return getComputedStyle(n)[t];c(i)||(n.style[t]=""+i)}function ln(n,t){_(n,"display",t)}function dn(n){n.setActive&&n.setActive()||n.focus({preventScroll:!0})}function z(n,t){return n.getAttribute(t)}function vn(n,t){return n&&n.classList.contains(t)}function N(n){return n.getBoundingClientRect()}function T(n){g(n,function(n){n&&n.parentNode&&n.parentNode.removeChild(n)})}function hn(n){return fn((new DOMParser).parseFromString(n,"text/html").body)}function F(n,t){n.preventDefault(),t&&(n.stopPropagation(),n.stopImmediatePropagation())}function pn(n,t){return n&&n.querySelector(t)}function gn(n,t){return t?o(n.querySelectorAll(t)):[]}function X(n,t){A(n,t,!1)}function mn(n){return n.timeStamp}function W(n){return C(n)?n:n?n+"px":""}var yn="splide",f="data-"+yn;function bn(n,t){if(!n)throw new Error("["+yn+"] "+(t||""))}var Y=Math.min,wn=Math.max,xn=Math.floor,kn=Math.ceil,U=Math.abs;function Sn(n,t,i){return U(n-t)<i}function En(n,t,i,r){var o=Y(t,i),t=wn(t,i);return r?o<n&&n<t:o<=n&&n<=t}function q(n,t,i){var r=Y(t,i),t=wn(t,i);return Y(wn(r,n),t)}function Ln(n){return(0<n)-(n<0)}function On(t,n){return g(n,function(n){t=t.replace("%s",""+n)}),t}function An(n){return n<10?"0"+n:""+n}var _n={};function zn(){var c=[];function i(n,i,r){g(n,function(t){t&&g(i,function(n){n.split(" ").forEach(function(n){n=n.split(".");r(t,n[0],n[1])})})})}return{bind:function(n,t,u,e){i(n,t,function(n,t,i){var r="addEventListener"in n,o=r?n.removeEventListener.bind(n,t,u,e):n.removeListener.bind(n,u);r?n.addEventListener(t,u,e):n.addListener(u),c.push([n,t,i,u,o])})},unbind:function(n,t,o){i(n,t,function(t,i,r){c=c.filter(function(n){return!!(n[0]!==t||n[1]!==i||n[2]!==r||o&&n[3]!==o)||(n[4](),!1)})})},dispatch:function(n,t,i){var r;return"function"==typeof CustomEvent?r=new CustomEvent(t,{bubbles:!0,detail:i}):(r=document.createEvent("CustomEvent")).initCustomEvent(t,!0,!1,i),n.dispatchEvent(r),r},destroy:function(){c.forEach(function(n){n[4]()}),D(c)}}}var B="mounted",H="move",Dn="moved",Mn="click",Pn="active",In="inactive",Rn="visible",Cn="hidden",J="refresh",K="updated",jn="resize",Nn="resized",Tn="scroll",V="scrolled",a="destroy",Gn="navigation:mounted",Fn="autoplay:play",Xn="autoplay:pause",Wn="lazyload:loaded",Yn="sk",Un="sh";function Q(n){var i=n?n.event.bus:document.createDocumentFragment(),r=zn();return n&&n.event.on(a,r.destroy),an(r,{bus:i,on:function(n,t){r.bind(i,y(n).join(" "),function(n){t.apply(t,e(n.detail)?n.detail:[])})},off:R(r.unbind,i),emit:function(n){r.dispatch(i,n,o(arguments,1))}})}function qn(t,n,i,r){var o,u,e=Date.now,c=0,f=!0,a=0;function s(){if(!f){if(c=t?Y((e()-o)/t,1):1,i&&i(c),1<=c&&(n(),o=e(),r&&++a>=r))return l();u=h(s)}}function l(){f=!0}function d(){u&&cancelAnimationFrame(u),f=!(u=c=0)}return{start:function(n){n||d(),o=e()-(n?c*t:0),f=!1,u=h(s)},rewind:function(){o=e(),c=0,i&&i(c)},pause:l,cancel:d,set:function(n){t=n},isPaused:function(){return f}}}function s(n){var t=n;return{set:function(n){t=n},is:function(n){return b(y(n),t)}}}var n="Arrow",Bn=n+"Left",Hn=n+"Right",t=n+"Up",n=n+"Down",Jn="ttb",l={width:["height"],left:["top","right"],right:["bottom","left"],x:["y"],X:["Y"],Y:["X"],ArrowLeft:[t,Hn],ArrowRight:[n,Bn]};var Z="role",$="tabindex",i="aria-",Kn=i+"controls",Vn=i+"current",Qn=i+"selected",nn=i+"label",Zn=i+"labelledby",$n=i+"hidden",nt=i+"orientation",tt=i+"roledescription",it=i+"live",rt=i+"busy",ot=i+"atomic",ut=[Z,$,"disabled",Kn,Vn,nn,Zn,$n,nt,tt],i=yn+"__",et=yn,ct=i+"track",ft=i+"list",at=i+"slide",st=at+"--clone",lt=at+"__container",dt=i+"arrows",vt=i+"arrow",ht=vt+"--prev",pt=vt+"--next",gt=i+"pagination",mt=gt+"__page",yt=i+"progress"+"__bar",bt=i+"toggle",wt=i+"sr",tn="is-active",xt="is-prev",kt="is-next",St="is-visible",Et="is-loading",Lt="is-focus-in",Ot="is-overflow",At=[tn,St,xt,kt,Et,Lt,Ot];var _t="touchstart mousedown",zt="touchmove mousemove",Dt="touchend touchcancel mouseup click";var Mt="slide",Pt="loop",It="fade";function Rt(o,r,t,u){var e,n=Q(o),i=n.on,c=n.emit,f=n.bind,a=o.Components,s=o.root,l=o.options,d=l.isNavigation,v=l.updateOnMove,h=l.i18n,p=l.pagination,g=l.slideFocus,m=a.Direction.resolve,y=z(u,"style"),b=z(u,nn),w=-1<t,x=fn(u,"."+lt);function k(){var n=o.splides.map(function(n){n=n.splide.Components.Slides.getAt(r);return n?n.slide.id:""}).join(" ");I(u,nn,On(h.slideX,(w?t:r)+1)),I(u,Kn,n),I(u,Z,g?"button":""),g&&P(u,tt)}function S(){e||E()}function E(){var n,t,i;e||(n=o.index,(i=L())!==vn(u,tn)&&(A(u,tn,i),I(u,Vn,d&&i||""),c(i?Pn:In,O)),i=function(){if(o.is(It))return L();var n=N(a.Elements.track),t=N(u),i=m("left",!0),r=m("right",!0);return xn(n[i])<=kn(t[i])&&xn(t[r])<=kn(n[r])}(),t=!i&&(!L()||w),o.state.is([G,rn])||I(u,$n,t||""),I(gn(u,l.focusableNodes||""),$,t?-1:""),g&&I(u,$,t?-1:0),i!==vn(u,St)&&(A(u,St,i),c(i?Rn:Cn,O)),i||document.activeElement!==u||(t=a.Slides.getAt(o.index))&&dn(t.slide),A(u,xt,r===n-1),A(u,kt,r===n+1))}function L(){var n=o.index;return n===r||l.cloneStatus&&n===t}var O={index:r,slideIndex:t,slide:u,container:x,isClone:w,mount:function(){w||(u.id=s.id+"-slide"+An(r+1),I(u,Z,p?"tabpanel":"group"),I(u,tt,h.slide),I(u,nn,b||On(h.slideLabel,[r+1,o.length]))),f(u,"click",R(c,Mn,O)),f(u,"keydown",R(c,Yn,O)),i([Dn,Un,V],E),i(Gn,k),v&&i(H,S)},destroy:function(){e=!0,n.destroy(),X(u,At),P(u,ut),I(u,"style",y),I(u,nn,b||"")},update:E,style:function(n,t,i){_(i&&x||u,n,t)},isWithin:function(n,t){return n=U(n-r),(n=w||!l.rewind&&!o.is(Pt)?n:Y(n,o.length-n))<=t}};return O}var Ct=f+"-interval";var jt={passive:!1,capture:!0};var Nt={Spacebar:" ",Right:Hn,Left:Bn,Up:t,Down:n};function Tt(n){return n=C(n)?n:n.key,Nt[n]||n}var Gt="keydown";var Ft=f+"-lazy",Xt=Ft+"-srcset",Wt="["+Ft+"], ["+Xt+"]";var Yt=[" ","Enter"];var Ut=Object.freeze({__proto__:null,Media:function(r,n,o){var u=r.state,t=o.breakpoints||{},e=o.reducedMotion||{},i=zn(),c=[];function f(n){n&&i.destroy()}function a(n,t){t=matchMedia(t);i.bind(t,"change",s),c.push([n,t])}function s(){var n=u.is(7),t=o.direction,i=c.reduce(function(n,t){return d(n,t[1].matches?t[0]:{})},{});sn(o),l(i),o.destroy?r.destroy("completely"===o.destroy):n?(f(!0),r.mount()):t!==o.direction&&r.refresh()}function l(n,t,i){d(o,n),t&&d(Object.getPrototypeOf(o),n),!i&&u.is(1)||r.emit(K,o)}return{setup:function(){var i="min"===o.mediaQuery;E(t).sort(function(n,t){return i?+n-+t:+t-+n}).forEach(function(n){a(t[n],"("+(i?"min":"max")+"-width:"+n+"px)")}),a(e,v),s()},destroy:f,reduce:function(n){matchMedia(v).matches&&(n?d(o,e):sn(o,E(e)))},set:l}},Direction:function(n,t,o){return{resolve:function(n,t,i){var r="rtl"!==(i=i||o.direction)||t?i===Jn?0:-1:1;return l[n]&&l[n][r]||n.replace(/width|left|right/i,function(n,t){n=l[n.toLowerCase()][r]||n;return 0<t?n.charAt(0).toUpperCase()+n.slice(1):n})},orient:function(n){return n*("rtl"===o.direction?1:-1)}}},Elements:function(n,t,i){var r,o,u,e=Q(n),c=e.on,f=e.bind,a=n.root,s=i.i18n,l={},d=[],v=[],h=[];function p(){r=y("."+ct),o=fn(r,"."+ft),bn(r&&o,"A track/list element is missing."),k(d,S(o,"."+at+":not(."+st+")")),w({arrows:dt,pagination:gt,prev:ht,next:pt,bar:yt,toggle:bt},function(n,t){l[t]=y("."+n)}),an(l,{root:a,track:r,list:o,slides:d});var n=a.id||function(n){return""+n+An(_n[n]=(_n[n]||0)+1)}(yn),t=i.role;a.id=n,r.id=r.id||n+"-track",o.id=o.id||n+"-list",!z(a,Z)&&"SECTION"!==a.tagName&&t&&I(a,Z,t),I(a,tt,s.carousel),I(o,Z,"presentation"),m()}function g(n){var t=ut.concat("style");D(d),X(a,v),X(r,h),P([r,o],t),P(a,n?t:["style",tt])}function m(){X(a,v),X(r,h),v=b(et),h=b(ct),M(a,v),M(r,h),I(a,nn,i.label),I(a,Zn,i.labelledby)}function y(n){n=pn(a,n);return n&&function(n,t){if(x(n.closest))return n.closest(t);for(var i=n;i&&1===i.nodeType&&!cn(i,t);)i=i.parentElement;return i}(n,"."+et)===a?n:void 0}function b(n){return[n+"--"+i.type,n+"--"+i.direction,i.drag&&n+"--draggable",i.isNavigation&&n+"--nav",n===et&&tn]}return an(l,{setup:p,mount:function(){c(J,g),c(J,p),c(K,m),f(document,_t+" keydown",function(n){u="keydown"===n.type},{capture:!0}),f(a,"focusin",function(){A(a,Lt,!!u)})},destroy:g})},Slides:function(r,o,u){var n=Q(r),t=n.on,e=n.emit,c=n.bind,f=(n=o.Elements).slides,a=n.list,s=[];function i(){f.forEach(function(n,t){d(n,t,-1)})}function l(){h(function(n){n.destroy()}),D(s)}function d(n,t,i){t=Rt(r,t,i,n);t.mount(),s.push(t),s.sort(function(n,t){return n.index-t.index})}function v(n){return n?p(function(n){return!n.isClone}):s}function h(n,t){v(t).forEach(n)}function p(t){return s.filter(x(t)?t:function(n){return C(t)?cn(n.slide,t):b(y(t),n.index)})}return{mount:function(){i(),t(J,l),t(J,i)},destroy:l,update:function(){h(function(n){n.update()})},register:d,get:v,getIn:function(n){var t=o.Controller,i=t.toIndex(n),r=t.hasFocus()?1:u.perPage;return p(function(n){return En(n.index,i,i+r-1)})},getAt:function(n){return p(n)[0]},add:function(n,o){g(n,function(n){var t,i,r;m(n=C(n)?hn(n):n)&&((t=f[o])?O(n,t):L(a,n),M(n,u.classes.slide),t=n,i=R(e,jn),t=gn(t,"img"),(r=t.length)?t.forEach(function(n){c(n,"load error",function(){--r||i()})}):i())}),e(J)},remove:function(n){T(p(n).map(function(n){return n.slide})),e(J)},forEach:h,filter:p,style:function(t,i,r){h(function(n){n.style(t,i,r)})},getLength:function(n){return(n?f:s).length},isEnough:function(){return s.length>u.perPage}}},Layout:function(t,n,i){var r,o,u,e=(a=Q(t)).on,c=a.bind,f=a.emit,a=n.Slides,s=n.Direction.resolve,l=(n=n.Elements).root,d=n.track,v=n.list,h=a.getAt,p=a.style;function g(){r=i.direction===Jn,_(l,"maxWidth",W(i.width)),_(d,s("paddingLeft"),y(!1)),_(d,s("paddingRight"),y(!0)),m(!0)}function m(n){var t=N(l);!n&&o.width===t.width&&o.height===t.height||(_(d,"height",function(){var n="";r&&(bn(n=b(),"height or heightRatio is missing."),n="calc("+n+" - "+y(!1)+" - "+y(!0)+")");return n}()),p(s("marginRight"),W(i.gap)),p("width",i.autoWidth?null:W(i.fixedWidth)||(r?"":w())),p("height",W(i.fixedHeight)||(r?i.autoHeight?null:w():b()),!0),o=t,f(Nn),u!==(u=O())&&(A(l,Ot,u),f("overflow",u)))}function y(n){var t=i.padding,n=s(n?"right":"left");return t&&W(t[n]||(un(t)?0:t))||"0px"}function b(){return W(i.height||N(v).width*i.heightRatio)}function w(){var n=W(i.gap);return"calc((100%"+(n&&" + "+n)+")/"+(i.perPage||1)+(n&&" - "+n)+")"}function x(){return N(v)[s("width")]}function k(n,t){n=h(n||0);return n?N(n.slide)[s("width")]+(t?0:L()):0}function S(n,t){var i,n=h(n);return n?(n=N(n.slide)[s("right")],i=N(v)[s("left")],U(n-i)+(t?0:L())):0}function E(n){return S(t.length-1)-S(0)+k(0,n)}function L(){var n=h(0);return n&&parseFloat(_(n.slide,s("marginRight")))||0}function O(){return t.is(It)||E(!0)>x()}return{mount:function(){var n,t,i;g(),c(window,"resize load",(n=R(f,jn),i=qn(t||0,n,null,1),function(){i.isPaused()&&i.start()})),e([K,J],g),e(jn,m)},resize:m,listSize:x,slideSize:k,sliderSize:E,totalSize:S,getPadding:function(n){return parseFloat(_(d,s("padding"+(n?"Right":"Left"))))||0},isOverflow:O}},Clones:function(c,i,f){var t,r=Q(c),n=r.on,a=i.Elements,s=i.Slides,o=i.Direction.resolve,l=[];function u(){if(n(J,d),n([K,jn],v),t=h()){var o=t,u=s.get().slice(),e=u.length;if(e){for(;u.length<o;)k(u,u);k(u.slice(-o),u.slice(0,o)).forEach(function(n,t){var i=t<o,r=function(n,t){n=n.cloneNode(!0);return M(n,f.classes.clone),n.id=c.root.id+"-clone"+An(t+1),n}(n.slide,t);i?O(r,u[0].slide):L(a.list,r),k(l,r),s.register(r,t-o+(i?0:e),n.index)})}i.Layout.resize(!0)}}function d(){e(),u()}function e(){T(l),D(l),r.destroy()}function v(){var n=h();t!==n&&(t<n||!n)&&r.emit(J)}function h(){var n,t=f.clones;return c.is(Pt)?en(t)&&(t=(n=f[o("fixedWidth")]&&i.Layout.slideSize(0))&&kn(N(a.track)[o("width")]/n)||f[o("autoWidth")]&&c.length||2*f.perPage):t=0,t}return{mount:u,destroy:e}},Move:function(r,c,o){var e,n=Q(r),t=n.on,f=n.emit,a=r.state.set,u=(n=c.Layout).slideSize,i=n.getPadding,s=n.totalSize,l=n.listSize,d=n.sliderSize,v=(n=c.Direction).resolve,h=n.orient,p=(n=c.Elements).list,g=n.track;function m(){c.Controller.isBusy()||(c.Scroll.cancel(),y(r.index),c.Slides.update())}function y(n){b(S(n,!0))}function b(n,t){r.is(It)||(t=t?n:function(n){{var t,i;r.is(Pt)&&(t=k(n),i=t>c.Controller.getEnd(),(t<0||i)&&(n=w(n,i)))}return n}(n),_(p,"transform","translate"+v("X")+"("+t+"px)"),n!==t&&f(Un))}function w(n,t){var i=n-L(t),r=d();return n-=h(r*(kn(U(i)/r)||1))*(t?1:-1)}function x(){b(E(),!0),e.cancel()}function k(n){for(var t=c.Slides.get(),i=0,r=1/0,o=0;o<t.length;o++){var u=t[o].index,e=U(S(u,!0)-n);if(!(e<=r))break;r=e,i=u}return i}function S(n,t){var i=h(s(n-1)-(n=n,"center"===(i=o.focus)?(l()-u(n,!0))/2:+i*u(n)||0));return t?(n=i,n=o.trimSpace&&r.is(Mt)?q(n,0,h(d(!0)-l())):n):i}function E(){var n=v("left");return N(p)[n]-N(g)[n]+h(i(!1))}function L(n){return S(n?c.Controller.getEnd():0,!!o.trimSpace)}return{mount:function(){e=c.Transition,t([B,Nn,K,J],m)},move:function(n,t,i,r){var o,u;n!==t&&(o=i<n,u=h(w(E(),o)),o?0<=u:u<=p[v("scrollWidth")]-N(g)[v("width")])&&(x(),b(w(E(),i<n),!0)),a(G),f(H,t,i,n),e.start(t,function(){a(3),f(Dn,t,i,n),r&&r()})},jump:y,translate:b,shift:w,cancel:x,toIndex:k,toPosition:S,getPosition:E,getLimit:L,exceededLimit:function(n,t){t=en(t)?E():t;var i=!0!==n&&h(t)<h(L(!1)),n=!1!==n&&h(t)>h(L(!0));return i||n},reposition:m}},Controller:function(o,u,e){var c,f,a,s,n=Q(o),t=n.on,i=n.emit,l=u.Move,d=l.getPosition,r=l.getLimit,v=l.toPosition,h=(n=u.Slides).isEnough,p=n.getLength,g=e.omitEnd,m=o.is(Pt),y=o.is(Mt),b=R(L,!1),w=R(L,!0),x=e.start||0,k=x;function S(){f=p(!0),a=e.perMove,s=e.perPage,c=_();var n=q(x,0,g?c:f-1);n!==x&&(x=n,l.reposition())}function E(){c!==_()&&i("ei")}function L(n,t){var i=a||(P()?1:s),i=O(x+i*(n?-1:1),x,!(a||P()));return-1===i&&y&&!Sn(d(),r(!n),1)?n?0:c:t?i:A(i)}function O(n,t,i){var r;return h()||P()?((r=function(n){if(y&&"move"===e.trimSpace&&n!==x)for(var t=d();t===v(n,!0)&&En(n,0,o.length-1,!e.rewind);)n<x?--n:++n;return n}(n))!==n&&(t=n,n=r,i=!1),n<0||c<n?n=a||!En(0,n,t,!0)&&!En(c,t,n,!0)?m?i?n<0?-(f%s||s):f:n:e.rewind?n<0?c:0:-1:z(D(n)):i&&n!==t&&(n=z(D(t)+(n<t?-1:1)))):n=-1,n}function A(n){return m?(n+f)%f||0:n}function _(){for(var n=f-(P()||m&&a?1:s);g&&0<n--;)if(v(f-1,!0)!==v(n,!0)){n++;break}return q(n,0,f-1)}function z(n){return q(P()?n:s*n,0,c)}function D(n){return P()?Y(n,c):xn((c<=n?f-1:n)/s)}function M(n){n!==x&&(k=x,x=n)}function P(){return!en(e.focus)||e.isNavigation}function I(){return o.state.is([G,rn])&&!!e.waitForTransition}return{mount:function(){S(),t([K,J,"ei"],S),t(Nn,E)},go:function(n,t,i){var r;I()||-1<(r=A(n=function(n){var t=x;{var i,r;C(n)?(r=n.match(/([+\-<>])(\d+)?/)||[],i=r[1],r=r[2],"+"===i||"-"===i?t=O(x+ +(""+i+(+r||1)),x):">"===i?t=r?z(+r):b(!0):"<"===i&&(t=w(!0))):t=m?n:q(n,0,c)}return t}(n)))&&(t||r!==x)&&(M(r),l.move(n,r,k,i))},scroll:function(n,t,i,r){u.Scroll.scroll(n,t,i,function(){var n=A(l.toIndex(d()));M(g?Y(n,c):n),r&&r()})},getNext:b,getPrev:w,getAdjacent:L,getEnd:_,setIndex:M,getIndex:function(n){return n?k:x},toIndex:z,toPage:D,toDest:function(n){return n=l.toIndex(n),y?q(n,0,c):n},hasFocus:P,isBusy:I}},Arrows:function(o,n,t){var i,r,u=Q(o),e=u.on,c=u.bind,f=u.emit,a=t.classes,s=t.i18n,l=n.Elements,d=n.Controller,v=l.arrows,h=l.track,p=v,g=l.prev,m=l.next,y={};function b(){var n=t.arrows;!n||g&&m||(p=v||j("div",a.arrows),g=S(!0),m=S(!1),i=!0,L(p,[g,m]),v||O(p,h)),g&&m&&(an(y,{prev:g,next:m}),ln(p,n?"":"none"),M(p,r=dt+"--"+t.direction),n&&(e([B,Dn,J,V,"ei"],E),c(m,"click",R(k,">")),c(g,"click",R(k,"<")),E(),I([g,m],Kn,h.id),f("arrows:mounted",g,m))),e(K,w)}function w(){x(),b()}function x(){u.destroy(),X(p,r),i?(T(v?[g,m]:p),g=m=null):P([g,m],ut)}function k(n){d.go(n,!0)}function S(n){return hn('<button class="'+a.arrow+" "+(n?a.prev:a.next)+'" type="button"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" width="40" height="40" focusable="false"><path d="'+(t.arrowPath||"m15.5 0.932-4.3 4.38 14.5 14.6-14.5 14.5 4.3 4.4 14.6-14.6 4.4-4.3-4.4-4.4-14.6-14.6z")+'" />')}function E(){var n,t,i,r;g&&m&&(r=o.index,n=d.getPrev(),t=d.getNext(),i=-1<n&&r<n?s.last:s.prev,r=-1<t&&t<r?s.first:s.next,g.disabled=n<0,m.disabled=t<0,I(g,nn,i),I(m,nn,r),f("arrows:updated",g,m,n,t))}return{arrows:y,mount:b,destroy:x,update:E}},Autoplay:function(n,t,i){var r,o,u=Q(n),e=u.on,c=u.bind,f=u.emit,a=qn(i.interval,n.go.bind(n,">"),function(n){var t=l.bar;t&&_(t,"width",100*n+"%"),f("autoplay:playing",n)}),s=a.isPaused,l=t.Elements,d=(u=t.Elements).root,v=u.toggle,h=i.autoplay,p="pause"===h;function g(){s()&&t.Slides.isEnough()&&(a.start(!i.resetProgress),o=r=p=!1,b(),f(Fn))}function m(n){p=!!(n=void 0===n?!0:n),b(),s()||(a.pause(),f(Xn))}function y(){p||(r||o?m(!1):g())}function b(){v&&(A(v,tn,!p),I(v,nn,i.i18n[p?"play":"pause"]))}function w(n){n=t.Slides.getAt(n);a.set(n&&+z(n.slide,Ct)||i.interval)}return{mount:function(){h&&(i.pauseOnHover&&c(d,"mouseenter mouseleave",function(n){r="mouseenter"===n.type,y()}),i.pauseOnFocus&&c(d,"focusin focusout",function(n){o="focusin"===n.type,y()}),v&&c(v,"click",function(){p?g():m(!0)}),e([H,Tn,J],a.rewind),e(H,w),v&&I(v,Kn,l.track.id),p||g(),b())},destroy:a.cancel,play:g,pause:m,isPaused:s}},Cover:function(n,t,i){var r=Q(n).on;function o(i){t.Slides.forEach(function(n){var t=fn(n.container||n.slide,"img");t&&t.src&&u(i,t,n)})}function u(n,t,i){i.style("background",n?'center/cover no-repeat url("'+t.src+'")':"",!0),ln(t,n?"none":"")}return{mount:function(){i.cover&&(r(Wn,R(u,!0)),r([B,K,J],R(o,!0)))},destroy:R(o,!1)}},Scroll:function(n,c,u){var f,a,t=Q(n),i=t.on,s=t.emit,l=n.state.set,d=c.Move,v=d.getPosition,e=d.getLimit,h=d.exceededLimit,p=d.translate,g=n.is(Mt),m=1;function y(n,t,i,r,o){var u,e=v(),i=(x(),!i||g&&h()||(i=c.Layout.sliderSize(),u=Ln(n)*i*xn(U(n)/i)||0,n=d.toPosition(c.Controller.toDest(n%i))+u),Sn(e,n,1));m=1,t=i?0:t||wn(U(n-e)/1.5,800),a=r,f=qn(t,b,R(w,e,n,o),1),l(rn),s(Tn),f.start()}function b(){l(3),a&&a(),s(V)}function w(n,t,i,r){var o=v(),r=(n+(t-n)*(t=r,(n=u.easingFunc)?n(t):1-Math.pow(1-t,4))-o)*m;p(o+r),g&&!i&&h()&&(m*=.6,U(r)<10&&y(e(h(!0)),600,!1,a,!0))}function x(){f&&f.cancel()}function r(){f&&!f.isPaused()&&(x(),b())}return{mount:function(){i(H,x),i([K,J],r)},destroy:x,scroll:y,cancel:r}},Drag:function(e,o,c){var f,t,u,a,s,l,d,v,n=Q(e),i=n.on,h=n.emit,p=n.bind,g=n.unbind,m=e.state,y=o.Move,b=o.Scroll,w=o.Controller,x=o.Elements.track,k=o.Media.reduce,r=(n=o.Direction).resolve,S=n.orient,E=y.getPosition,L=y.exceededLimit,O=!1;function j(){var n=c.drag;C(!n),a="free"===n}function N(n){var t,i,r;l=!1,d||(t=R(n),i=n.target,r=c.noDrag,cn(i,"."+mt+", ."+vt)||r&&cn(i,r)||!t&&n.button||(w.isBusy()?F(n,!0):(v=t?x:window,s=m.is([G,rn]),u=null,p(v,zt,A,jt),p(v,Dt,_,jt),y.cancel(),b.cancel(),z(n))))}function A(n){var t,i,r,o,u;m.is(6)||(m.set(6),h("drag")),n.cancelable&&(s?(y.translate(f+D(n)/(O&&e.is(Mt)?5:1)),u=200<M(n),t=O!==(O=L()),(u||t)&&z(n),l=!0,h("dragging"),F(n)):U(D(u=n))>U(D(u,!0))&&(t=n,i=c.dragMinThreshold,r=un(i),o=r&&i.mouse||0,r=(r?i.touch:+i)||10,s=U(D(t))>(R(t)?r:o),F(n)))}function _(n){var t,i,r;m.is(6)&&(m.set(3),h("dragged")),s&&(i=function(n){return E()+Ln(n)*Y(U(n)*(c.flickPower||600),a?1/0:o.Layout.listSize()*(c.flickMaxPages||1))}(t=function(n){if(e.is(Pt)||!O){var t=M(n);if(t&&t<200)return D(n)/t}return 0}(t=n)),r=c.rewind&&c.rewindByDrag,k(!1),a?w.scroll(i,0,c.snap):e.is(It)?w.go(S(Ln(t))<0?r?"<":"-":r?">":"+"):e.is(Mt)&&O&&r?w.go(L(!0)?">":"<"):w.go(w.toDest(i),!0),k(!0),F(n)),g(v,zt,A),g(v,Dt,_),s=!1}function T(n){!d&&l&&F(n,!0)}function z(n){u=t,t=n,f=E()}function D(n,t){return I(n,t)-I(P(n),t)}function M(n){return mn(n)-mn(P(n))}function P(n){return t===n&&u||t}function I(n,t){return(R(n)?n.changedTouches[0]:n)["page"+r(t?"Y":"X")]}function R(n){return"undefined"!=typeof TouchEvent&&n instanceof TouchEvent}function C(n){d=n}return{mount:function(){p(x,zt,on,jt),p(x,Dt,on,jt),p(x,_t,N,jt),p(x,"click",T,{capture:!0}),p(x,"dragstart",F),i([B,K],j)},disable:C,isDragging:function(){return s}}},Keyboard:function(t,n,i){var r,o,u=Q(t),e=u.on,c=u.bind,f=u.unbind,a=t.root,s=n.Direction.resolve;function l(){var n=i.keyboard;n&&(r="global"===n?window:a,c(r,Gt,h))}function d(){f(r,Gt)}function v(){var n=o;o=!0,p(function(){o=n})}function h(n){o||((n=Tt(n))===s(Bn)?t.go("<"):n===s(Hn)&&t.go(">"))}return{mount:function(){l(),e(K,d),e(K,l),e(H,v)},destroy:d,disable:function(n){o=n}}},LazyLoad:function(i,n,o){var t=Q(i),r=t.on,u=t.off,e=t.bind,c=t.emit,f="sequential"===o.lazyLoad,a=[Dn,V],s=[];function l(){D(s),n.Slides.forEach(function(r){gn(r.slide,Wt).forEach(function(n){var t=z(n,Ft),i=z(n,Xt);t===n.src&&i===n.srcset||(t=o.classes.spinner,t=fn(i=n.parentElement,"."+t)||j("span",t,i),s.push([n,r,t]),n.src||ln(n,"none"))})}),(f?p:(u(a),r(a,d),d))()}function d(){(s=s.filter(function(n){var t=o.perPage*((o.preloadPages||1)+1)-1;return!n[1].isWithin(i.index,t)||v(n)})).length||u(a)}function v(n){var t=n[0];M(n[1].slide,Et),e(t,"load error",R(h,n)),I(t,"src",z(t,Ft)),I(t,"srcset",z(t,Xt)),P(t,Ft),P(t,Xt)}function h(n,t){var i=n[0],r=n[1];X(r.slide,Et),"error"!==t.type&&(T(n[2]),ln(i,""),c(Wn,i,r),c(jn)),f&&p()}function p(){s.length&&v(s.shift())}return{mount:function(){o.lazyLoad&&(l(),r(J,l))},destroy:R(D,s),check:d}},Pagination:function(l,n,d){var v,h,t=Q(l),p=t.on,g=t.emit,m=t.bind,y=n.Slides,b=n.Elements,w=n.Controller,x=w.hasFocus,r=w.getIndex,e=w.go,c=n.Direction.resolve,k=b.pagination,S=[];function E(){v&&(T(k?o(v.children):v),X(v,h),D(S),v=null),t.destroy()}function L(n){e(">"+n,!0)}function O(n,t){var i=S.length,r=Tt(t),o=A(),u=-1,o=(r===c(Hn,!1,o)?u=++n%i:r===c(Bn,!1,o)?u=(--n+i)%i:"Home"===r?u=0:"End"===r&&(u=i-1),S[u]);o&&(dn(o.button),e(">"+u),F(t,!0))}function A(){return d.paginationDirection||d.direction}function _(n){return S[w.toPage(n)]}function z(){var n,t=_(r(!0)),i=_(r());t&&(X(n=t.button,tn),P(n,Qn),I(n,$,-1)),i&&(M(n=i.button,tn),I(n,Qn,!0),I(n,$,"")),g("pagination:updated",{list:v,items:S},t,i)}return{items:S,mount:function n(){E(),p([K,J,"ei"],n);var t=d.pagination;if(k&&ln(k,t?"":"none"),t){p([H,Tn,V],z);var t=l.length,i=d.classes,r=d.i18n,o=d.perPage,u=x()?w.getEnd()+1:kn(t/o);M(v=k||j("ul",i.pagination,b.track.parentElement),h=gt+"--"+A()),I(v,Z,"tablist"),I(v,nn,r.select),I(v,nt,A()===Jn?"vertical":"");for(var e=0;e<u;e++){var c=j("li",null,v),f=j("button",{class:i.page,type:"button"},c),a=y.getIn(e).map(function(n){return n.slide.id}),s=!x()&&1<o?r.pageX:r.slideX;m(f,"click",R(L,e)),d.paginationKeyboard&&m(f,"keydown",R(O,e)),I(c,Z,"presentation"),I(f,Z,"tab"),I(f,Kn,a.join(" ")),I(f,nn,On(s,e+1)),I(f,$,-1),S.push({li:c,button:f,page:e})}z(),g("pagination:mounted",{list:v,items:S},_(l.index))}},destroy:E,getAt:_,update:z}},Sync:function(i,n,t){var r=t.isNavigation,o=t.slideFocus,u=[];function e(){var n,t;i.splides.forEach(function(n){n.isParent||(f(i,n.splide),f(n.splide,i))}),r&&(n=Q(i),(t=n.on)(Mn,s),t(Yn,l),t([B,K],a),u.push(n),n.emit(Gn,i.splides))}function c(){u.forEach(function(n){n.destroy()}),D(u)}function f(n,r){n=Q(n);n.on(H,function(n,t,i){r.go(r.is(Pt)?i:n)}),u.push(n)}function a(){I(n.Elements.list,nt,t.direction===Jn?"vertical":"")}function s(n){i.go(n.index)}function l(n,t){b(Yt,Tt(t))&&(s(n),F(t))}return{setup:R(n.Media.set,{slideFocus:en(o)?r:o},!0),mount:e,destroy:c,remount:function(){c(),e()}}},Wheel:function(e,c,f){var n=Q(e).bind,a=0;function t(n){var t,i,r,o,u;n.cancelable&&(t=(u=n.deltaY)<0,i=mn(n),r=f.wheelMinThreshold||0,o=f.wheelSleep||0,U(u)>r&&o<i-a&&(e.go(t?"<":">"),a=i),u=t,f.releaseWheel&&!e.state.is(G)&&-1===c.Controller.getAdjacent(u)||F(n))}return{mount:function(){f.wheel&&n(c.Elements.track,"wheel",t,jt)}}},Live:function(n,t,i){var r=Q(n).on,o=t.Elements.track,u=i.live&&!i.isNavigation,e=j("span",wt),c=qn(90,R(f,!1));function f(n){I(o,rt,n),n?(L(o,e),c.start()):(T(e),c.cancel())}function a(n){u&&I(o,it,n?"off":"polite")}return{mount:function(){u&&(a(!t.Autoplay.isPaused()),I(o,ot,!0),e.textContent="â¦",r(Fn,R(a,!0)),r(Xn,R(a,!1)),r([Dn,V],R(f,!0)))},disable:a,destroy:function(){P(o,[it,ot,rt]),T(e)}}}}),qt={type:"slide",role:"region",speed:400,perPage:1,cloneStatus:!0,arrows:!0,pagination:!0,paginationKeyboard:!0,interval:5e3,pauseOnHover:!0,pauseOnFocus:!0,resetProgress:!0,easing:"cubic-bezier(0.25, 1, 0.5, 1)",drag:!0,direction:"ltr",trimSpace:!0,focusableNodes:"a, button, textarea, input, select, iframe",live:!0,classes:{slide:at,clone:st,arrows:dt,arrow:vt,prev:ht,next:pt,pagination:gt,page:mt,spinner:i+"spinner"},i18n:{prev:"Previous slide",next:"Next slide",first:"Go to first slide",last:"Go to last slide",slideX:"Go to slide %s",pageX:"Go to page %s",play:"Start autoplay",pause:"Pause autoplay",carousel:"carousel",slide:"slide",select:"Select a slide to show",slideLabel:"%s of %s"},reducedMotion:{speed:0,rewindSpeed:0,autoplay:"pause"}};function Bt(n,t,i){var r=t.Slides;function o(){r.forEach(function(n){n.style("transform","translateX(-"+100*n.index+"%)")})}return{mount:function(){Q(n).on([B,J],o)},start:function(n,t){r.style("transition","opacity "+i.speed+"ms "+i.easing),p(t)},cancel:on}}function Ht(u,n,e){var c,f=n.Move,a=n.Controller,s=n.Scroll,t=n.Elements.list,l=R(_,t,"transition");function i(){l(""),s.cancel()}return{mount:function(){Q(u).bind(t,"transitionend",function(n){n.target===t&&c&&(i(),c())})},start:function(n,t){var i=f.toPosition(n,!0),r=f.getPosition(),o=function(n){var t=e.rewindSpeed;if(u.is(Mt)&&t){var i=a.getIndex(!0),r=a.getEnd();if(0===i&&r<=n||r<=i&&0===n)return t}return e.speed}(n);1<=U(i-r)&&1<=o?e.useScroll?s.scroll(i,o,!1,t):(l("transform "+o+"ms "+e.easing),f.translate(i,!0),c=t):(f.jump(n),t())},cancel:i}}t=function(){function i(n,t){this.event=Q(),this.Components={},this.state=s(1),this.splides=[],this.n={},this.t={};n=C(n)?pn(document,n):n;bn(n,n+" is invalid."),t=d({label:z(this.root=n,nn)||"",labelledby:z(n,Zn)||""},qt,i.defaults,t||{});try{d(t,JSON.parse(z(n,f)))}catch(n){bn(!1,"Invalid JSON")}this.n=Object.create(d({},t))}var n=i.prototype;return n.mount=function(n,t){var i=this,r=this.state,o=this.Components;return bn(r.is([1,7]),"Already mounted!"),r.set(1),this.i=o,this.r=t||this.r||(this.is(It)?Bt:Ht),this.t=n||this.t,w(an({},Ut,this.t,{Transition:this.r}),function(n,t){n=n(i,o,i.n);(o[t]=n).setup&&n.setup()}),w(o,function(n){n.mount&&n.mount()}),this.emit(B),M(this.root,"is-initialized"),r.set(3),this.emit("ready"),this},n.sync=function(n){return this.splides.push({splide:n}),n.splides.push({splide:this,isParent:!0}),this.state.is(3)&&(this.i.Sync.remount(),n.Components.Sync.remount()),this},n.go=function(n){return this.i.Controller.go(n),this},n.on=function(n,t){return this.event.on(n,t),this},n.off=function(n){return this.event.off(n),this},n.emit=function(n){var t;return(t=this.event).emit.apply(t,[n].concat(o(arguments,1))),this},n.add=function(n,t){return this.i.Slides.add(n,t),this},n.remove=function(n){return this.i.Slides.remove(n),this},n.is=function(n){return this.n.type===n},n.refresh=function(){return this.emit(J),this},n.destroy=function(t){void 0===t&&(t=!0);var n=this.event,i=this.state;return i.is(1)?Q(this).on("ready",this.destroy.bind(this,t)):(w(this.i,function(n){n.destroy&&n.destroy(t)},!0),n.emit(a),n.destroy(),t&&D(this.splides),i.set(7)),this},Jt(i,[{key:"options",get:function(){return this.n},set:function(n){this.i.Media.set(n,!0,!0)}},{key:"length",get:function(){return this.i.Slides.getLength(!0)}},{key:"index",get:function(){return this.i.Controller.getIndex()}}]),i}();return t.defaults={},t.STATES=r,t},"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(n="undefined"!=typeof globalThis?globalThis:n||self).Splide=t();
9054</script>
vendor: 1 bytes, line 9054
9054
9055<script> 9056 function initCustomSelect() { 9057 const selects = document.querySelectorAll("select"); 9058 9059 selects.forEach((selElmnt) => { 9060 // Check if already initialized or if wrapper already exists 9061 if (selElmnt.dataset.customSelectInit) return; 9062 if (!selElmnt.options.length || getComputedStyle(selElmnt).display === 'none') return; 9063 9064 // Check if wrapper already exists to prevent double-wrapping 9065 if (selElmnt.parentNode && selElmnt.parentNode.classList.contains('custom-select-wrapper')) return; 9066 9067 selElmnt.dataset.customSelectInit = true; 9068 9069 const wrapper = document.createElement("DIV"); 9070 wrapper.setAttribute("class", "custom-select-wrapper"); 9071 selElmnt.classList.forEach(cls => { 9072 wrapper.classList.add(cls); 9073 }); 9074 selElmnt.parentNode.insertBefore(wrapper, selElmnt); 9075 9076 const selected = document.createElement("DIV"); 9077 selected.setAttribute("class", "select-selected"); 9078 wrapper.appendChild(selected); 9079 9080 const itemsBox = document.createElement("DIV"); 9081 itemsBox.setAttribute("class", "select-items select-hide"); 9082 wrapper.appendChild(itemsBox); 9083 wrapper.appendChild(selElmnt); 9084 9085 function updateDropdown() { 9086 selected.innerHTML = selElmnt.options[selElmnt.selectedIndex].innerHTML; 9087 itemsBox.innerHTML = ''; 9088 9089 for (let j = 0; j < selElmnt.length; j++) { 9090 const item = document.createElement("DIV"); 9091 item.innerHTML = selElmnt.options[j].innerHTML; 9092 9093 if (j === selElmnt.selectedIndex) { 9094 item.setAttribute("class", "same-as-selected"); 9095 } 9096 9097 item.addEventListener("click", function(e) { 9098 e.stopPropagation(); 9099 selElmnt.selectedIndex = j; 9100 selElmnt.dispatchEvent(new Event('change', { bubbles: true })); 9101 updateDropdown(); 9102 selected.classList.remove("select-arrow-active"); 9103 itemsBox.classList.add("select-hide"); 9104 }); 9105 9106 itemsBox.appendChild(item); 9107 } 9108 } 9109 9110 updateDropdown(); 9111 9112 selected.addEventListener("click", function(e) { 9113 e.stopPropagation(); 9114 closeAllSelects(selected); 9115 itemsBox.classList.toggle("select-hide"); 9116 selected.classList.toggle("select-arrow-active"); 9117 }); 9118 9119 // Sync change 9120 selElmnt.addEventListener('change', () => { 9121 updateDropdown(); 9122 selected.classList.remove('error'); 9123 const errorMsg = wrapper.querySelector('.mage-error'); 9124 if (errorMsg) errorMsg.remove(); 9125 }); 9126 9127 // Native invalid event (triggers Magento's validation message) 9128 selElmnt.addEventListener('invalid', () => { 9129 selected.classList.add('error'); 9130 9131 // Only show message if it doesnât exist already 9132 if (!wrapper.querySelector('.mage-error')) { 9133 const msg = document.createElement('div'); 9134 msg.classList.add('mage-error'); 9135 msg.textContent = 'This is a required field.'; // same as Magento core 9136 wrapper.appendChild(msg); 9137 } 9138 }); 9139 9140 const observer = new MutationObserver(updateDropdown); 9141 observer.observe(selElmnt, { childList: true }); 9142 }); 9143 9144 function closeAllSelects(except) { 9145 document.querySelectorAll(".select-selected").forEach(sel => { 9146 if (sel !== except) { 9147 sel.classList.remove("select-arrow-active"); 9148 sel.nextSibling.classList.add("select-hide"); 9149 } 9150 }); 9151 } 9152 9153 // Global click handler that only closes dropdowns when clicking outside 9154 if (!document._customSelectClickHandlerAdded) { 9155 document.addEventListener("click", function(e) { 9156 // Only close dropdowns if clicking outside of any custom-select-wrapper 9157 if (!e.target.closest('.custom-select-wrapper')) { 9158 closeAllSelects(); 9159 } 9160 }); 9161 document._customSelectClickHandlerAdded = true; 9162 } 9163 } 9164 9165 // Debounce function to prevent multiple rapid calls 9166 let initTimeout; 9167 function debounceInit() { 9168 clearTimeout(initTimeout); 9169 initTimeout = setTimeout(initCustomSelect, 100); 9170 } 9171 9172 // Initial load on DOMContentLoaded 9173 if (document.readyState === 'loading') { 9174 document.addEventListener('DOMContentLoaded', initCustomSelect); 9175 } else { 9176 initCustomSelect(); 9177 } 9178 9179 // Watch for dynamic content changes 9180 if (typeof MutationObserver !== 'undefined') { 9181 const bodyObserver = new MutationObserver(debounceInit); 9182 if (document.readyState === 'loading') { 9183 document.addEventListener('DOMContentLoaded', () => { 9184 bodyObserver.observe(document.body, { 9185 childList: true, 9186 subtree: true 9187 }); 9188 }); 9189 } else { 9190 bodyObserver.observe(document.body, { 9191 childList: true, 9192 subtree: true 9193 }); 9194 } 9195 } 9196 9197 // Hyvä Checkout specific events 9198 window.addEventListener('checkout:step:loaded', debounceInit); 9199 window.addEventListener('alpine:initialized', debounceInit); 9200 9201 // Listen for private content loaded (customer data) 9202 window.addEventListener('private-content-loaded', debounceInit); 9203 9204 // Re-initialize after AJAX updates 9205 document.addEventListener('ajaxComplete', debounceInit); 9206</script>
vendor: 1 bytes, line 9206
9206
9207<script> 9208 function createAjaxLoader() { 9209 const loader = document.createElement('div'); 9210 loader.className = 'absolute inset-0 flex justify-center items-center loader'; 9211 loader.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" class="w-full h-full p-2" role="img"> 9212 <circle style="opacity: .3;" cx="12" cy="12" r="10"/> 9213 <path fill="none" stroke-width="2" d="M12,2 a10,10 0 0,1 10,10"/> 9214<title>Loading...</title></svg> 9215`; 9216 return loader; 9217 } 9218 9219 function addAjaxLoader(button, loader) { 9220 button.setAttribute('data-label', button.getAttribute('aria-label')); 9221 button.removeAttribute('aria-label'); 9222 button.prepend(loader); 9223 button.classList.add('relative', '[&>:not(.loader)]:invisible'); 9224 button.disabled = true; 9225 } 9226 9227 function removeAjaxLoader(button, loader) { 9228 button.setAttribute('aria-label', button.getAttribute('data-label')); 9229 button.removeAttribute('data-label'); 9230 loader.remove(); 9231 button.classList.remove('[&>:not(.loader)]:invisible'); 9232 button.disabled = false; 9233 } 9234 9235 async function addAjaxCartForm(form, extraDelay = 500) { 9236 if (!form) return; 9237 event.preventDefault(); 9238 9239 if (form.hasAttribute('enctype') || !form.reportValidity()) { 9240 return form.submit(); 9241 } 9242 9243 const formData = new FormData(form); 9244 const button = form.querySelector('button:not([type=button], [type=reset])') 9245 ? form.querySelector('button:not([type=button], [type=reset])') 9246 : document.getElementById('product-addtocart-button'); 9247 const loader = createAjaxLoader(button); 9248 const formUenc = hyva.getUenc(); 9249 const postUrl = event.target.action.replace('%25uenc%25', formUenc); 9250 9251 let bodyUrl = new URLSearchParams(formData); 9252 bodyUrl.append("uenc", formUenc); 9253 addAjaxLoader(button, loader); 9254 9255 try { 9256 const response = await fetch(postUrl, { 9257 method: 'POST', 9258 body: bodyUrl.toString(), 9259 mode: "cors", 9260 credentials: "include", 9261 headers: { 9262 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 9263 'X-Requested-With': 'XMLHttpRequest' 9264 } 9265 }); 9266 9267 if (!response.ok) { 9268 return form.submit(); 9269 } 9270 9271 if (response.redirected) { 9272 return window.location.href = response.url; 9273 } 9274 9275 const responseData = await response.json(); 9276 if (responseData.backUrl) { 9277 return window.location.href = responseData.backUrl; 9278 } 9279 9280 window.dispatchEvent(new CustomEvent("reload-customer-section-data")); 9281 9282 } catch (err) { 9283 console.warn(err); 9284 window.dispatchEvent(new CustomEvent('product-addtocart-error')); 9285 9286 if (typeof window.dispatchMessages === "undefined") return; 9287 window.dispatchMessages([{ 9288 text:'There\u0020was\u0020a\u0020problem\u0020adding\u0020your\u0020item\u0020to\u0020the\u0020cart.', 9289 type: 'error' 9290 }], 5000); 9291 } finally { 9292 setTimeout(() => removeAjaxLoader(button, loader), extraDelay); 9293 } 9294 } 9295 9296 function removeAjaxCartForm(form) { 9297 removeEventListener('submit', form); 9298 } 9299 9300 document.addEventListener('submit', function(event) { 9301 if (!event.target.matches('#product_addtocart_form, .product_addtocart_form')) return; 9302 addAjaxCartForm(event.target, 500); 9303 }); 9304</script>
vendor: 1 bytes, line 9304
9304
9305<script> 9306 9307(_ => { 9308 document.addEventListener('DOMContentLoaded', event => { 9309 9310 if (!Alpine.version.startsWith('2')) { 9311 return; 9312 } 9313 9314 const teleportDirective = (el, expression) => { 9315 let target = document.querySelector(expression) 9316 let clone = el.content.cloneNode(true).firstElementChild; 9317 9318 target.appendChild(clone); 9319 setTimeout(() => { 9320 target.querySelectorAll('[x-data]') 9321 .forEach(el => Alpine.initializeComponent(el)); 9322 }, 0) 9323 }; 9324 9325 document.querySelectorAll('template').forEach(el => { 9326 if (!el.hasAttribute('x-teleport')) { 9327 return; 9328 } 9329 9330 teleportDirective(el, el.getAttribute('x-teleport')); 9331 }); 9332 9333 document.querySelectorAll('[x-data]').forEach(el => { 9334 if (el.hasAttribute('x-init')) { 9335 return; 9336 } 9337 9338 setTimeout(() => { 9339 Alpine.initializeComponent(el); 9340 }, 0); 9341 }); 9342 }); 9343})()</script>
vendor: 1 bytes, line 9343
9343
9344<script type="module" 9345 src="https://www.banham.co.uk/static/version1790667712/frontend/BraveBison/default/en_GB/Hyva_Theme/js/alpine3.min.js" 9346 defer 9347 crossorigin 9348></script>
vendor: 1 bytes, line 9348
9348
9349<script> 9350 function dispatchMessages(messages, hideAfter) { 9351 const messagesEvent = new CustomEvent("messages-loaded", { 9352 detail: { 9353 messages: messages, 9354 hideAfter: hideAfter 9355 } 9356 }); 9357 window.dispatchEvent(messagesEvent); 9358 } 9359 9360 if (typeof hyva === 'undefined' || (!hyva.getBrowserStorage || !hyva.getCookie || !hyva.setCookie)) { 9361 console.warn("Hyvä helpers are not loaded yet. Make sure they are included before this script"); 9362 } 9363 9364 (function( hyva, undefined ) { 9365 9366 hyva.initFormKey = () => { 9367 const inputSelector = 'input[name="form_key"]', 9368 formKey = hyva.getFormKey(); 9369 9370 Array.from(document.querySelectorAll(inputSelector)).map(function (input) { 9371 input.value = formKey 9372 }); 9373 } 9374 9375 hyva.initMessages = () => { 9376 try { 9377 const messages = hyva.getCookie('mage-messages'); 9378 window.mageMessages = messages ? JSON.parse(decodeURIComponent(messages).replace(/\+/g, ' ')) : []; 9379 9380 dispatchMessages(window.mageMessages); 9381 9382 const skipSetDomain = true; 9383 hyva.setCookie('mage-messages','', -1, skipSetDomain); 9384 9385 } catch (error) { 9386 console.warn('Error parsing Cookie Messages:', error); 9387 } 9388 } 9389 9390 window.addEventListener('DOMContentLoaded', hyva.initFormKey); 9391 hyva.alpineInitialized(hyva.initMessages) 9392 9393 }( window.hyva = window.hyva || {} ));</script>
vendor: 1 bytes, line 9393
9393
9394<script> 9395 { 9396 const private_content_key = 'mage-cache-storage'; 9397 const private_content_expire_key = 'mage-cache-timeout'; 9398 const private_content_version_key = 'private_content_version'; 9399 const section_data_ids_key = 'section_data_ids'; 9400 const mage_cache_session_id_key = 'mage-cache-sessid'; 9401 const last_visited_store_key = 'last_visited_store'; 9402 9403 const ttl = 24800; 9404 9405 if (typeof hyva === 'undefined' || (!hyva.getBrowserStorage || !hyva.getCookie || !hyva.setCookie)) { 9406 console.warn("Hyvä helpers are not loaded yet. Make sure they are included before this script"); 9407 } 9408 9409 function loadSectionData () { 9410 const browserStorage = hyva.getBrowserStorage(); 9411 if (!browserStorage) { 9412 typeof window.dispatchMessages !== "undefined" && window.dispatchMessages( 9413 [{ 9414 type: "warning", 9415 text: "Please enable LocalStorage in your browser." 9416 }] 9417 ); 9418 return; 9419 } 9420 try { 9421 let isInvalid = false; 9422 9423 if (hyva.getCookie(last_visited_store_key) !== CURRENT_STORE_CODE) { 9424 isInvalid = true; 9425 } 9426 hyva.setCookie(last_visited_store_key, CURRENT_STORE_CODE, false, false); 9427 9428 if (!hyva.getCookie(mage_cache_session_id_key)) { 9429 isInvalid = true; 9430 browserStorage.removeItem(private_content_key); 9431 const skipSetDomain = true; 9432 const days = false; 9433 hyva.setCookie(mage_cache_session_id_key, true, days, skipSetDomain) 9434 } 9435 9436 const cookieVersion = hyva.getCookie(private_content_version_key); 9437 const storageVersion = browserStorage.getItem(private_content_version_key); 9438 9439 if (cookieVersion && !storageVersion || cookieVersion !== storageVersion) { 9440 isInvalid = true; 9441 } 9442 9443 const privateContentExpires = browserStorage.getItem(private_content_expire_key); 9444 if (privateContentExpires && new Date(privateContentExpires) < new Date()) { 9445 browserStorage.removeItem(private_content_key); 9446 } 9447 9448 if (isInvalid && cookieVersion) { 9449 fetchPrivateContent([]); 9450 } else if (cookieVersion && storageVersion && cookieVersion === storageVersion) { 9451 const privateContent = JSON.parse(browserStorage.getItem(private_content_key)); 9452 if ( 9453 privateContent && 9454 privateContentExpires && 9455 privateContent.cart && 9456 privateContent.customer 9457 ) { 9458 dispatchPrivateContent(privateContent); 9459 } else { 9460 fetchPrivateContent([]); 9461 } 9462 } else { 9463 if (document.getElementById('default-section-data')) { 9464 const privateContent = JSON.parse(document.getElementById('default-section-data').innerText.trim()); 9465 dispatchPrivateContent(privateContent); 9466 } else { 9467 dispatchPrivateContent({}); 9468 } 9469 } 9470 9471 } catch (error) { 9472 console.warn('Error retrieving Private Content:', error); 9473 } 9474 } 9475 9476 hyva.alpineInitialized(loadSectionData) 9477 window.addEventListener('reload-customer-section-data', loadSectionData); 9478 9479 window.processSectionDataBeforeDispatch = window.processSectionDataBeforeDispatch || (data => data); 9480 9481 function dispatchPrivateContent(data) { 9482 9483 const privateContentEvent = new CustomEvent("private-content-loaded", { 9484 detail: { 9485 data: window.processSectionDataBeforeDispatch(data) 9486 } 9487 }); 9488 window.dispatchEvent(privateContentEvent); 9489 } 9490 9491 function fetchPrivateContent(sections) { 9492 fetch(`${BASE_URL}customer/section/load/?sections=${encodeURIComponent(sections.join(','))}`, { 9493 method: 'GET', 9494 headers: { 9495 'Content-Type': 'application/json', 9496 'X-Requested-With': 'XMLHttpRequest' 9497 } 9498 }) 9499 .then(response => response.ok && response.json()) 9500 .then( 9501 data => { 9502 if (data) { 9503 try { 9504 const browserStorage = hyva.getBrowserStorage(); 9505 9506 9507 const oldSectionData = JSON.parse(browserStorage.getItem(private_content_key) || '{}') || {}; 9508 9509 if ((! data.cart || ! data.cart.cartId) && oldSectionData['checkout-data']) { 9510 delete oldSectionData['checkout-data']; 9511 } 9512 const newSectionData = Object.assign(oldSectionData, data); 9513 9514 dispatchPrivateContent(newSectionData); 9515 9516 9517 if (newSectionData.messages && newSectionData.messages.messages ) { 9518 newSectionData.messages.messages = []; 9519 } 9520 9521 browserStorage.setItem(private_content_key, JSON.stringify(newSectionData)); 9522 9523 const expiresAt = new Date(Date.now() + (ttl * 1000)).toISOString(); 9524 browserStorage.setItem(private_content_expire_key, expiresAt); 9525 9526 const newCookieVersion = hyva.getCookie(private_content_version_key); 9527 browserStorage.setItem(private_content_version_key, newCookieVersion); 9528 9529 9530 9531 hyva.setCookie( 9532 section_data_ids_key, 9533 JSON.stringify( 9534 Object.keys(data).reduce((sectionDataIds, sectionKey) => { 9535 sectionDataIds[sectionKey] = data[sectionKey]['data_id']; 9536 return sectionDataIds; 9537 }, {}) 9538 ), 9539 false, 9540 true 9541 ); 9542 } catch (error) { 9543 console.warn("Couldn't store privateContent", error); 9544 } 9545 } 9546 } 9547 ); 9548 } 9549 }</script>
vendor: 1 bytes, line 9549
9549
9550<script> 9551 (() => { 9552 document.addEventListener('submit', event => event.target.action = event.target.action.replace('%25uenc%25', hyva.getUenc())); 9553 })()</script>
vendor: 1 bytes, line 9553
9553
9554<script> 9555 (events => { 9556 const dispatchUserInteractionEvent = () => { 9557 events.forEach(type => window.removeEventListener(type, dispatchUserInteractionEvent)) 9558 window.dispatchEvent(new Event('init-external-scripts')) 9559 }; 9560 events.forEach(type => window.addEventListener(type, dispatchUserInteractionEvent, {once: true, passive: true})) 9561 })(['touchstart', 'mousemove', 'wheel', 'scroll', 'keydown']);</script>
vendor: 1 bytes, line 9561
9561
9562<script> 9563 function initConfigurableOptions(productId, optionConfig) { 9564 9565 function findPathParam(key) { 9566 const baseUrl = (BASE_URL.substring(0, 2) === '//' ? 'http:' : '') + BASE_URL; 9567 const baseUrlParts = (new URL(baseUrl)).pathname.replace(/\/$/, '').split('/'); 9568 const pathParts = window.location.pathname.split('/').slice(baseUrlParts.length + 3); 9569 for (let i = 0; i < pathParts.length; i += 2) { 9570 if (pathParts[i] === key && pathParts.length > i) { 9571 return pathParts[i + 1]; 9572 } 9573 } 9574 } 9575 9576 return { 9577 optionConfig, 9578 productId, 9579 itemId: (new URLSearchParams(window.location.search)).get('id') || findPathParam('id'), 9580 allowedAttributeOptions: [], 9581 selectedValues: [], 9582 init() { 9583 this.findAllowedAttributeOptions(); 9584 this.$nextTick(() => { 9585 if (typeof this.optionConfig.defaultValues === 'object') { 9586 for (const [attributeId, value] of Object.entries(this.optionConfig.defaultValues)) { 9587 this.changeOption(attributeId, value + ''); 9588 } 9589 } 9590 this.preselectQuerystringItems(); 9591 this.preselectLocationHashItems(); 9592 }); 9593 }, 9594 productIndex: 0, 9595 productIndexForPrice: 0, 9596 optionIsActive(attributeId, optionId) { 9597 return !!this.getAllowedAttributeOptions(attributeId).find( 9598 option => option.id === optionId 9599 ) 9600 }, 9601 optionIsEnabled(attributeId, optionId) { 9602 for (const productId in this.optionConfig.index) { 9603 if (this.optionConfig.index[productId][attributeId] === optionId) { 9604 return true; 9605 } 9606 } 9607 return false; 9608 }, 9609 findSimpleIndex() { 9610 this.productIndex = this.calculateSimpleIndexForPartialSelection(this.selectedValues); 9611 this.productIndexForPrice = this.findCheapestProductForPartialSelection(this.selectedValues); 9612 }, 9613 calculateSimpleIndexForPartialSelection(selectedValues) { 9614 if (selectedValues.length === 0) return 0; 9615 return this.findProductIdsForPartialSelection(selectedValues)[0]; 9616 }, 9617 calculateSimpleIndexForFullSelection(selectedValues) { 9618 if (! this.isFullSelection(selectedValues)) return false; 9619 9620 const productIndexes = this.optionConfig.index; 9621 return Object.keys(productIndexes).find(productIndex => { 9622 const productCandidateOptions = productIndexes[productIndex]; 9623 9624 for (const productOption in productCandidateOptions) { 9625 if ( 9626 !selectedValues[productOption] || 9627 selectedValues[productOption] !== productCandidateOptions[productOption] 9628 ) { 9629 return false; 9630 } 9631 } 9632 return productIndex; 9633 }); 9634 }, 9635 findAllowedAttributeOptions() { 9636 this.allowedAttributeOptions = this.calculateAllowedAttributeOptions(this.selectedValues); 9637 }, 9638 calculateAllowedAttributeOptions(selectedValues) { 9639 const allAttributes = this.optionConfig.attributes; 9640 const allAttributesSorted = Object.values(allAttributes).sort((a,b) => { 9641 return a.position - b.position 9642 }); 9643 9644 const newAllowedAttributeOptions = []; 9645 9646 allAttributesSorted.forEach(attribute => { 9647 const selectionWithoutAttr = Object.assign({}, this.removeAttrFromSelection(selectedValues, attribute.id)); 9648 const availableIndexes = this.calculateAvailableProductIndexes(selectionWithoutAttr); 9649 newAllowedAttributeOptions[attribute.id] = allAttributes[attribute.id].options.filter(option => { 9650 return !!option.products.find(product => { 9651 return availableIndexes.includes(product); 9652 }) 9653 }); 9654 }); 9655 return newAllowedAttributeOptions; 9656 }, 9657 calculateAvailableProductIndexes(selectedOptions) { 9658 if (Object.keys(selectedOptions).length === 0) { 9659 if (Object.values(this.optionConfig.salable || {}).length) { 9660 return [].concat.apply([], [].concat.apply([], Object.values(this.optionConfig.salable).map(Object.values))).filter((x, i, a) => a.indexOf(x) === i) 9661 } 9662 return Object.keys(this.optionConfig.index); 9663 } 9664 9665 const selectedIds = Object.keys(selectedOptions); 9666 if (Object.values(this.optionConfig.salable || {}).length) { 9667 const selectedOptionIndexes = selectedIds.map(attrId => { 9668 const optionValue = selectedOptions[attrId]; 9669 return this.optionConfig.salable[attrId] && this.optionConfig.salable[attrId][optionValue] || [] 9670 }) 9671 return selectedOptionIndexes.reduce((acc, optionIndexes) => { 9672 return acc.filter(index => optionIndexes.includes(index)); 9673 }); 9674 } else { 9675 const productIndexes = this.optionConfig.index; 9676 return Object.keys(productIndexes).filter(index => { 9677 for (const attrId of selectedIds) { 9678 if (productIndexes[index][attrId] !== `${selectedOptions[attrId]}`) return false 9679 } 9680 return true 9681 }); 9682 } 9683 }, 9684 findAttributeByOptionId(optionId) { 9685 for (const attributeId in this.optionConfig.attributes) { 9686 const attributeOptions = this.optionConfig.attributes[attributeId].options || []; 9687 if (attributeOptions.find(option => option.id === optionId)) { 9688 return attributeId; 9689 } 9690 } 9691 }, 9692 getAllowedAttributeOptions(attributeId) { 9693 return this.allowedAttributeOptions[attributeId] || [] 9694 }, 9695 getAllAttributeOptions(attributeId) { 9696 return ( 9697 this.optionConfig.attributes[attributeId] && 9698 this.optionConfig.attributes[attributeId].options 9699 ) || [] 9700 }, 9701 getProductIdsForOption(option) { 9702 const attributeId = this.findAttributeByOptionId(option.id); 9703 const allOptions = this.optionConfig.attributes[attributeId]; 9704 const opt = (allOptions && allOptions.options || []).find(o => o.id === option.id); 9705 return opt && opt.products 9706 ? opt.products 9707 : []; 9708 }, 9709 findProductIdsForPartialSelection(optionSelection) { 9710 const candidateProducts = Object.values(optionSelection).reduce((candidates, optionId) => { 9711 const newCandidates = this.getProductIdsForOption({id: optionId}); 9712 return candidates === null 9713 ? newCandidates 9714 : candidates.filter(productId => newCandidates.includes(productId)); 9715 }, null); 9716 return candidateProducts || []; 9717 },
9718 findCheapestProductForPartialSelection(optionSelection) { 9719 const candidateProducts = this.findProductIdsForPartialSelection(optionSelection); 9720 return candidateProducts.reduce((cheapest, simpleIdx) => { 9721 // in the first iteration we start with simpleIdx as the currently cheapest product 9722 if (! this.optionConfig.optionPrices[cheapest]) return simpleIdx; 9723 const knownCheapestPrice = this.optionConfig.optionPrices[cheapest].finalPrice.amount; 9724 return knownCheapestPrice > this.optionConfig.optionPrices[simpleIdx].finalPrice.amount 9725 ? simpleIdx 9726 : cheapest; 9727 }, 0) 9728 }, 9729 findProductIdToUseForOptionPrice(option) { 9730 // try to find a product for a complete selection 9731 const attributeId = this.findAttributeByOptionId(option.id); 9732 const optionSelection = Object.assign({}, this.selectedValues, {[attributeId]: option.id}); 9733 const matchingSimpleIndex = this.calculateSimpleIndexForFullSelection(optionSelection); 9734 // if there is no complete selection, use the cheapest product for the option 9735 return matchingSimpleIndex || this.findCheapestProductForPartialSelection(optionSelection); 9736 }, 9737 getAttributeOptionLabel(option) { 9738 const optionProduct = this.findProductIdToUseForOptionPrice(option); 9739 9740 if ((! optionProduct) || (optionProduct === this.productIndexForPrice)) { 9741 return option.label; 9742 } 9743 9744 const currentPrice = this.getOptionPriceAdjustmentBasePrice(); 9745 9746 if (this.optionConfig.optionPrices[optionProduct]) { 9747 const optionPrice = this.optionConfig.optionPrices[optionProduct].finalPrice.amount; 9748 if (optionPrice !== currentPrice){ 9749 return option.label + ' ' + hyva.formatPrice(optionPrice - currentPrice, true); 9750 } 9751 } 9752 return option.label; 9753 }, 9754 getOptionPriceAdjustmentBasePrice() { 9755 if (this.optionConfig.optionPrices[this.productIndexForPrice]) { 9756 return this.optionConfig.optionPrices[this.productIndexForPrice].finalPrice.amount 9757 } 9758 return this.optionConfig.prices.finalPrice.amount; // default price if no option selection 9759 }, 9760 clearOptionIfActive(optionId, value) { 9761 if (this.selectedValues[optionId] === value) { 9762 this.blurLabel() 9763 this.changeOption(optionId, '') 9764 } 9765 }, 9766 removeAttrFromSelection(selectedValues, attributeId) { 9767 attributeId = parseInt(attributeId); 9768 return selectedValues.reduce((newSelection, val, attr) => { 9769 if (attr !== attributeId) { 9770 newSelection[attr] = val; 9771 } 9772 return newSelection; 9773 }, []); 9774 }, 9775 changeOption(attributeId, value) { 9776 if (value === '') { 9777 this.selectedValues = this.removeAttrFromSelection(this.selectedValues, attributeId) 9778 } else if (value && this.getAllowedAttributeOptions(attributeId).find(option => option.id === value)) { 9779 this.selectedValues[attributeId] = value; 9780 } 9781 this.findSimpleIndex(); 9782 this.findAllowedAttributeOptions(); 9783 this.updatePrices(); 9784 this.updateGallery(); 9785 9786 const candidates = this.findProductIdsForPartialSelection(this.selectedValues); 9787 9788 window.dispatchEvent( 9789 new CustomEvent( 9790 'configurable-selection-changed', 9791 { 9792 detail: { 9793 productId: this.productId, 9794 optionId: attributeId, 9795 value: value, 9796 productIndex: this.productIndex, 9797 selectedValues: this.selectedValues, 9798 candidates: candidates, 9799 skuCandidates: Object.values(candidates).map(id =>
9799 this.optionConfig.sku?.[id] ?? null).filter(Boolean), 9800 } 9801 } 9802 ) 9803 ); 9804 }, 9805 calculateIsMinimalPrice() { 9806 return ! this.isFullSelection(this.selectedValues); 9807 }, 9808 isFullSelection(selectedValues) { 9809 return Object.values(selectedValues).length === Object.keys(this.optionConfig.attributes).length; 9810 }, 9811 updatePrices() { 9812 const value = this.optionConfig.optionPrices[this.productIndexForPrice] || this.optionConfig.prices; 9813 window.dispatchEvent( 9814 new CustomEvent( 9815 "update-prices-" + this.productId, 9816 { 9817 detail: Object.assign( 9818 value, 9819 { isMinimalPrice: this.calculateIsMinimalPrice() } 9820 ) 9821 } 9822 ) 9823 ); 9824 }, 9825 updateGallery () { 9826 if (this.productIndex) { 9827 const images = this.optionConfig.images[this.productIndex]; 9828 images && window.dispatchEvent(new CustomEvent( 9829 "update-gallery", 9830 { detail: this.sortImagesByPosition(images) } 9831 )); 9832 } else { 9833 window.dispatchEvent(new Event("reset-gallery")); 9834 } 9835 }, 9836 sortImagesByPosition(images) { 9837 return images.sort((x, y) => { 9838 return x.position === y.position ? 0 : (parseInt(x.position) > parseInt(y.position) ? 1 : -1) 9839 }); 9840 }, 9841 onGetCartData(data) { 9842 }, 9843 preselectCartItems(data) { 9844 // pre-select options based on cart data for current (quote) itemId 9845 const cart = data && data.cart; 9846 if (cart && cart.items) { 9847 const cartItem = cart.items.find((item) => { 9848 return ( 9849 item.item_id === this.itemId 9850 && item.product_id === this.productId 9851 ) 9852 }); 9853 if (cartItem && cartItem.options && cartItem.options.length) { 9854 cartItem.options.map(option => { 9855 this.changeOption(option.option_id, option.option_value); 9856 }) 9857 } 9858 } 9859 }, 9860 preselectQuerystringItems() { 9861 // pre-select option like ?size=167 9862 const urlQueryParams = new URLSearchParams(window.location.search.replace('?','')); 9863 this.preselectItemsBasedOnLocation(attribute => urlQueryParams.get(attribute.code)); 9864 }, 9865 preselectLocationHashItems() { 9866 // pre-select option like #144=167 9867 const urlHashParams = new URLSearchParams(window.location.hash.replace('#','')); 9868 this.preselectItemsBasedOnLocation(attribute => urlHashParams.get(attribute.id)); 9869 }, 9870 preselectItemsBasedOnLocation(getLocationValue) { 9871 Object.values(this.optionConfig.attributes).map(attribute => { 9872 const v = getLocationValue(attribute); 9873 v && this.changeOption(attribute.id, v) 9874 }); 9875 } 9876 } 9877 } 9878</script>
vendor: 1 bytes, line 9878
9878
9879<script> 9880 function initSwatchOptions(swatchConfig) { 9881 return { 9882 swatchConfig, 9883 getAttributeSwatchData(attributeId) { 9884 const swatchConfig = Object.assign({}, this.swatchConfig[attributeId]); 9885 swatchConfig['details'] = JSON.parse(swatchConfig['additional_data']); 9886 9887 return swatchConfig; 9888 }, 9889 mapSwatchTypeNumberToTypeCode(typeNumber) { 9890 switch ("" + typeNumber) { 9891 case "1": 9892 return "color" 9893 case "2": 9894 return "image" 9895 case "3": 9896 return "empty" 9897 case "0": 9898 default: 9899 return "text" 9900 } 9901 }, 9902 getTypeOfFirstOption(attributeId) { 9903 for (const optionId in this.swatchConfig[attributeId]) { 9904 const option = this.swatchConfig[attributeId][optionId]; 9905 if (typeof option.type !== 'undefined') { 9906 return this.mapSwatchTypeNumberToTypeCode(option.type); 9907 } 9908 } 9909 }, 9910 getVisualSwatchType(attributeId, targetOptionId) { 9911 // If a type configuration is present for the given option id, use it 9912 const config = this.swatchConfig[attributeId]; 9913 if (config[targetOptionId] && typeof config[targetOptionId].type !== 'undefined') { 9914 return this.mapSwatchTypeNumberToTypeCode(config[targetOptionId].type); 9915 } 9916 9917 // Otherwise - if no config is present for the target option - use the type of the first option 9918 // with a type property from the attribute, thus assuming its the same type as the target option. 9919 // (This edge case condition can occur on single swatch products if some options are not salable) 9920 return this.getTypeOfFirstOption(attributeId); 9921 }, 9922 getSwatchType(attributeId, optionId) { 9923 // Deserialize the attribute details the first time they are used 9924 if (this.swatchConfig[attributeId] && ! this.swatchConfig[attributeId].details) { 9925 this.swatchConfig[attributeId] = this.getAttributeSwatchData(attributeId); 9926 } 9927 const type = this.swatchConfig[attributeId] && 9928 this.swatchConfig[attributeId].details && 9929 this.swatchConfig[attributeId].details.swatch_input_type || 9930 "empty"; 9931 return type === 'visual' ? this.getVisualSwatchType(attributeId, optionId) : type; 9932 }, 9933 isTextSwatch(attributeId, optionId) { 9934 return this.getSwatchType(attributeId, optionId) === 'text'; 9935 }, 9936 isVisualSwatch(attributeId, optionId) { 9937 const type = this.getSwatchType(attributeId, optionId); 9938 9939 return ['image', 'color'].includes(type); 9940 }, 9941 getSwatchBackgroundStyle(attributeId, optionId) { 9942 const config = this.getSwatchConfig(attributeId, optionId); 9943 const type = this.getSwatchType(attributeId, optionId); 9944 9945 if (type === "color") { 9946 return 'background-color:' + config.value; 9947 } else if (type === "image") { 9948 return "background: #fff url('" + config.value + "') no-repeat center"; 9949 } else { 9950 return ''; 9951 } 9952 }, 9953 getSwatchText(attributeId, optionId) { 9954 const config = this.getSwatchConfig(attributeId, optionId); 9955 return config.label || config.value || this.getOptionLabelFromOptionConfig(attributeId, optionId); 9956 }, 9957 getOptionLabelFromOptionConfig(attributeId, optionId) { 9958 // Fallback if no value is present in swatchConfig data 9959 // Reference issue https://gitlab.hyva.io/hyva-themes/magento2-default-theme/-/issues/190 9960 const option = this.getAllAttributeOptions(attributeId).filter(option => option.id === optionId); 9961 return option && option[0] && option[0].label ||''; 9962 }, 9963 getSwatchConfig(attributeId, optionId) { 9964 return this.swatchConfig[attributeId] && this.swatchConfig[attributeId][optionId] 9965 ? this.swatchConfig[attributeId][optionId] 9966 : false;
9967 }, 9968 activeTooltipItem: false, 9969 tooltipPositionElement: false, 9970 isTooltipVisible() { 9971 return this.activeTooltipItem && 9972 this.getSwatchConfig( 9973 this.activeTooltipItem.attribute, 9974 this.activeTooltipItem.item 9975 ); 9976 }, 9977 isFirstItemCol() { 9978 return this.activeTooltipItem.index === 0; 9979 }, 9980 getTooltipImageStyle(attributeId, optionId) { 9981 const config = this.getSwatchConfig(attributeId, optionId); 9982 const type = this.getSwatchType(attributeId, optionId); 9983 9984 if (type === "color") { 9985 return 'background-color:' + config.value + '; width: 110px; height: 90px;'; 9986 } else if (type === "image") { 9987 return "background: #ffffff url('" + config.thumb + 9988 "') center center no-repeat; width: 110px; height: 90px;"; 9989 } else { 9990 return 'display:none'; 9991 } 9992 }, 9993 getTooltipPosition() { 9994 return this.tooltipPositionElement ? 9995 `top: ${this.tooltipPositionElement.offsetTop}px;` + 9996 `left: ${ 9997 this.tooltipPositionElement.offsetLeft - ( 9998 this.tooltipPositionElement.closest('.snap') && 9999 this.tooltipPositionElement.closest('.snap').scrollLeft || 10000 0 10001 ) 10002 }px;` : '' 10003 }, 10004 getTooltipLabel() { 10005 return this.getSwatchConfig(this.activeTooltipItem.attribute, this.activeTooltipItem.item).label 10006 }, 10007 focusedLabel: false, 10008 focusLabel(optionId) { 10009 this.focusedLabel = optionId; 10010 }, 10011 blurLabel() { 10012 this.focusedLabel = false; 10013 } 10014 } 10015 } 10016</script>
vendor: 1 bytes, line 10016
10016
10017<script> 10018 'use strict'; 10019 10020 const grecaptchaV2LoadCallbacks = []; 10021 10022 function executeRecaptchaLoadCallbacks () { 10023 while (window.grecaptcha && grecaptchaV2LoadCallbacks.length > 0) { 10024 const callback = grecaptchaV2LoadCallbacks.pop(); 10025 typeof callback === 'string' ? window[callback]() : callback(); 10026 } 10027 } 10028 10029 (function() { 10030 const loadRecaptchaScript = (event) => { 10031 10032 if (window.grecaptcha || !event.target.form) { 10033 return; 10034 } 10035 10036 const url = 'https://www.google.com/recaptcha/api.js?onload=executeRecaptchaLoadCallbacks'; 10037 const siteKey = '6LfHDCMrAAAAACnDJl581eICSW3VnauItgp_hR2S'; 10038 const script = document.createElement('script'); 10039 script.type = 'text/javascript'; 10040 script.src = siteKey ? `${url}&render=${siteKey}` : `${url}&render=explicit`; 10041 script.async = true; 10042 document.head.appendChild(script); 10043 10044 script.addEventListener('load', () => { 10045 Array.from(document.querySelectorAll('button[data-recaptcha-btn]:disabled')).forEach(btn => btn.removeAttribute('disabled')); 10046 }); 10047 } 10048 10049 window.forceLoadRecaptchaScript = (form) => { 10050 loadRecaptchaScript({target: {form: form || 'dummy'}}); 10051 executeRecaptchaLoadCallbacks(); 10052 }; 10053 10054 document.body.addEventListener('input', loadRecaptchaScript, { once: true }); 10055 document.body.addEventListener('focus', loadRecaptchaScript, { once: true }); 10056 10057 }()); 10058 10059</script>
vendor: 1 bytes, line 10059
10059
10060<script> 10061 (function (w, d, s, l, i) { 10062 w[l] = w[l] || []; w[l].push({ 10063 'gtm.start': 10064 new Date().getTime(), event: 'gtm.js' 10065 }); var f = d.getElementsByTagName(s)[0], 10066 j = d.createElement(s), dl = l != 'dataLayer' ? '&l=' + l : ''; j.async = true; j.src = 10067 'https://www.googletagmanager.com/gtm.js?id=' + i + dl; f.parentNode.insertBefore(j, f); 10068 })(window, document, 'script', 'dataLayer', 'GTM\u002D5VC2G8L'); 10069</script>
vendor: 1 bytes, line 10069
10069
10070<script> 10071 'use strict'; 10072 10073 function initAmInitConfigurableSwatchOptions() { 10074 const productId = this.$el.dataset.productId ?? this.$el.closest('form')?.querySelector('input[name="product"]').value; 10075 const component = window['initConfigurableSwatchOptions'] ?? window['initConfigurableSwatchOptions_' + productId] 10076 const originInitConfigurableSwatchOptions = component.call(this); 10077 10078 return { 10079 ...originInitConfigurableSwatchOptions, 10080 init() { 10081 if (this.showSwatches !== undefined) { 10082 this.$watch('showSwatches', value => { 10083 if (value) { 10084 this.dispatchConfigurableShowEvent(); 10085 } 10086 }); 10087 } else { 10088 this.$nextTick(() => { 10089 this.dispatchConfigurableShowEvent(); 10090 }); 10091 } 10092 10093 this.findAllowedAttributeOptions(); 10094 this.$nextTick(() => { 10095 if (typeof this.optionConfig.defaultValues === 'object') { 10096 for (const [attributeId, value] of Object.entries(this.optionConfig.defaultValues)) { 10097 this.changeOption(attributeId, value + ''); 10098 } 10099 } 10100 }); 10101 }, 10102 10103 dispatchConfigurableShowEvent() { 10104 window.dispatchEvent(new CustomEvent('configurable-show-swatches', { 10105 detail: { 10106 productId: this.productId, 10107 element: this.$el 10108 } 10109 })); 10110 } 10111 } 10112 } 10113 10114 window.addEventListener('alpine:init', () => { 10115 Alpine.data('initAmInitConfigurableSwatchOptions', initAmInitConfigurableSwatchOptions); 10116 }, {once: true});</script>
vendor: 1 bytes, line 10116
10116
10117<script type="text/json" id="default-section-data"> 10118 {"messages":[],"customer":[],"compare-products":[],"last-ordered-items":[],"requisition":[],"cart":[],"directory-data":{"GB":{"name":"United Kingdom"},"data_id":1790927930},"captcha":[],"wishlist":{"items":[]},"company":[],"company_authorization":[],"customer-companies":[],"negotiable_quote":[],"instant-purchase":[],"loggedAsCustomer":[],"multiplewishlist":[],"purchase_order":[],"persistent":[],"review":[],"payments":[],"amasty-storepickup-data":[],"apptrian_metapixelapi_matching_section":[],"webforms":[],"hyva_checkout":[],"chatData":[],"recently_viewed_product":[],"recently_compared_product":[],"product_data_storage":[],"paypal-billing-agreement":[],"paypal-buyer-country":[]}</script>
vendor: 1 bytes, line 10118
10118
10119<script> 10120 'use strict'; 10121 10122 function attributeIcon() { 10123 return { 10124 labelledById: '', 10125 10126 init() { 10127 const timestamp = Date.now(); 10128 const randomNum = Math.floor(Math.random() * 1000); 10129 this.labelledById = `amshopby-option-id-${timestamp}-${randomNum}`; 10130 } 10131 } 10132 } 10133 window.addEventListener( 10134 'alpine:init', 10135 () => Alpine.data('attributeIcon',attributeIcon), 10136 {once: true} 10137 );</script>
vendor: 1 bytes, line 10137
10137
10138<script> 10139document.addEventListener('alpine:init', () => { 10140 Alpine.data('newsletterForm', () => ({ 10141 firstname: '', 10142 lastname: '', 10143 email: '', 10144 errors: {}, 10145 10146 validateField(field) { 10147 this.errors[field] = ''; 10148 10149 if (field === 'firstname' && !this.firstname.trim()) { 10150 this.errors.firstname = 'First\u0020Name\u0020is\u0020required.'; 10151 } 10152 10153 if (field === 'lastname' && !this.lastname.trim()) { 10154 this.errors.lastname = 'Last\u0020Name\u0020is\u0020required.'; 10155 } 10156 10157 if (field === 'email') { 10158 if (!this.email.trim()) { 10159 this.errors.email = 'Email\u0020is\u0020required.'; 10160 } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.email)) { 10161 this.errors.email = 'Please\u0020enter\u0020a\u0020valid\u0020email\u0020address.'; 10162 } 10163 } 10164 }, 10165 10166 validateForm() { 10167 this.validateField('firstname'); 10168 this.validateField('lastname'); 10169 this.validateField('email'); 10170 return Object.values(this.errors).every(error => error === ''); 10171 }, 10172 10173 submitForm() { 10174 const form = document.querySelector('#newsletter-validate-detail'); 10175 10176 if (this.validateForm()) { 10177 form.submit(); 10178 } 10179 } 10180 })); 10181}); 10182</script>
vendor: 1 bytes, line 10182
10182
10183<script type="text/x-magento-init"> 10184 { 10185 ".popup-newsletter": { 10186 "newsletterPopup": { 10187 10188 "delay":"40", 10189 "closing":"5" 10190 } 10191 } 10192 }</script>
vendor: 1 bytes, line 10192
10192
10193<script type="text/x-magento-init">{"*": 10194 {"Magento_Persistent/js/remember-me-config": { 10195 "config": {"persistenceConfig":{"isRememberMeCheckboxVisible":true,"isRememberMeCheckboxChecked":true}} 10196 }}}</script>
10196 10197</body> 10198</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.