1<!doctype html> 2<!--[if lt IE 7]><html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="en"> <![endif]--> 3<!--[if IE 7]><html class="no-js lt-ie9 lt-ie8" lang="en"> <![endif]--> 4<!--[if IE 8]><html class="no-js lt-ie9" lang="en"> <![endif]--> 5<!--[if IE 9 ]><html class="ie9 no-js"> <![endif]--> 6<!--[if (gt IE 9)|!(IE)]><!--> <html class="no-js"> <!--<![endif]--> 7<head> 8 9 <!-- Basic page needs ================================================== --> 10 <meta charset="utf-8"> 11 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> 12 13 14 <link rel="icon" href="https://cdn.store-assets.com/s/1325208/f/11789123.png" /> 15 16 17 <!-- Title and description ================================================== --> 18 <title> 19 Store Locator – FINE LABEL 20 </title> 21 22 23 24 <!-- Social meta ================================================== --> 25 26 27 <meta property="og:type" content="website"> 28 <meta property="og:title" content="Store Locator"> 29 <meta property="og:url" content="https://www.fine-label.com/pages/store-locator"> 30 31 <meta property="og:image" content="https://cdn.store-assets.com/s/1325208/f/17866599.png"> 32 <meta property="og:image:secure_url" content="https://cdn.store-assets.com/s/1325208/f/17866599.png"> 33 34 35 36<meta property="og:description" content="FINE LABEL"> 37<meta property="og:site_name" content="FINE LABEL"> 38 39 40 41 <meta name="twitter:card" content="summary"> 42 43 44 45 <meta name="twitter:site" content="@"> 46 47 48 49 50 51 <!-- Helpers ================================================== --> 52 <link rel="canonical" href="https://www.fine-label.com/pages/store-locator"> 53 <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1"> 54 <meta name="theme-color" content="#000000"> 55 56
56<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js" type="text/javascript" ></script>
56 57 58 <!-- Header hook for plugins ================================================== --> 59 60<!-- ScriptTags -->
61<script>window.__st={p: "page",cid: null,cem: null,cph: null,cfn: null,cln: null,cge: null,cdb: null,ccc: null};</script>
61<script src='/assets/events.js'></script>
61<script>(function(){function asyncLoad(){var urls=['/assets/facebook-pixel.js?v=1.2.1787999665','/assets/traffic.js?v=1','https://apps.easystore.co/timer-discount/script.js?shop=miyacostore.easy.co&t=1697164155','https://apps.easystore.co/google-analytics/script.js?shop=miyacostore.easy.co&t=1696147478','https://apps.easystore.co/google-analytics-4/script.js?shop=miyacostore.easy.co&t=1771850563','https://smartarget.online/loader.js?type=int&u=5b005cd48353d53dc05b0d4340a6efeda1aa07d6&source=easystore_whatsapp','https://smartarget.online/loader.js?type=int&u=9ab458647cd423cbc5665f224ad3b23a5107759f&source=easystore_whatsapp','https://apps.easystore.co/facebook-login/script.js?shop=miyacostore.easy.co&t=1728305893'];for(var i=0;i<urls.length;i++){var s=document.createElement('script');s.type='text/javascript';s.async=true;s.src=urls[i];var x=document.getElementsByTagName('script')[0];x.parentNode.insertBefore(s, x);}}window.attachEvent ? window.attachEvent('onload', asyncLoad) : window.addEventListener('load', asyncLoad, false);})();</script>
61 62<!-- /ScriptTags --> 63 64 65 66
66<script src="https://store-themes.easystore.co/1325208/themes/308896/assets/global.js?t=1791095546" defer="defer"></script>
66 67 68 69 70
70<script> 71 (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 72 (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 73 m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 74 })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 75 76 ga('create', '', 'auto','myTracker'); 77 ga('myTracker.send', 'pageview'); 78 79 </script>
79 80 81
81<script src="/assets/storefront.js?id=1d419de2011a5572c98b" type="text/javascript" ></script>
81 82 83 84 <!-- CSS ================================================== --> 85 <style> 86 @font-face { 87 font-family: 'Alata'; 88 font-style: normal; 89 font-weight: regular; 90 src: local('Alata regular'), local('Alata-regular'), url(https://fonts.gstatic.com/s/alata/v2/PbytFmztEwbIofe6xKcRQEOX.ttf) format('truetype'); 91 } 92 93 @font-face { 94 font-family: 'Alata'; 95 font-style: normal; 96 font-weight: regular; 97 src: local('Alata regular'), local('Alata-regular'), url(https://fonts.gstatic.com/s/alata/v2/PbytFmztEwbIofe6xKcRQEOX.ttf) format('truetype'); 98 } 99 100 :root { 101 --font-body-family: 'Alata', sans-serif; 102 --font-body-style: normal; 103 --font-body-weight: 500; 104 105 --font-heading-family: 'Alata', sans-serif; 106 --font-heading-style: normal; 107 --font-heading-weight: 700; 108 109 --color-heading-text: 0,0,0; 110 --color-base-text: 0,0,0; 111 --color-base-background-1: 255,255,255; 112 --color-base-background-2: 255,255,255; 113 114 --color-base-solid-button-labels: 255,255,255; 115 --color-base-outline-button-labels: 5,28,98; 116 117 --color-base-accent-1: 0,0,0; 118 --color-base-accent-2: 0,0,0; 119 --payment-terms-background-color: 255,255,255; 120 121 --gradient-base-background-1: 255,255,255; 122 --gradient-base-background-2: 255,255,255; 123 --gradient-base-accent-1: 0,0,0; 124 --gradient-base-accent-2: 0,0,0; 125 126 --page-width: 145rem; 127 } 128 129 *, 130 *::before, 131 *::after { 132 box-sizing: inherit; 133 } 134 135 html { 136 box-sizing: border-box; 137 font-size: 62.5%; 138 height: 100%; 139 } 140 141 body { 142 display: grid; 143 grid-template-rows: auto auto 1fr auto; 144 grid-template-columns: 100%; 145 min-height: 100%; 146 margin: 0; 147 font-size: 1.5rem; 148 letter-spacing: 0.06rem; 149 line-height: 1.8; 150 font-family: var(--font-body-family); 151 font-style: var(--font-body-style); 152 font-weight: var(--font-body-weight); 153 154 } 155 156 @media screen and (min-width: 750px) { 157 body { 158 font-size: 1.6rem; 159 } 160 } 161 </style> 162 163 <link href="https://store-themes.easystore.co/1325208/themes/308896/assets/base.css?t=1791095546" rel="stylesheet" type="text/css" media="screen" /> 164 165 166 167<!-- Snippet:global/head: OnVoard AOV Progress Bar -->
168<script> 169 (function() { 170 window.OnVoard = window.OnVoard || function() { 171 (window.OnVoard.q = window.OnVoard.q || []).push(arguments); 172 }; 173 var script = document.createElement("script"); 174 var parent = document.getElementsByTagName("script")[0].parentNode; 175 script.async = 1; 176 177 178 script.src = "https://scripts.onvoard.com/apps/loader.js"; 179 180 parent.appendChild(script); 181 })(); 182</script>
182 183 184
185<script> 186 187var ovData = {}; 188ovData["ecommerce_platform"] = "easystore"; 189ovData["account_id"] = "acct_6r1h4l52ykzoduq"; 190 191 192 ovData["template"] = "page"; 193 ovData["cart"] = {"item_count":0,"total_price":0,"announcements":[]}; 194 ovData["product"] = ""; 195 ovData["shop"] = {}; 196 ovData["customer"] = {}; 197 198 199 ovData["shop"] = { 200 "currency": "MYR", 201 "domain": "fine-label.com", 202 "url": "https://fine-label.com", 203 "money_format": "<span class=money data-ori-price='{{amount}}'>RM {{amount}} </span>", 204 }; 205 206 207 208 209 window.OnVoardData = ovData; 210</script>
210 211 212<!-- /Snippet --> 213 214<!-- Snippet:global/head: Recently View --> 215 216 <link rel="stylesheet" href="https://apps.easystore.co/assets/css/recently-view/main.css?v1"> 217<!-- /Snippet --> 218 219<!-- Snippet:global/head: Site Verification --> 220<meta name="google-site-verification" content="AJWgo3mh5nhLCv5weJY01jpS9ahI3dWpdV2-iqNEKBY" /> 221<!-- /Snippet --> 222 223 224
224<script>document.documentElement.className = document.documentElement.className.replace('no-js', 'js');</script>
224 225</head> 226 227<body id="store-locator" class="template-page" > 228 229 230<!-- Snippet:global/body_start: Announcement Bar --> 231<link rel="stylesheet" href="https://apps.easystore.co/assets/css/annoucement-bar/slider.css?v1.01"> 232 233<input type="hidden" id="total_announcement_left" name="total_announcement_left" value="4"> 234<input type="hidden" id="locale" name="locale" value="en_US"> 235 236<slider-announcement-bar-app id="announcement-bar" name="announcement-bar" style=" 237 border: unset; 238 padding: 4px 0px; 239 margin: 0px; 240 z-index:2999; 241 background:rgb(250, 250, 250); 242 color:rgb(0, 0, 0); 243 244"> 245 246 <div style=" 247 display: flex; 248 justify-content: space-between; 249 align-items: center; 250 padding: 3px 0px; 251 border-top: 1px dashed; 252 border-bottom: 1px dashed; 253 border-color: rgb(0, 0, 0); 254 "> 255 <div style="height: 24px;"> 256 <a id="previous-announcement-bar-button" class="announcement-bar-direction-button" type="button" name="previous" style="height: 24px;"> 257 <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-chevron-left" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="white" fill="none" stroke-linecap="round" stroke-linejoin="round"> 258 <path stroke="none" d="M0 0h24v24H0z" fill="none" /> 259 <polyline points="15 6 9 12 15 18" /> 260 </svg> 261 </a> 262 </div> 263 264 <ul style="flex-grow: 2; margin: 0px;"> 265 <li id="announcement_bar_0" style=" 266 display: flex; 267 justify-content: center; 268 align-items: center; 269 flex-wrap: wrap; 270 margin: 0px; 271 width: 100%; 272 word-wrap: break-word; 273 "> 274 Hello October ⨠New Month, New Looks · Every 2pcs Off RM10 | Code: HELLOOCT 275 <input type="hidden" id="expired_at_0" name="expired_at_0" value="2026-09-10 12:00"> 276 </li> 277 <li id="announcement_bar_1" style=" 278 display: flex; 279 justify-content: center; 280 align-items: center; 281 flex-wrap: wrap; 282 margin: 0px; 283 width: 100%; 284 word-wrap: break-word; 285 "> 286 ? 02/09 伿¯ä¸å¤©ï½é¨å¸æåè¥ä¸ï¼å®¢æä¸åºè´§å°äº 03/09 æ¢å¤å¤çï¼å®ç½ç §å¸¸ä¸å â¤ï¸ 287 <div id='announcementBar_countdown_1' class="announcementBar_timer style_1" style="color: rgb(233, 69, 69); background: rgb(255, 255, 255); display: none;"> 288 </div> 289 <input type="hidden" id="expired_at_1" name="expired_at_1" value="2026-06-10 00:00"> 290 </li> 291 <li id="announcement_bar_2" style=" 292 display: flex; 293 justify-content: center; 294 align-items: center; 295 flex-wrap: wrap; 296 margin: 0px; 297 width: 100%; 298 word-wrap: break-word; 299 "> 300 ? åªè¦ä¹°æ»¡ RM100ï¼å°±è½æ¿çº¢å å° Ã2ï¼éå®ä¸ºæ¢ï¼ 301 <div id='announcementBar_countdown_2' class="announcementBar_timer style_2" style="color: rgb(233, 69, 69); background: rgb(255, 255, 255); display: none;"> 302 </div> 303 <input type="hidden" id="expired_at_2" name="expired_at_2" value="2024-08-19 00:00"> 304 </li> 305 <li id="announcement_bar_3" style=" 306 display: flex; 307 justify-content: center; 308 align-items: center; 309 flex-wrap: wrap; 310 margin: 0px; 311 width: 100%; 312 word-wrap: break-word; 313 "> 314 å§å¦¹ä»¬ å£è¯ä¼æ 10%ææ£ç»æå计æ¶ï¼ä¸è¦è®©ä½ çè´ç©è½¦ç©ºçè¿å£è¯å¦ï½?âï¸ Use Code: XMAS10 315 <div id='announcementBar_countdown_3' class="announcementBar_timer style_1" style="color: rgb(233, 69, 69); background: rgb(255, 255, 255); display: none;"> 316 </div> 317 <input type="hidden" id="expired_at_3" name="expired_at_3" value="2024-09-02 00:00"> 318 </li> 319 </ul> 320 321 <div style="display: flex;"> 322 <a id="next-announcement-bar-button" class="announcement-bar-direction-button" type="button" name="next" style="height: 24px;"> 323 <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-chevron-right" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="white" fill="none" stroke-linecap="round" stroke-linejoin="round"> 324 <path stroke="none" d="M0 0h24v24H0z" fill="none" /> 325 <polyline points="9 6 15 12 9 18" /> 326 </svg> 327 </a>
328 <span id="announcement-close-button" name="announcement-close-button" class="announcement-close-button" style="margin-right: 10px;"></span> 329 </div> 330 </div> 331 332 333</slider-announcement-bar-app> 334
335<script> 336 $(function() { 337 var is_top_enabled = 0; 338 339 const loadScript = function(url, callback) { 340 const script = document.createElement("script"); 341 script.type = "text/javascript"; 342 // If the browser is Internet Explorer. 343 if (script.readyState) { 344 script.onreadystatechange = function() { 345 if (script.readyState == "loaded" || script.readyState == "complete") { 346 script.onreadystatechange = null; 347 callback(); 348 } 349 }; 350 // For any other browser. 351 } else { 352 script.onload = function() { 353 callback(); 354 }; 355 } 356 script.src = url; 357 document.getElementsByTagName("head")[0].appendChild(script); 358 }; 359 360 const announcementBarAppJS = function($) { 361 362 $('#announcement-close-button').on('click', function() { 363 $('#announcement-bar, #announcement-bar-top').hide(); 364 $('#easystore-section-header, .sticky-topbar').css('top', ''); 365 }); 366 } 367 368 if (typeof jQuery === 'undefined') { 369 loadScript('//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js', function() { 370 jQuery = jQuery.noConflict(true); 371 announcementBarAppJS(jQuery); 372 }); 373 } else { 374 announcementBarAppJS(jQuery); 375 } 376 377 378 379 if(is_top_enabled){ 380 // Prevent announcement bar block header 381 setTimeout(()=>{ 382 $('#easystore-section-header, .sticky-topbar').css('top', $('#announcement-bar').height()+'px'); 383 }, 2000); 384 } 385 }); 386 387 setInterval(function() { 388 const total_announcement = "4" 389 var total_announcement_left = $("#total_announcement_left").val(); 390 391 for (let i = 0; i <= total_announcement; i++) { 392 const startDate = new Date(); 393 const endDateStr = $("#expired_at_" + i).val(); 394 const endDate = endDateStr == undefined ? new Date() : new Date(endDateStr.replace(/-/g, "/")); 395 const seconds = (endDate.getTime() - startDate.getTime()) / 1000; 396 397 const days = parseInt(seconds / 86400); 398 const hours = parseInt((seconds % 86400) / 3600); 399 const mins = parseInt((seconds % 86400 % 3600) / 60); 400 const secs = parseInt((seconds % 86400 % 3600) % 60); 401 402 // use to translate countdown unit 403 // (translate based on the preferred language when save announcement bar setting) 404 String.prototype.translate = function() { 405 try { 406 if ($("#locale").val() == "zh_TW") { 407 if (this.toString() === 'day') { 408 return "天"; 409 } 410 if (this.toString() === 'hour') { 411 return "å°æ"; 412 } 413 if (this.toString() === 'min') { 414 return "åé"; 415 } 416 if (this.toString() === 'sec') { 417 return "ç§"; 418 } 419 } else { 420 if (this.toString() === 'day') { 421 if (days > 0) { 422 return "Days"; 423 } else { 424 return "Day"; 425 } 426 } else if (this.toString() === 'hour') { 427 if (hours > 0) { 428 return "Hours"; 429 } else { 430 return "Hour"; 431 } 432 } else if (this.toString() === 'min') { 433 if (mins > 0) { 434 return "Mins"; 435 } else { 436 return "Min"; 437 } 438 } else if (this.toString() === 'sec') { 439 if (secs > 0) { 440 return "Secs"; 441 } else { 442 return "Sec"; 443 } 444 } 445 } 446 } catch (error) { 447 console.log("Some errors heres", error); 448 } 449 }; 450 451 const announcementBar_countdown = document.getElementById("announcementBar_countdown_" + i); 452 if (announcementBar_countdown && seconds > 0) { 453 454 $(announcementBar_countdown).show() 455 announcementBar_countdown.innerHTML = ` 456 <div> 457 ${days} <small>${'day'.translate()}</small> 458 </div> 459 <div> 460 ${hours} <small>${'hour'.translate()}</small> 461 </div> 462 <div> 463 ${mins} <small>${'min'.translate()}</small> 464 </div> 465 <div> 466 ${secs} <small>${'sec'.translate()}</small> 467 </div> 468 `; 469 470 471 } else if (announcementBar_countdown && seconds <= 0) { 472 $("#announcement_bar_" + i).remove(); 473 total_announcement_left = total_announcement_left - 1; 474 $("#total_announcement_left").val(total_announcement_left); 475 } 476 } 477 478 showOrHide(total_announcement_left); 479 }, 1000); 480 481 482 function showOrHide(total_announcement_left) { 483 if (total_announcement_left <= 1) { 484 $("#previous-announcement-bar-button,#next-announcement-bar-button").hide(); 485 } else { 486 $("#previous-announcement-bar-button,#next-announcement-bar-button").show(); 487 } 488 489 if (total_announcement_left == 0) { 490 $("#announcement-close-button").hide(); 491 $("#announcement-bar").hide(); 492 $('#announcement-bar-top').hide(); 493 $('#easystore-section-header, .sticky-topbar').css('top', ''); 494 } 495 }; 496 497 let annoucementBarAutoMoveInterval = ''; 498 class AnnouncementBarAppSlider extends HTMLElement { 499 constructor() { 500 super(); 501 this.slider = this.querySelector('ul'); 502 this.sliderItems = this.querySelectorAll('li'); 503 this.prevButton = this.querySelector('a[name="previous"]'); 504 this.nextButton = this.querySelector('a[name="next"]'); 505 506 if (!this.slider || !this.nextButton) return; 507 508 const resizeObserver = new ResizeObserver(entries => this.initPages()); 509 resizeObserver.observe(this.slider); 510 511 this.slider.addEventListener('scroll', this.update.bind(this)); 512 this.prevButton.addEventListener('click', this.onButtonClick.bind(this)); 513 this.nextButton.addEventListener('click', this.onButtonClick.bind(this)); 514 515 516 } 517 518 initPages() { 519 const sliderItemsToShow = Array.from(this.sliderItems).filter(element => element.clientWidth > 0); 520 this.sliderLastItem = sliderItemsToShow[sliderItemsToShow.length - 1]; 521 if (sliderItemsToShow.length === 0) return; 522 this.slidesPerPage = Math.floor(this.slider.clientWidth / sliderItemsToShow[0].clientWidth); 523 this.totalPages = sliderItemsToShow.length - this.slidesPerPage + 1; 524 this.update(); 525 let self = this 526 var total_announcement_left = $("#total_announcement_left").val(); 527 annoucementBarAutoMoveInterval = setInterval(function() { 528 if (total_announcement_left > 1) { 529 self.moveSlide('next') 530 } 531 }, 5000) 532 } 533 534 update() { 535 this.currentPage = Math.round(this.slider.scrollLeft / this.sliderLastItem.clientWidth) + 1; 536 } 537 538 onButtonClick(event) { 539 event.preventDefault(); 540 let self = this; 541 self.moveSlide(event.currentTarget.name); 542 } 543 544 545 moveSlide(move_to) { 546 547 clearInterval(annoucementBarAutoMoveInterval); 548 let self = this; 549 annoucementBarAutoMoveInterval = setInterval(function() { 550 self.moveSlide('next'); 551 }, 5000) 552 553 if (move_to === 'previous' && this.currentPage === 1) { 554 this.slider.scrollTo({ 555 left: this.sliderLastItem.clientWidth * (this.totalPages - 1) 556 }); 557 } else if (move_to === 'next' && this.currentPage === this.totalPages) { 558 this.slider.scrollTo({ 559 left: 0 560 }); 561 } else { 562 const slideScrollPosition = move_to === 'next' ? this.slider.scrollLeft + this.sliderLastItem 563 .clientWidth : this.slider.scrollLeft - this.sliderLastItem.clientWidth; 564 this.slider.scrollTo({ 565 left: slideScrollPosition 566 }); 567 } 568 } 569 570 } 571 572 customElements.define('slider-announcement-bar-app', AnnouncementBarAppSlider); 573</script>
573 574 575<!-- /Snippet --> 576 577<!-- Snippet:global/body_start: Console Extension --> 578<div id='es_console' style='display: none;'>1325208</div> 579<!-- /Snippet --> 580 581 582 <style> 583 header { 584 --logo-width: 105px; 585 } 586 .header-wrapper, 587 .header-wrapper .list-menu--disclosure, 588 .header-wrapper .search-modal{ 589 background-color: #FFFFFF; 590 } 591 .header-wrapper .search-modal .field__input{ 592 background-color: rgb(var(--color-background)); 593 } 594 .header-wrapper summary .icon-caret, 595 .header-wrapper .header__menu-item a, 596 .header-wrapper .list-menu__item, 597 .header-wrapper .link--text{ 598 color: #000000; 599 } 600 601 .search-modal__form{ 602 position: relative; 603 } 604 605 .dropdown { 606 display: none; 607 position: absolute; 608 top: 100%; 609 left: 0; 610 width: 100%; 611 padding: 5px 0; 612 background-color: #fff; 613 color:#000; 614 z-index: 1000; 615 border-bottom-left-radius: 15px; 616 border-bottom-right-radius: 15px; 617 border: 1px solid rgba(var(--color-foreground), 1); 618 border-top: none; 619 overflow-x: hidden; 620 overflow-y: auto; 621 max-height: 350px; 622 } 623 624 .dropdown-item { 625 padding: 0.4rem 2rem; 626 cursor: pointer; 627 line-height: 1.4; 628 overflow: hidden; 629 text-overflow: ellipsis; 630 white-space: nowrap; 631 } 632 633 .dropdown-item:hover{ 634 background-color: #f3f3f3; 635 } 636 637 .search__input.is-focus{ 638 border-radius: 15px; 639 border-bottom-left-radius: 0; 640 border-bottom-right-radius: 0; 641 border: 1px solid rgba(var(--color-foreground), 1); 642 border-bottom: none; 643 box-shadow: none; 644 } 645 646 .clear-all { 647 text-align: right; 648 padding: 0 2rem 0.2rem; 649 line-height: 1; 650 font-size: 70%; 651 margin-bottom: -2px; 652 } 653 654 .clear-all:hover{ 655 background-color: #fff; 656 } 657 658 .search-input-focus .easystore-section-header-hidden{ 659 transform: none; 660 } 661 .referral-notification { 662 position: absolute; 663 top: 100%; 664 right: 0; 665 background: white; 666 border: 1px solid #e0e0e0; 667 border-radius: 8px; 668 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); 669 width: 280px; 670 z-index: 1000; 671 padding: 16px; 672 margin-top: 8px; 673 font-size: 14px; 674 line-height: 1.4; 675 } 676 677 .referral-notification::before { 678 content: ''; 679 position: absolute; 680 top: -8px; 681 right: 20px; 682 width: 0; 683 height: 0; 684 border-left: 8px solid transparent; 685 border-right: 8px solid transparent; 686 border-bottom: 8px solid white; 687 } 688 689 .referral-notification::after { 690 content: ''; 691 position: absolute; 692 top: -9px; 693 right: 20px; 694 width: 0; 695 height: 0; 696 border-left: 8px solid transparent; 697 border-right: 8px solid transparent; 698 border-bottom: 8px solid #e0e0e0; 699 } 700 701 .referral-notification h4 { 702 margin: 0 0 8px 0; 703 color: #2196F3; 704 font-size: 16px; 705 font-weight: 600; 706 } 707 708 .referral-notification p { 709 margin: 0 0 12px 0; 710 color: #666; 711 } 712 713 .referral-notification .referral-code { 714 background: #f5f5f5; 715 padding: 8px; 716 border-radius: 4px; 717 font-family: monospace; 718 font-size: 13px; 719 text-align: center; 720 margin: 8px 0; 721 border: 1px solid #ddd; 722 } 723 724 .referral-notification .close-btn { 725 position: absolute; 726 top: 8px; 727 right: 8px; 728 background: none; 729 border: none; 730 font-size: 18px; 731 color: #999; 732 cursor: pointer; 733 padding: 0; 734 width: 20px; 735 height: 20px; 736 line-height: 1; 737 } 738 739 .referral-notification .close-btn:hover { 740 color: #333; 741 } 742 743 .referral-notification .action-btn { 744 background: #2196F3; 745 color: white; 746 border: none; 747 padding: 8px 16px; 748 border-radius: 4px; 749 cursor: pointer; 750 font-size: 12px; 751 margin-right: 8px; 752 margin-top: 8px; 753 } 754 755 .referral-notification .action-btn:hover { 756 background: #1976D2; 757 } 758 759 .referral-notification .dismiss-btn { 760 background: transparent; 761 color: #666; 762 border: 1px solid #ddd; 763 padding: 8px 16px; 764 border-radius: 4px; 765 cursor: pointer; 766 font-size: 12px; 767 margin-top: 8px; 768 } 769 770 .referral-notification .dismiss-btn:hover { 771 background: #f5f5f5; 772 } 773 .referral-modal__dialog { 774 transform: translate(-50%, 0); 775 transition: transform var(--duration-default) ease, visibility 0s; 776 z-index: 10000; 777 position: fixed; 778 top: 20vh; 779 left: 50%; 780 width: 100%; 781 max-width: 350px; 782 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); 783 border-radius: 12px; 784 background-color: #fff; 785 color: #000; 786 padding: 20px; 787 text-align: center; 788 } 789 790 .modal-overlay.referral-modal-overlay { 791 position: fixed; 792 top: 0; 793 left: 0; 794 width: 100%; 795 height: 100%; 796 background-color: rgba(0, 0, 0, 0.7); 797 z-index: 2001; 798 display: block; 799 opacity: 1;
800 transition: opacity var(--duration-default) ease, visibility 0s; 801 } 802</style> 803 804<link rel="preload" href="https://store-themes.easystore.co/1325208/themes/308896/assets/section-header.css?t=1791095546" as="style" onload="this.onload=null;this.rel='stylesheet'"> 805<link rel="preload" href="https://store-themes.easystore.co/1325208/themes/308896/assets/component-list-menu.css?t=1791095546" as="style" onload="this.onload=null;this.rel='stylesheet'"> 806<link rel="preload" href="https://store-themes.easystore.co/1325208/themes/308896/assets/component-menu-drawer.css?t=1791095546" as="style" onload="this.onload=null;this.rel='stylesheet'"> 807<link rel="preload" href="https://store-themes.easystore.co/1325208/themes/308896/assets/component-cart-notification.css?v1.1?t=1791095546" as="style" onload="this.onload=null;this.rel='stylesheet'"> 808
809<script src="https://store-themes.easystore.co/1325208/themes/308896/assets/cart-notification.js?t=1791095546" defer="defer"></script>
vendor: 2 bytes, line 809
809
810<script src="https://store-themes.easystore.co/1325208/themes/308896/assets/details-modal.js?t=1791095546" defer="defer"></script>
810 811 812<svg xmlns="http://www.w3.org/2000/svg" class="hidden"> 813 <symbol id="icon-search" viewbox="0 0 18 19" fill="none"> 814 <path fill-rule="evenodd" clip-rule="evenodd" d="M11.03 11.68A5.784 5.784 0 112.85 3.5a5.784 5.784 0 018.18 8.18zm.26 1.12a6.78 6.78 0 11.72-.7l5.4 5.4a.5.5 0 11-.71.7l-5.41-5.4z" fill="currentColor"/> 815 </symbol> 816 817 <symbol id="icon-close" class="icon icon-close" fill="none" viewBox="0 0 18 17"> 818 <path d="M.865 15.978a.5.5 0 00.707.707l7.433-7.431 7.579 7.282a.501.501 0 00.846-.37.5.5 0 00-.153-.351L9.712 8.546l7.417-7.416a.5.5 0 10-.707-.708L8.991 7.853 1.413.573a.5.5 0 10-.693.72l7.563 7.268-7.418 7.417z" fill="currentColor"> 819 </symbol> 820</svg> 821 822 823<div id="easystore-section-header"> 824 <sticky-header class="header-wrapper header-wrapper--border-bottom"> 825 <header class="header header--middle-left page-width header--has-menu" itemscope itemtype="http://schema.org/Organization"> 826 <header-drawer data-breakpoint="tablet"> 827 <details class="menu-drawer-container menu-opening"> 828 <summary class="header__icon header__icon--menu header__icon--summary link link--text focus-inset" aria-label="Menu" role="button" aria-expanded="true" aria-controls="menu-drawer"> 829 <span> 830 831 832 833 <svg class="icon icon-hamburger " data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600"><path d="M32.5,65h535a25,25,0,0,0,0-50H32.5a25,25,0,0,0,0,50Z" fill="currentColor"/><path d="M567.5,275H32.5a25,25,0,0,0,0,50h535a25,25,0,0,0,0-50Z" fill="currentColor"/><path d="M567.5,535H32.5a25,25,0,0,0,0,50h535a25,25,0,0,0,0-50Z" fill="currentColor"/></svg> 834 835 836 837 838 839 840 <svg class="icon icon-close " data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600"><path d="M335.36,300,581.87,53.48a25,25,0,0,0-35.35-35.35L300,264.64,53.48,18.13A25,25,0,0,0,18.13,53.48L264.64,300,18.13,546.52a25,25,0,0,0,35.35,35.35L300,335.36,546.52,581.87a25,25,0,0,0,35.35-35.35Z" fill="currentColor"/></svg> 841 842 843 844 </span> 845 </summary> 846 <div id="menu-drawer" class="menu-drawer motion-reduce" tabindex="-1"> 847 <div class="menu-drawer__inner-container"> 848 <div class="menu-drawer__navigation-container"> 849 <nav class="menu-drawer__navigation"> 850 <ul class="menu-drawer__menu list-menu" role="list"> 851 852 853 854 <li> 855 <a href="/" class="menu-drawer__menu-item list-menu__item link link--text focus-inset"> 856 Home 857 </a> 858 </li> 859 860 861 862 863 <li> 864 <a href="/collections/10-10-2026" class="menu-drawer__menu-item list-menu__item link link--text focus-inset"> 865 伿 å ç åº â¨ 866 </a> 867 </li> 868 869 870 871 872 <li> 873 <a href="/collections/new-arrivals" class="menu-drawer__menu-item list-menu__item link link--text focus-inset"> 874 æ°å | New Arrivals! 875 </a> 876 </li> 877 878 879 880 881 882 <li> 883 <details> 884 <summary class="menu-drawer__menu-item list-menu__item link link--text focus-inset" role="button" aria-expanded="false" aria-controls="link-Bags"> 885 <a href="/collections/shop-all" class="link--text list-menu__item menu-drawer__menu-item"> 886 Shop All 887 </a> 888 889 890 891 <svg class="icon icon-arrow " data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 339.56"><path d="M31.06,196.67H504l-88.56,88.56a26.89,26.89,0,1,0,38,38L588,188.79a26.89,26.89,0,0,0,0-38L453.49,16.29a26.89,26.89,0,0,0-38,38L504,142.88H31.06a26.9,26.9,0,0,0,0,53.79Z" fill="currentColor"/></svg> 892 893 894 895 896 897 898 <svg aria-hidden="true" focusable="false" role="presentation" class="icon icon-caret " viewBox="0 0 10 6"> 899 <path fill-rule="evenodd" clip-rule="evenodd" d="M9.354.646a.5.5 0 00-.708 0L5 4.293 1.354.646a.5.5 0 00-.708.708l4 4a.5.5 0 00.708 0l4-4a.5.5 0 000-.708z" fill="currentColor"></path> 900 </svg> 901 902 903 904 </summary> 905 <div class="menu-drawer__submenu motion-reduce" tabindex="-1"> 906 <div class="menu-drawer__inner-submenu"> 907 <button class="menu-drawer__close-button link link--text focus-inset" aria-expanded="true"> 908 909 910 911 <svg class="icon icon-arrow " data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 339.56"><path d="M31.06,196.67H504l-88.56,88.56a26.89,26.89,0,1,0,38,38L588,188.79a26.89,26.89,0,0,0,0-38L453.49,16.29a26.89,26.89,0,0,0-38,38L504,142.88H31.06a26.9,26.9,0,0,0,0,53.79Z" fill="currentColor"/></svg> 912 913 914 915 Shop All 916 </button> 917 <ul class="menu-drawer__menu list-menu" role="list" tabindex="-1"> 918 919 920 921 <li> 922 <a href="/collections/tops" class="menu-drawer__menu-item link link--text list-menu__item focus-inset"> 923 Tops 924 </a> 925 </li> 926 927 928 929 930 <li> 931 <a href="/collections/bottoms" class="menu-drawer__menu-item link link--text list-menu__item focus-inset"> 932 Bottoms 933 </a> 934 </li> 935 936 937 938 939 <li> 940 <a href="/collections/dresses" class="menu-drawer__menu-item link link--text list-menu__item focus-inset"> 941 Dresses 942 </a> 943 </li> 944 945 946 947 948 <li> 949 <a href="/collections/jumpsuits" class="menu-drawer__menu-item link link--text list-menu__item focus-inset"> 950 Jumpsuits 951 </a> 952 </li> 953 954 955 956 957 <li> 958 <a href="/collections/shoes" class="menu-drawer__menu-item link link--text list-menu__item focus-inset"> 959 Shoes 960 </a> 961 </li> 962 963 964 965 966 <li> 967 <a href="/collections/accessories" class="menu-drawer__menu-item link link--text list-menu__item focus-inset"> 968 Accessories 969 </a> 970 </li> 971 972 973 974 </ul> 975 </div> 976 </div> 977 </details> 978 </li> 979 980 981 982 983 <li> 984 <a href="/collections/plus-size" class="menu-drawer__menu-item list-menu__item link link--text focus-inset"> 985 Plus Size 986 </a> 987 </li> 988 989 990 991 992 <li> 993 <a href="/collections/fine-label" class="menu-drawer__menu-item list-menu__item link link--text focus-inset"> 994 FINE LABEL 995 </a> 996 </li> 997 998 999 1000 1001 <li> 1002 <a href="/pages/store-locator" class="menu-drawer__menu-item list-menu__item link link--text focus-inset"> 1003 STORE LOCATOR 1004 </a> 1005 </li> 1006 1007 1008 </ul> 1009 </nav> 1010 1011 <div class="menu-drawer__utility-links"> 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 <a href="/account/login" class="menu-drawer__account link link--text focus-inset h5"> 1023 1024 1025 1026 <svg class="icon icon-account " data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600"><path d="M300,296.19c-70.7,0-137.11,28.74-187,80.93-49.59,51.87-76.9,120.77-76.9,194v25H563.89v-25c0-73.22-27.31-142.12-76.9-194C437.11,324.93,370.7,296.19,300,296.19ZM87.42,546.11C99.29,433.81,190.1,346.19,300,346.19s200.71,87.62,212.58,199.92Z" fill="currentColor"/><path d="M300,285.34c77.6,0,140.73-63.13,140.73-140.73S377.6,3.89,300,3.89,159.27,67,159.27,144.61,222.4,285.34,300,285.34Zm0-231.45a90.73,90.73,0,1,1-90.73,90.72A90.82,90.82,0,0,1,300,53.89Z" fill="currentColor"/></svg> 1027 1028 1029 1030 1031 Log in 1032 </a> 1033 1034 <a href="/account/register" class="menu-drawer__account link link--text focus-inset h5"> 1035 1036 1037 1038 1039 <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" role="presentation" class="icon icon-plus " fill="none" viewBox="0 0 10 10"> 1040 <path fill-rule="evenodd" clip-rule="evenodd" d="M1 4.51a.5.5 0 000 1h3.5l.01 3.5a.5.5 0 001-.01V5.5l3.5-.01a.5.5 0 00-.01-1H5.5L5.49.99a.5.5 0 00-1 .01v3.5l-3.5.01H1z" fill="currentColor"></path> 1041 </svg> 1042 1043 1044 1045 Create account 1046 </a> 1047 1048 1049 1050 </div> 1051 </div> 1052 </div> 1053 </div> 1054 </details> 1055 </header-drawer> 1056 1057 1058 <a href="/" class="header__heading-link link link--text focus-inset" itemprop="url"> 1059 <img src="https://cdn.store-assets.com/s/1325208/f/17866599.png" class="header__heading-logo" alt="FINE LABEL" loading="lazy" itemprop="logo"> 1060 </a> 1061 1062 1063 1064 <div class="header__search-input" tabindex="-1"> 1065 <form action="/search" method="get" role="search" class="search search-modal__form"> 1066 <div class="field"> 1067 <input class="search__input field__input" 1068 id="Search-In-Modal" 1069 type="search" 1070 name="q" 1071 value="" 1072 placeholder="Search" 1073 autocomplete="off" 1074 > 1075 <label class="field__label" for="Search-In-Modal">Search</label> 1076 <div class="dropdown" id="searchDropdown"></div> 1077 <input type="hidden" name="search_history" class="hidden_search_history"> 1078 <input type="hidden" name="options[prefix]" value="last"> 1079 <button class="search__button field__button" aria-label="Search"> 1080 <svg class="icon icon-search" aria-hidden="true" focusable="false" role="presentation"> 1081 <use href="#icon-search"> 1082 </svg> 1083 </button> 1084 </div> 1085 </form> 1086 </div> 1087 1088 1089 <nav class="header__inline-menu"> 1090 <ul class="list-menu list-menu--inline" role="list"> 1091 1092 1093 1094 <li> 1095 <a href="/" class="header__menu-item header__menu-item list-menu__item link link--text focus-inset"> 1096 Home 1097 </a> 1098 </li> 1099 1100 1101 1102 1103 <li> 1104 <a href="/collections/10-10-2026" class="header__menu-item header__menu-item list-menu__item link link--text focus-inset"> 1105 伿 å ç åº â¨ 1106 </a> 1107 </li> 1108 1109 1110 1111 1112 <li> 1113 <a href="/collections/new-arrivals" class="header__menu-item header__menu-item list-menu__item link link--text focus-inset"> 1114 æ°å | New Arrivals! 1115 </a> 1116 </li> 1117 1118 1119 1120 1121 1122 <li> 1123 <details-disclosure> 1124 <details> 1125 <summary class="header__menu-item list-menu__item link focus-inset"> 1126 <a href="/collections/shop-all" >Shop All</a> 1127 1128 1129 1130 <svg aria-hidden="true" focusable="false" role="presentation" class="icon icon-caret " viewBox="0 0 10 6"> 1131 <path fill-rule="evenodd" clip-rule="evenodd" d="M9.354.646a.5.5 0 00-.708 0L5 4.293 1.354.646a.5.5 0 00-.708.708l4 4a.5.5 0 00.708 0l4-4a.5.5 0 000-.708z" fill="currentColor"></path> 1132 </svg> 1133 1134 1135 1136 </summary> 1137 <ul class="header__submenu list-menu list-menu--disclosure caption-large motion-reduce" role="list" tabindex="-1"> 1138 1139 1140 1141 1142 1143 <li> 1144 <a href="/collections/tops" class="header__menu-item list-menu__item link link--text focus-inset caption-large"> 1145 Tops 1146 </a> 1147 </li> 1148 1149 1150 1151 1152 1153 <li> 1154 <a href="/collections/bottoms" class="header__menu-item list-menu__item link link--text focus-inset caption-large"> 1155 Bottoms 1156 </a> 1157 </li> 1158 1159 1160 1161 1162 1163 <li> 1164 <a href="/collections/dresses" class="head
1164er__menu-item list-menu__item link link--text focus-inset caption-large"> 1165 Dresses 1166 </a> 1167 </li> 1168 1169 1170 1171 1172 1173 <li> 1174 <a href="/collections/jumpsuits" class="header__menu-item list-menu__item link link--text focus-inset caption-large"> 1175 Jumpsuits 1176 </a> 1177 </li> 1178 1179 1180 1181 1182 1183 <li> 1184 <a href="/collections/shoes" class="header__menu-item list-menu__item link link--text focus-inset caption-large"> 1185 Shoes 1186 </a> 1187 </li> 1188 1189 1190 1191 1192 1193 <li> 1194 <a href="/collections/accessories" class="header__menu-item list-menu__item link link--text focus-inset caption-large"> 1195 Accessories 1196 </a> 1197 </li> 1198 1199 1200 </ul> 1201 </details> 1202 </details-disclosure> 1203 </li> 1204 1205 1206 1207 1208 <li> 1209 <a href="/collections/plus-size" class="header__menu-item header__menu-item list-menu__item link link--text focus-inset"> 1210 Plus Size 1211 </a> 1212 </li> 1213 1214 1215 1216 1217 <li> 1218 <a href="/collections/fine-label" class="header__menu-item header__menu-item list-menu__item link link--text focus-inset"> 1219 FINE LABEL 1220 </a> 1221 </li> 1222 1223 1224 1225 1226 <li> 1227 <a href="/pages/store-locator" class="header__menu-item header__menu-item list-menu__item link link--text focus-inset"> 1228 STORE LOCATOR 1229 </a> 1230 </li> 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 </ul> 1244 </nav> 1245 <div class="header__icons"> 1246 1247 1248 1249 <div class="header__icon header__icon--account link link--text focus-inset small-hide medium-hide" style="position: relative;"> 1250 <a href="/account/login" class="header__icon link link--text focus-inset p-2"> 1251 1252 1253 1254 <svg class="icon icon-account " data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600"><path d="M300,296.19c-70.7,0-137.11,28.74-187,80.93-49.59,51.87-76.9,120.77-76.9,194v25H563.89v-25c0-73.22-27.31-142.12-76.9-194C437.11,324.93,370.7,296.19,300,296.19ZM87.42,546.11C99.29,433.81,190.1,346.19,300,346.19s200.71,87.62,212.58,199.92Z" fill="currentColor"/><path d="M300,285.34c77.6,0,140.73-63.13,140.73-140.73S377.6,3.89,300,3.89,159.27,67,159.27,144.61,222.4,285.34,300,285.34Zm0-231.45a90.73,90.73,0,1,1-90.73,90.72A90.82,90.82,0,0,1,300,53.89Z" fill="currentColor"/></svg> 1255 1256 1257 1258 <span id="my-account" class="header__link-label">Login</span> 1259 </a> 1260 1261 <!-- Referral Notification Popup for Desktop --> 1262 <div id="referralNotification" class="referral-notification" style="display: none;"> 1263 <button class="close-btn" onclick="dismissReferralNotification()">×</button> 1264 <div style="text-align: center; margin-bottom: 16px;"> 1265 <div style="width: 100px; height: 100px; margin: 0 auto;"> 1266 <img src="https://resources.easystore.co/storefront/icons/channels/vector_referral_program.svg"> 1267 </div> 1268 </div> 1269 <p id="referralSignupMessage" style="text-align: center; font-size: 14px; color: #666; margin: 0 0 20px 0;"> 1270 Your friend gifted you credit! Sign up now to redeem. 1271 </p> 1272 <div style="text-align: center;"> 1273 <button class="btn" onclick="goToSignupPage()" style="width: 100%;"> 1274 Sign Up 1275 </button> 1276 </div> 1277 </div> 1278 </div> 1279 1280 1281 1282 <a href="/cart" class="header__icon link link--text focus-inset" id="cart-icon-bubble">
1283 <span class="header__icon--cart"> 1284 1285 1286 1287 <svg class="icon icon-cart-empty " data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496.56 600"><path d="M453.52,128.63a25,25,0,0,0-24.91-22.8H364.72a117.48,117.48,0,0,0-232.89,0H67.94A25,25,0,0,0,43,128.63L8.8,515.21a72.11,72.11,0,0,0,19.05,55.6,79,79,0,0,0,58.22,25.3H410.49a79,79,0,0,0,58.22-25.3,72.11,72.11,0,0,0,19.05-55.6ZM248.28,53.89a67.58,67.58,0,0,1,65.65,51.94H182.63A67.57,67.57,0,0,1,248.28,53.89ZM431.83,537.05a28.85,28.85,0,0,1-21.34,9.06H86.07a28.85,28.85,0,0,1-21.34-9.06,22.69,22.69,0,0,1-6.13-17.43L90.82,155.83h40v51.23a25,25,0,0,0,50,0V155.83h135v51.23a25,25,0,0,0,50,0V155.83h40L438,519.62A22.68,22.68,0,0,1,431.83,537.05Z" fill="currentColor"/></svg> 1288 1289 1290 1291 <div class="cart-count-bubble hidden"> 1292 <span aria-hidden="true" class="js-content-cart-count">0</span> 1293 </div> 1294 </span> 1295 <span class="header__link-label medium-hide small-hide"> 1296 Cart 1297 </span> 1298 </a> 1299 1300 1301 1302 <!-- Referral Notification Modal for Mobile --> 1303 <details-modal class="referral-modal" id="referralMobileModal"> 1304 <details> 1305 <summary class="referral-modal__toggle" aria-haspopup="dialog" aria-label="Open referral modal" style="display: none;"> 1306 </summary> 1307 <div class="modal-overlay referral-modal-overlay"></div> 1308 <div class="referral-modal__dialog" role="dialog" aria-modal="true" aria-label="Referral notification"> 1309 <div class="referral-modal__content"> 1310 <button type="button" class="modal__close-button" aria-label="Close" onclick="closeMobileReferralModal()" style="position: absolute; top: 16px; right: 16px; background: none; border: none; font-size: 24px; cursor: pointer;"> 1311 × 1312 </button> 1313 1314 <div style="margin-bottom: 24px;"> 1315 <div style="width: 120px; height: 120px; margin: 0 auto 20px;"> 1316 <img src="https://resources.easystore.co/storefront/icons/channels/vector_referral_program.svg" style="width: 100%; height: 100%;"> 1317 </div> 1318 </div> 1319 1320 <p id="referralSignupMessageMobile" style="font-size: 16px; color: #333; margin: 0 0 30px 0; line-height: 1.5;"> 1321 Your friend gifted you credit! Sign up now to redeem. 1322 </p> 1323 1324 <button type="button" class="btn" onclick="goToSignupPageFromMobile()" style="width: 100%;"> 1325 Sign Up 1326 </button> 1327 </div> 1328 </div> 1329 </details> 1330 </details-modal> 1331 1332 1333 1334 </div> 1335 </header> 1336 </sticky-header> 1337 1338<cart-notification> 1339 <div class="cart-notification-wrapper page-width color-background-1"> 1340 <div id="cart-notification" class="cart-notification focus-inset" aria-modal="true" aria-label="Added to cart" role="dialog" tabindex="-1"> 1341 <div class="cart-notification__header"> 1342 <h2 class="cart-notification__heading caption-large"> 1343 1344 1345 <svg class="icon icon-checkmark color-foreground-text " aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 9" fill="none"> 1346 <path fill-rule="evenodd" clip-rule="evenodd" d="M11.35.643a.5.5 0 01.006.707l-6.77 6.886a.5.5 0 01-.719-.006L.638 4.845a.5.5 0 11.724-.69l2.872 3.011 6.41-6.517a.5.5 0 01.707-.006h-.001z" fill="currentColor"></path> 1347 </svg> 1348 1349 1350 Added to cart</h2> 1351 <button type="button" class="cart-notification__close modal__close-button link link--text focus-inset" aria-label="accessibility.close"> 1352 1353 1354 1355 <svg class="icon icon-close " data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600"><path d="M335.36,300,581.87,53.48a25,25,0,0,0-35.35-35.35L300,264.64,53.48,18.13A25,25,0,0,0,18.13,53.48L264.64,300,18.13,546.52a25,25,0,0,0,35.35,35.35L300,335.36,546.52,581.87a25,25,0,0,0,35.35-35.35Z" fill="currentColor"/></svg> 1356 1357 1358 1359 </button> 1360 </div> 1361 <div id="cart-notification-product" class="cart-notification-product"></div> 1362 <div class="cart-notification__links"> 1363 <a href="/cart" id="cart-notification-button" class="button button--secondary button--full-width">View cart (<span class="js-content-cart-count">0</span>)</a> 1364 <form action="/cart" method="post" id="cart-notification-form"> 1365 <input type="hidden" name="_token" value="1ajnERQGYBXr92PAa8BfcMsXdRFvZvjf7BJ2cIbT"> 1366 <input type="hidden" name="current_currency" value=""> 1367 <button class="button button--primary button--full-width" name="checkout" value="true">Checkout</button> 1368 </form> 1369 <a href="/collections/all" class="link button-label">Continue shopping</a> 1370 </div> 1371 </div> 1372 </div> 1373</cart-notification> 1374<style> 1375 .cart-notification { 1376 display: none; 1377 } 1378</style>
1379<script> 1380document.getElementById('cart-notification-form').addEventListener('submit',(event)=>{ 1381 if(event.submitter) event.submitter.classList.add('loading'); 1382}) 1383 1384</script>
1384 1385 1386</div> 1387 1388
1389<script> 1390 1391 class StickyHeader extends HTMLElement { 1392 constructor() { 1393 super(); 1394 } 1395 1396 connectedCallback() { 1397 this.header = document.getElementById('easystore-section-header'); 1398 this.headerBounds = {}; 1399 this.currentScrollTop = 0; 1400 this.preventReveal = false; 1401 1402 this.onScrollHandler = this.onScroll.bind(this); 1403 this.hideHeaderOnScrollUp = () => this.preventReveal = true; 1404 1405 this.addEventListener('preventHeaderReveal', this.hideHeaderOnScrollUp); 1406 window.addEventListener('scroll', this.onScrollHandler, false); 1407 1408 this.createObserver(); 1409 } 1410 1411 disconnectedCallback() { 1412 this.removeEventListener('preventHeaderReveal', this.hideHeaderOnScrollUp); 1413 window.removeEventListener('scroll', this.onScrollHandler); 1414 } 1415 1416 createObserver() { 1417 let observer = new IntersectionObserver((entries, observer) => { 1418 this.headerBounds = entries[0].intersectionRect; 1419 observer.disconnect(); 1420 }); 1421 1422 observer.observe(this.header); 1423 } 1424 1425 onScroll() { 1426 const scrollTop = window.pageYOffset || document.documentElement.scrollTop; 1427 1428 if (scrollTop > this.currentScrollTop && scrollTop > this.headerBounds.bottom) { 1429 requestAnimationFrame(this.hide.bind(this)); 1430 } else if (scrollTop < this.currentScrollTop && scrollTop > this.headerBounds.bottom) { 1431 if (!this.preventReveal) { 1432 requestAnimationFrame(this.reveal.bind(this)); 1433 } else { 1434 window.clearTimeout(this.isScrolling); 1435 1436 this.isScrolling = setTimeout(() => { 1437 this.preventReveal = false; 1438 }, 66); 1439 1440 requestAnimationFrame(this.hide.bind(this)); 1441 } 1442 } else if (scrollTop <= this.headerBounds.top) { 1443 requestAnimationFrame(this.reset.bind(this)); 1444 } 1445 1446 1447 this.currentScrollTop = scrollTop; 1448 } 1449 1450 hide() { 1451 this.header.classList.add('easystore-section-header-hidden', 'easystore-section-header-sticky'); 1452 this.closeMenuDisclosure(); 1453 // this.closeSearchModal(); 1454 } 1455 1456 reveal() { 1457 this.header.classList.add('easystore-section-header-sticky', 'animate'); 1458 this.header.classList.remove('easystore-section-header-hidden'); 1459 } 1460 1461 reset() { 1462 this.header.classList.remove('easystore-section-header-hidden', 'easystore-section-header-sticky', 'animate'); 1463 } 1464 1465 closeMenuDisclosure() { 1466 this.disclosures = this.disclosures || this.header.querySelectorAll('details-disclosure'); 1467 this.disclosures.forEach(disclosure => disclosure.close()); 1468 } 1469 1470 // closeSearchModal() { 1471 // this.searchModal = this.searchModal || this.header.querySelector('details-modal'); 1472 // this.searchModal.close(false); 1473 // } 1474 } 1475 1476 customElements.define('sticky-header', StickyHeader); 1477 1478 1479 class DetailsDisclosure extends HTMLElement{ 1480 constructor() { 1481 super(); 1482 this.mainDetailsToggle = this.querySelector('details'); 1483 // this.mainDetailsToggle.addEventListener('focusout', this.onFocusOut.bind(this)); 1484 this.mainDetailsToggle.addEventListener('mouseover', this.open.bind(this)); 1485 this.mainDetailsToggle.addEventListener('mouseleave', this.close.bind(this)); 1486 } 1487 1488 onFocusOut() { 1489 setTimeout(() => { 1490 if (!this.contains(document.activeElement)) this.close(); 1491 }) 1492 } 1493 1494 open() { 1495 this.mainDetailsToggle.setAttribute('open',1) 1496 } 1497 1498 close() { 1499 this.mainDetailsToggle.removeAttribute('open') 1500 } 1501 } 1502 1503 customElements.define('details-disclosure', DetailsDisclosure); 1504 1505 function clearAll() { 1506 localStorage.removeItem('searchHistory'); 1507 1508 var customer = ""; 1509 if(customer) { 1510 // Your logic to clear all data or call an endpoint 1511 fetch('/account/search_histories', { 1512 method: 'DELETE', 1513 headers: { 1514 "Content-Type": "application/json", 1515 "X-Requested-With": "XMLHttpRequest" 1516 }, 1517 body: JSON.stringify({ 1518 _token: "1ajnERQGYBXr92PAa8BfcMsXdRFvZvjf7BJ2cIbT"
1519 }), 1520 }) 1521 .then(response => response.json()) 1522 } 1523 } 1524 // End - delete search history from local storage 1525 1526 // search history dropdown 1527 var searchInputs = document.querySelectorAll('.search__input'); 1528 1529 if(searchInputs.length > 0) { 1530 1531 searchInputs.forEach((searchInput)=> { 1532 1533 searchInput.addEventListener('focus', function() { 1534 var searchDropdown = searchInput.closest('form').querySelector('#searchDropdown'); 1535 searchDropdown.innerHTML = ''; // Clear existing items 1536 1537 // retrieve data from local storage 1538 var searchHistoryData = JSON.parse(localStorage.getItem('searchHistory')) || []; 1539 1540 // Convert JSON to string 1541 var search_history_json = JSON.stringify(searchHistoryData); 1542 1543 // Set the value of the hidden input 1544 searchInput.closest('form').querySelector(".hidden_search_history").value = search_history_json; 1545 1546 // create dropdown items 1547 var clearAllItem = document.createElement('div'); 1548 clearAllItem.className = 'dropdown-item clear-all'; 1549 clearAllItem.textContent = 'Clear'; 1550 clearAllItem.addEventListener('click', function() { 1551 clearAll(); 1552 searchDropdown.style.display = 'none'; 1553 searchInput.classList.remove('is-focus'); 1554 }); 1555 searchDropdown.appendChild(clearAllItem); 1556 1557 // create dropdown items 1558 searchHistoryData.forEach(function(item) { 1559 var dropdownItem = document.createElement('div'); 1560 dropdownItem.className = 'dropdown-item'; 1561 dropdownItem.textContent = item.term; 1562 dropdownItem.addEventListener('click', function() { 1563 searchInput.value = item.term; 1564 searchInput.closest('form').submit(); 1565 searchDropdown.style.display = 'none'; 1566 }); 1567 searchDropdown.appendChild(dropdownItem); 1568 }); 1569 1570 if(searchHistoryData.length > 0){ 1571 // display the dropdown 1572 searchInput.classList.add('is-focus'); 1573 document.body.classList.add('search-input-focus'); 1574 searchDropdown.style.display = 'block'; 1575 } 1576 }); 1577 1578 searchInput.addEventListener("focusout", (event) => { 1579 var searchDropdown = searchInput.closest('form').querySelector('#searchDropdown'); 1580 searchInput.classList.remove('is-focus'); 1581 searchDropdown.classList.add('transparent'); 1582 setTimeout(()=>{ 1583 searchDropdown.style.display = 'none'; 1584 searchDropdown.classList.remove('transparent'); 1585 document.body.classList.remove('search-input-focus'); 1586 }, 400); 1587 }); 1588 }) 1589 } 1590 // End - search history dropdown 1591 1592 // referral notification popup 1593 const referralMessageTemplate = 'Your friend gifted you __CURRENCY_CODE__ __CREDIT_AMOUNT__ credit! Sign up now to redeem.'; 1594 const shopCurrency = 'RM'; 1595 1596 function getCookie(name) { 1597 const value = `; ${document.cookie}`; 1598 const parts = value.split(`; ${name}=`); 1599 if (parts.length === 2) return parts.pop().split(';').shift(); 1600 return null; 1601 } 1602 1603 function removeCookie(name) { 1604 document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; 1605 } 1606 1607 function showReferralNotification() { 1608 const referralCode = getCookie('customer_referral_code'); 1609 1610 1611 const customerReferralCode = null; 1612 1613 1614 const activeReferralCode = referralCode || customerReferralCode; 1615 const referralData = JSON.parse(localStorage.getItem('referral_notification_data')) || {}; 1616 1617 if (referralData.timestamp && referralData.expirationDays) { 1618 const expirationTime = referralData.timestamp + (referralData.expirationDays * 24 * 60 * 60 * 1000); 1619 if (Date.now() > expirationTime) { 1620 localStorage.removeItem('referral_notification_data'); 1621 return; 1622 } 1623 } 1624 1625 if (referralData.dismissed) return; 1626 1627 if (activeReferralCode) { 1628 if (referralData.creditAmount) { 1629 displayReferralNotification(referralData.creditAmount); 1630 } else { 1631 fetchReferralCampaignData(activeReferralCode); 1632 } 1633 } 1634 } 1635 1636 function fetchReferralCampaignData(referralCode) { 1637 fetch(`/customer/referral_program/campaigns/${referralCode}`) 1638 .then(response => { 1639 if (!response.ok) { 1640 throw new Error('Network response was not ok'); 1641 } 1642 return response.json(); 1643 }) 1644 .then(data => { 1645 let refereeCreditAmount = null; 1646 if (data.data.campaign && data.data.campaign.referral_rules) { 1647 const refereeRule = data.data.campaign.referral_rules.find(rule => 1648 rule.target_type === 'referee' && 1649 rule.event_name === 'customer/create' && 1650 rule.entitlement && 1651 rule.entitlement.type === 'credit' 1652 ); 1653 1654 if (refereeRule) { 1655 refereeCreditAmount = refereeRule.entitlement.amount; 1656 } 1657 } 1658 1659 const referralData = JSON.parse(localStorage.getItem('referral_notification_data')) || {}; 1660 referralData.creditAmount = refereeCreditAmount; 1661 referralData.dismissed = referralData.dismissed || false; 1662 referralData.timestamp = Date.now(); 1663 referralData.expirationDays = 3; 1664 localStorage.setItem('referral_notification_data', JSON.stringify(referralData)); 1665 1666 displayReferralNotification(refereeCreditAmount); 1667 }) 1668 .catch(error => { 1669 removeCookie('customer_referral_code'); 1670 }); 1671 } 1672 1673 function displayReferralNotification(creditAmount = null) { 1674 const isMobile = innerWidth <= 749; 1675 1676 if (isMobile) { 1677 const mobileModal = document.querySelector('#referralMobileModal details'); 1678 const mobileMessageElement = document.getElementById('referralSignupMessageMobile'); 1679 1680 if (mobileModal && mobileMessageElement) { 1681 if (creditAmount) {
1682 const finalMessage = referralMessageTemplate 1683 .replace('__CREDIT_AMOUNT__', creditAmount) 1684 .replace('__CURRENCY_CODE__', shopCurrency); 1685 1686 mobileMessageElement.textContent = finalMessage; 1687 } 1688 1689 mobileModal.setAttribute('open', ''); 1690 } 1691 } else { 1692 const notification = document.getElementById('referralNotification'); 1693 1694 if (notification) { 1695 if (creditAmount) { 1696 const messageElement = document.getElementById('referralSignupMessage'); 1697 if (messageElement) { 1698 const finalMessage = referralMessageTemplate 1699 .replace('__CREDIT_AMOUNT__', creditAmount) 1700 .replace('__CURRENCY_CODE__', shopCurrency); 1701 1702 messageElement.textContent = finalMessage; 1703 } 1704 } 1705 1706 notification.style.display = 'block'; 1707 } 1708 } 1709 } 1710 1711 showReferralNotification(); 1712 1713 addEventListener('beforeunload', function() { 1714 const referralData = JSON.parse(localStorage.getItem('referral_notification_data')) || {}; 1715 1716 if (referralData.creditAmount !== undefined) { 1717 referralData.creditAmount = null; 1718 localStorage.setItem('referral_notification_data', JSON.stringify(referralData)); 1719 } 1720 }); 1721 1722 function closeReferralNotification() { 1723 const notification = document.getElementById('referralNotification'); 1724 if (notification) { 1725 notification.style.display = 'none'; 1726 } 1727 } 1728 1729 function dismissReferralNotification() { 1730 const referralData = JSON.parse(localStorage.getItem('referral_notification_data')) || {}; 1731 1732 referralData.dismissed = true; 1733 referralData.timestamp = referralData.timestamp || Date.now(); 1734 referralData.expirationDays = referralData.expirationDays || 3; 1735 1736 localStorage.setItem('referral_notification_data', JSON.stringify(referralData)); 1737 1738 closeReferralNotification(); 1739 } 1740 1741 function goToSignupPage() { 1742 location.href = '/account/register'; 1743 dismissReferralNotification(); 1744 } 1745 1746 function closeMobileReferralModal() { 1747 const mobileModal = document.querySelector('#referralMobileModal details'); 1748 if (mobileModal) { 1749 mobileModal.removeAttribute('open'); 1750 } 1751 1752 const referralData = JSON.parse(localStorage.getItem('referral_notification_data')) || {}; 1753 referralData.dismissed = true; 1754 referralData.timestamp = referralData.timestamp || Date.now(); 1755 referralData.expirationDays = referralData.expirationDays || 3; 1756 localStorage.setItem('referral_notification_data', JSON.stringify(referralData)); 1757 } 1758 1759 function goToSignupPageFromMobile() { 1760 location.href = '/account/register'; 1761 closeMobileReferralModal(); 1762 } 1763 // End - referral notification popup 1764</script>
1764 1765 1766 1767 1768 <main id="MainContent" class="content-for-layout focus-none" role="main" tabindex="-1"> 1769 1770 1771 1772 1773<section class="spaced-section page-width"> 1774 <div class="rte"> 1775 <link href='/assets/css/froala_style.min.css' rel='stylesheet' type='text/css'/><div class='fr-view'><style> 1776@media (min-width: 768px) { 1777 .fl-store-page { 1778 margin-top: -280px; 1779 } 1780} 1781</style> 1782 1783<div class="fl-store-page"> 1784<iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3985.2988313053997!2d101.9234359137365!3d2.7273868555824055!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x31cddd6890538af1%3A0xaa502e87cd1e9ad3!2sFine%20Label%20Sdn%20Bhd!5e0!3m2!1sen!2smy!4v1783157857083!5m2!1sen!2smy" width="100%" height="450" style="border:0;" allowfullscreen=""> 1785</iframe> 1786 1787 1788<h2>ð Visit Our Boutique</h2> 1789 1790<p><strong>Fine Label Sdn Bhd</strong> 1791<br> No.20-G, Jalan PKL 2, 1792<br> Pusat Komersial Lobak, 1793<br> 70200 Seremban, Negeri Sembilan, 1794<br> Malaysia. 1795</p> 1796 1797<p><strong>ð Opening Hours</strong> 1798<br> Mon, Thu & Sun: 10:00AM – 5:30PM 1799<br> Tue, Wed, Fri & Sat: 10:00AM – 7:00PM</p> 1800 1801<p><strong>ð± WhatsApp</strong> 1802<br> 1803<a href="https://wa.me/60105133557">+6010-5133557</a></p> 1804 1805<p> 1806<a href="https://www.google.com/maps/search/?api=1&query=Fine%20Label%20Sdn%20Bhd%20No.20-G%20Jalan%20PKL%202%20Pusat%20Komersial%20Lobak%2070200%20Seremban" target="_blank" style="display:inline-block;padding:12px 18px;background:#111;color:#fff;text-decoration:none;border-radius:6px;"> 1807Get Directions → 1808</a> 1809</p></div> 1810
1810<script type="text/javascript"> 1811 $("img[data-type=youtube]").click(function(){ 1812 var this_parent = $(this).closest("div.Grid-cell--wrapper"); 1813 var height = $(this_parent).height(); 1814 var youtube_player = document.createElement("iframe"); 1815 youtube_player.setAttribute("style", "width:100%; height:"+height+"px;"); 1816 youtube_player.setAttribute("allowfullscreen",""); 1817 youtube_player.setAttribute("src","https://www.youtube.com/embed/"+$(this).data("youtube-id")+"?autoplay=1&fs=1"); 1818 this_parent.html(youtube_player); 1819 }); 1820 </script>
1820</div> 1821 <link rel="preload" href="https://store-themes.easystore.co/1325208/themes/308896/assets/component-card.css?t=1791095546" as="style" onload="this.onload=null;this.rel='stylesheet'"> 1822<link rel="preload" href="https://store-themes.easystore.co/1325208/themes/308896/assets/component-price.css?t=1791095546" as="style" onload="this.onload=null;this.rel='stylesheet'"> 1823<link rel="preload" href="https://store-themes.easystore.co/1325208/themes/308896/assets/component-product-grid.css?t=1791095546" as="style" onload="this.onload=null;this.rel='stylesheet'"> 1824 1825 1826 1827 1828 </div> 1829</section> 1830 1831 1832 1833 </main> 1834 <style> 1835 .footer{ 1836 background-color: #FFFFFF; 1837 color: #000000; 1838 --color-foreground: 0,0,0; 1839 } 1840 .footer .link--text, 1841 .footer .list-menu__item--link, 1842 .footer .footer-block__details-content .list-menu__item--link{ 1843 color: #000000; 1844 } 1845 .footer-block__heading{ 1846 color: #000000; 1847 } 1848</style> 1849 1850<link rel="preload" href="https://store-themes.easystore.co/1325208/themes/308896/assets/section-footer.css?t=1791095546" as="style" onload="this.onload=null;this.rel='stylesheet'"> 1851 1852<footer class="footer color-background-1"> 1853 <div class="footer__content-top page-width"> 1854 <div class="grid grid--1-col grid--4-col-tablet "> 1855 1856 1857 1858 <div class="grid__item"> 1859 1860 <h2 class="footer-block__heading">Follow us</h2> 1861 <ul class="footer__list-social list-unstyled list-social footer-block__details-content" role="list"> 1862 1863 1864 <li class="list-social__item"> 1865 <a href="https://www.facebook.com/finelabel22" class="link link--text list-social__link"> 1866 1867 1868 1869 <svg aria-hidden="true" focusable="false" role="presentation" class="icon icon-facebook " 1870 viewBox="0 0 18 18"> 1871 <path fill="currentColor" 1872 d="M16.42.61c.27 0 .5.1.69.28.19.2.28.42.28.7v15.44c0 .27-.1.5-.28.69a.94.94 0 01-.7.28h-4.39v-6.7h2.25l.31-2.65h-2.56v-1.7c0-.4.1-.72.28-.93.18-.2.5-.32 1-.32h1.37V3.35c-.6-.06-1.27-.1-2.01-.1-1.01 0-1.83.3-2.45.9-.62.6-.93 1.44-.93 2.53v1.97H7.04v2.65h2.24V18H.98c-.28 0-.5-.1-.7-.28a.94.94 0 01-.28-.7V1.59c0-.27.1-.5.28-.69a.94.94 0 01.7-.28h15.44z"> 1873 </path> 1874 </svg> 1875 1876 1877 1878 </a> 1879 </li> 1880 1881 1882 <li class="list-social__item"> 1883 <a href="https://www.instagram.com/finelabel22" class="link link--text list-social__link"> 1884 1885 1886 1887 <svg aria-hidden="true" focusable="false" role="presentation" class="icon icon-instagram " 1888 viewBox="0 0 18 18"> 1889 <path fill="currentColor" 1890 d="M8.77 1.58c2.34 0 2.62.01 3.54.05.86.04 1.32.18 1.63.3.41.17.7.35 1.01.66.3.3.5.6.65 1 .12.32.27.78.3 1.64.05.92.06 1.2.06 3.54s-.01 2.62-.05 3.54a4.79 4.79 0 01-.3 1.63c-.17.41-.35.7-.66 1.01-.3.3-.6.5-1.01.66-.31.12-.77.26-1.63.3-.92.04-1.2.05-3.54.05s-2.62 0-3.55-.05a4.79 4.79 0 01-1.62-.3c-.42-.16-.7-.35-1.01-.66-.31-.3-.5-.6-.66-1a4.87 4.87 0 01-.3-1.64c-.04-.92-.05-1.2-.05-3.54s0-2.62.05-3.54c.04-.86.18-1.32.3-1.63.16-.41.35-.7.66-1.01.3-.3.6-.5 1-.65.32-.12.78-.27 1.63-.3.93-.05 1.2-.06 3.55-.06zm0-1.58C6.39 0 6.09.01 5.15.05c-.93.04-1.57.2-2.13.4-.57.23-1.06.54-1.55 1.02C1 1.96.7 2.45.46 3.02c-.22.56-.37 1.2-.4 2.13C0 6.1 0 6.4 0 8.77s.01 2.68.05 3.61c.04.94.2 1.57.4 2.13.23.58.54 1.07 1.02 1.56.49.48.98.78 1.55 1.01.56.22 1.2.37 2.13.4.94.05 1.24.06 3.62.06 2.39 0 2.68-.01 3.62-.05.93-.04 1.57-.2 2.13-.41a4.27 4.27 0 001.55-1.01c.49-.49.79-.98 1.01-1.56.22-.55.37-1.19.41-2.13.04-.93.05-1.23.05-3.61 0-2.39 0-2.68-.05-3.62a6.47 6.47 0 00-.4-2.13 4.27 4.27 0 00-1.02-1.55A4.35 4.35 0 0014.52.46a6.43 6.43 0 00-2.13-.41A69 69 0 008.77 0z"> 1891 </path> 1892 <path fill="currentColor" 1893 d="M8.8 4a4.5 4.5 0 100 9 4.5 4.5 0 000-9zm0 7.43a2.92 2.92 0 110-5.85 2.92 2.92 0 010 5.85zM13.43 5a1.05 1.05 0 100-2.1 1.05 1.05 0 000 2.1z"> 1894 </path> 1895 </svg> 1896 1897 1898 1899 </a> 1900 </li> 1901 1902 1903 <li class="list-social__item"> 1904 <a href="https://www.tiktok.com/finelabel22" class="link link--text list-social__link"> 1905 1906 1907 1908 <svg aria-hidden="true" focusable="false" role="presentation" class="icon icon-tiktok " width="16" 1909 height="18" fill="none" xmlns="http://www.w3.org/2000/svg"> 1910 <path 1911 d="M8.02 0H11s-.17 3.82 4.13 4.1v2.95s-2.3.14-4.13-1.26l.03 6.1a5.52 5.52 0 11-5.51-5.52h.77V9.4a2.5 2.5 0 101.76 2.4L8.02 0z" 1912 fill="currentColor"> 1913 </path> 1914 </svg> 1915 1916 1917 1918 </a> 1919 </li> 1920 1921 1922 <li class="list-social__item"> 1923 <a href="https://wa.me/60105133557" class="link link--text list-social__link"> 1924 1925 1926 1927 <svg class="icon icon-whatsapp " fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 750">
1927<path d="M703.5,195.64A371,371,0,0,0,572,56C523.89,26,471.51,8.4,415.21,2.51A370.48,370.48,0,0,0,316.5,5.42,364,364,0,0,0,215.23,37.47a369,369,0,0,0-132.68,108Q17.36,230.17,7,336.63A370.11,370.11,0,0,0,53.42,554.94a7.23,7.23,0,0,1,.61,4.9Q36.48,624.6,18.7,689.27C13.24,709.19,7.75,729.1,2.14,749.5c1.23-.26,1.93-.36,2.61-.55q95.75-25.11,191.54-50.2a8.43,8.43,0,0,1,5.19.61c12.79,5.8,25.28,12.34,38.29,17.59A370.28,370.28,0,0,0,666.09,604q69.06-86.36,80-196.47A370.36,370.36,0,0,0,743,311.86,361,361,0,0,0,703.5,195.64ZM684.64,388c-3.6,65.27-25.48,123.75-66.06,174.9-44.89,56.63-102.85,93.24-173.19,109.84a297.41,297.41,0,0,1-91.27,7A305.68,305.68,0,0,1,215.31,635a17.54,17.54,0,0,0-15.2-2.14c-35.5,9.58-71.1,18.74-106.66,28-.49.13-1,.21-2.14.45,1.72-6.37,3.29-12.22,4.89-18Q109,596.7,121.66,550.07a7.12,7.12,0,0,0-1-5.07c-12.37-19.32-23.31-39.25-31.6-60.64A309,309,0,0,1,70.4,410.63c-6.11-49.35-.93-97.44,16.1-144.15,57.86-160.1,234.55-243,394.65-185.11a308.31,308.31,0,0,1,120.2,79.18c34.9,37.19,59.32,80.31,72.81,129.53A304.75,304.75,0,0,1,684.64,388Z"/><path d="M556,454.42c-13.84-6.52-27.43-13.55-41.24-20.12-11.47-5.46-23-10.89-34.73-15.69-9.1-3.73-13.09-1.72-19.12,6.06-9.39,12.09-19.09,23.95-28.89,35.71-4.7,5.63-9.47,6.79-16.1,3.8-12.32-5.56-24.82-10.9-36.58-17.51-39.6-22.29-69.59-54.3-92.67-93.11-4.87-8.2-4.3-12.16,2.14-19.13,9.07-9.9,18.4-19.63,24.09-32.12a17.42,17.42,0,0,0,.47-14.67c-9.55-23-19-46-28.75-68.92a75.82,75.82,0,0,0-7-12.7c-2.56-3.82-6.6-5.49-11.28-5.36H255.78v-.56c-5.48.29-11,.11-16.39,1-8.66,1.37-15,6.75-20.95,12.94-25.46,26.48-34.58,57.89-28.66,93.83a145.64,145.64,0,0,0,20.75,53.75C220.48,377.5,231.32,393,242.65,408a435.09,435.09,0,0,0,70.53,73.19c31.48,26,67,44.54,105.56,57.58,16.1,5.44,32.08,11.08,49.36,11.42,11.52.22,23,.08,34.16-3.22a110.77,110.77,0,0,0,42.1-23.61c5.48-4.81,10.48-10.18,13.19-17a110.56,110.56,0,0,0,7.57-37.2C565.27,461.93,562.42,457.42,556,454.42Z"/></svg> 1928 1929 1930 1931 </a> 1932 </li> 1933 1934 1935 1936 1937 <li class="list-social__item"> 1938 <a href="https://www.youtube.com/@finelabel5830" class="link link--text list-social__link"> 1939 1940 1941 1942 <svg aria-hidden="true" focusable="false" role="presentation" class="icon icon-youtube " 1943 viewBox="0 0 100 70"> 1944 <path 1945 d="M98 11c2 7.7 2 24 2 24s0 16.3-2 24a12.5 12.5 0 01-9 9c-7.7 2-39 2-39 2s-31.3 0-39-2a12.5 12.5 0 01-9-9c-2-7.7-2-24-2-24s0-16.3 2-24c1.2-4.4 4.6-7.8 9-9 7.7-2 39-2 39-2s31.3 0 39 2c4.4 1.2 7.8 4.6 9 9zM40 50l26-15-26-15v30z" 1946 fill="currentColor"> 1947 </path> 1948 </svg> 1949 1950 1951 1952 </a> 1953 </li> 1954 1955 1956 </ul> 1957 1958 </div> 1959 1960 1961 1962 <div class="grid__item"> 1963 1964 <h2 class="footer-block__heading">We accept</h2> 1965 <div class="footer-block__details-content footer__payment"> 1966 <ul class="list list-payment" role="list"> 1967 1968 <li class="list-payment__item"> 1969 1970 1971 1972 <svg class="icon icon--full-color " viewBox="0 0 38 24" xmlns="http://www.w3.org/2000/svg" role="img" 1973 width="38" height="24" aria-labelledby="pi-visa"> 1974 <path opacity=".07" 1975 d="M35 0H3C1.3 0 0 1.3 0 3v18c0 1.7 1.4 3 3 3h32c1.7 0 3-1.3 3-3V3c0-1.7-1.4-3-3-3z"></path> 1976 <path fill="#fff" d="M35 1c1.1 0 2 .9 2 2v18c0 1.1-.9 2-2 2H3c-1.1 0-2-.9-2-2V3c0-1.1.9-2 2-2h32"> 1977 </path> 1978 <path 1979 d="M28.3 10.1H28c-.4 1-.7 1.5-1 3h1.9c-.3-1.5-.3-2.2-.6-3zm2.9 5.9h-1.7c-.1 0-.1 0-.2-.1l-.2-.9-.1-.2h-2.4c-.1 0-.2 0-.2.2l-.3.9c0 .1-.1.1-.1.1h-2.1l.2-.5L27 8.7c0-.5.3-.7.8-.7h1.5c.1 0 .2 0 .2.2l1.4 6.5c.1.4.2.7.2 1.1.1.1.1.1.1.2zm-13.4-.3l.4-1.8c.1 0 .2.1.2.1.7.3 1.4.5 2.1.4.2 0 .5-.1.7-.2.5-.2.5-.7.1-1.1-.2-.2-.5-.3-.8-.5-.4-.2-.8-.4-1.1-.7-1.2-1-.8-2.4-.1-3.1.6-.4.9-.8 1.7-.8 1.2 0 2.5 0 3.1.2h.1c-.1.6-.2 1.1-.4 1.7-.5-.2-1-.4-1.5-.4-.3 0-.6 0-.9.1-.2 0-.3.1-.4.2-.2.2-.2.5 0 .7l.5.4c.4.2.8.4 1.1.6.5.3 1 .8 1.1 1.4.2.9-.1 1.7-.9 2.3-.5.4-.7.6-1.4.6-1.4 0-2.5.1-3.4-.2-.1.2-.1.2-.2.1zm-3.5.3c.1-.7.1-.7.2-1 .5-2.2 1-4.5 1.4-6.7.
19791-.2.1-.3.3-.3H18c-.2 1.2-.4 2.1-.7 3.2-.3 1.5-.6 3-1 4.5 0 .2-.1.2-.3.2M5 8.2c0-.1.2-.2.3-.2h3.4c.5 0 .9.3 1 .8l.9 4.4c0 .1 0 .1.1.2 0-.1.1-.1.1-.1l2.1-5.1c-.1-.1 0-.2.1-.2h2.1c0 .1 0 .1-.1.2l-3.1 7.3c-.1.2-.1.3-.2.4-.1.1-.3 0-.5 0H9.7c-.1 0-.2 0-.2-.2L7.9 9.5c-.2-.2-.5-.5-.9-.6-.6-.3-1.7-.5-1.9-.5L5 8.2z" 1980 fill="#142688"></path> 1981 </svg> 1982 1983 1984 1985 </li> 1986 1987 1988 <li class="list-payment__item"> 1989 1990 1991 1992 <svg class="icon icon--full-color " viewBox="0 0 38 24" xmlns="http://www.w3.org/2000/svg" role="img" 1993 width="38" height="24" aria-labelledby="pi-master"> 1994 <path opacity=".07" 1995 d="M35 0H3C1.3 0 0 1.3 0 3v18c0 1.7 1.4 3 3 3h32c1.7 0 3-1.3 3-3V3c0-1.7-1.4-3-3-3z"></path> 1996 <path fill="#fff" d="M35 1c1.1 0 2 .9 2 2v18c0 1.1-.9 2-2 2H3c-1.1 0-2-.9-2-2V3c0-1.1.9-2 2-2h32"> 1997 </path> 1998 <circle fill="#EB001B" cx="15" cy="12" r="7"></circle> 1999 <circle fill="#F79E1B" cx="23" cy="12" r="7"></circle> 2000 <path fill="#FF5F00" 2001 d="M22 12c0-2.4-1.2-4.5-3-5.7-1.8 1.3-3 3.4-3 5.7s1.2 4.5 3 5.7c1.8-1.2 3-3.3 3-5.7z"></path> 2002 </svg> 2003 2004 2005 2006 </li> 2007 2008 2009 2010 2011 2012 2013 2014 <li class="list-payment__item"> 2015 <img src="https://s3.dualstack.ap-southeast-1.amazonaws.com/eesb.public/images/payment/payments_ipay88.svg" alt="payment_ipay88" loading="lazy" width="85px"> 2016 </li> 2017 2018 <li class="list-payment__item"> 2019 <img src="https://s3.dualstack.ap-southeast-1.amazonaws.com/eesb.public/images/payment/payments_tngpay.svg" alt="payment_tngpay" loading="lazy" width="85px"> 2020 </li> 2021 2022 2023 </div> 2024 2025 </div> 2026 2027 2028 2029 <div class="grid__item"> 2030 2031 <h2 class="footer-block__heading"> 2032 Quick links 2033 </h2> 2034 <ul class="footer-block__details-content list-unstyled"> 2035 2036 <li> 2037 <a href="/store-locator" class="link link--text list-menu__item list-menu__item--link"> 2038 Location 2039 </a> 2040 </li> 2041 2042 <li> 2043 <a href="/a/contact-form" class="link link--text list-menu__item list-menu__item--link"> 2044 Contact us 2045 </a> 2046 </li> 2047 2048 2049 </ul> 2050 2051 </div> 2052 2053 2054 2055 <div class="grid__item"> 2056 2057 <h2 class="footer-block__heading">Company Infos</h2> 2058 <div class="footer-block__details-content"> 2059 <p>Fine Label Sdn Bhd (202201022221) No.20-G, Jalan PKL 2, Pusat Komersir Lobak, 70200 Seremban, N.S.D.K.</p> 2060 </div> 2061 2062 </div> 2063 2064 </div> 2065 </div> 2066 <div class="footer__content-bottom"> 2067 <div class="footer__content-bottom-wrapper page-width"> 2068 <div class="footer__column footer__column--info"> 2069 2070 2071 2072 2073 2074 2075 2076 <div class="footer__copyright caption"> 2077 <div class="copyright__content">Copyright to FINE LABEL © 2026</div> 2078 </div> 2079 2080 <div class="footer__copyright caption"> 2081 2082 <div> 2083 2084 2085 2086 <a href="/legal/terms-of-service">Terms of Service</a> 2087 2088 2089 2090 2091 | <a href="/legal/privacy-policy">Privacy Policy</a> 2092 2093 2094 2095 2096 | <a href="/legal/refund-policy">Returns Policy</a> 2097 2098 2099 2100 2101 | <a href="/legal/shipping-delivery">Shipping & Delivery</a> 2102 2103 2104 2105 </div> 2106 2107 </div> 2108 2109 </div> 2110 </div> 2111 </div> 2112</footer> 2113 2114 2115 2116 2117 2118 2119
2119<script> 2120 EasyStore.Currencies.init([{"name":"Malaysian Ringgit","code":"MYR","rate":"1.0","format_prefix":"RM","format_suffix":null,"format_decimals":2,"thousand_separator":",","is_primary":true}], 1) 2121</script>
2121 2122 2123 2124 2125
2125<script> 2126 let page_template = 'page'; 2127 2128 if(document.querySelector('.currency-picker')) { 2129 EasyStore.Currencies.change(document.querySelector('.currency-picker').value) 2130 EasyStore.Currencies.convertAll() 2131 } 2132 2133 document.querySelectorAll('.currency-picker').forEach((el)=>{ 2134 el.addEventListener('change',(event)=>{ 2135 EasyStore.Currencies.change(event.target.value) 2136 2137 2138 if (page_template == 'cart') { 2139 const params = new URLSearchParams(window.location.search); 2140 params.set('delete_cache', 1); 2141 window.location.search = params; 2142 } else { 2143 window.location.reload(); 2144 } 2145 2146 return 2147 2148 2149 document.querySelectorAll('[name=currencies],[name=current_currency]').forEach((el)=>{ 2150 el.value = event.target.value 2151 }) 2152 2153 if(page_template == 'cart') location.reload(); 2154 }) 2155 }) 2156 </script>
2156 2157 2158 2159 2160 2161
2161<script> 2162 2163 window.variantStrings = { 2164 addToCart: `Add to Cart`, 2165 soldOut: `Sold Out`, 2166 unavailable: `Unavailable`, 2167 } 2168 2169 window.accessibilityStrings = { 2170 shareSuccess: `Link copied to clipboard`, 2171 } 2172 </script>
2172 2173 2174<!-- Snippet:global/body_end: Product Label --> 2175<link href='https://apps.easystore.co/assets/css/product-label/main.css?v2.3' rel='stylesheet' type='text/css' media='screen' />
2175<script src='https://apps.easystore.co/product-labels/productlabelv2.js?shop=miyacostore.easy.co' type='text/javascript'></script>
2175 2176<!-- /Snippet --> 2177 2178<!-- Snippet:global/body_end: Insert Code --> 2179 2180<!--Fine Label Browser Test - PageView-->
2181<script> 2182(function () { 2183 2184 if ( 2185 window.__FL_META_BROWSER_MATCH_INSTALLED__ 2186 ) { 2187 return; 2188 } 2189 2190 window.__FL_META_BROWSER_MATCH_INSTALLED__ = 2191 true; 2192 2193 2194 /* 2195 * ============================================================ 2196 * Fine Label Browser Tracking 2197 * 2198 * CURRENT PRODUCTION STATE 2199 * 2200 * Browser Match Capture: 2201 * LIVE 2202 * 2203 * Fine Label Meta Browser Events (fbq): 2204 * BUILT BUT DISABLED 2205 * 2206 * Fine Label Server ViewContent CAPI: 2207 * PRODUCTION READY / GLOBAL OFF 2208 * 2209 * Fine Label Server AddToCart CAPI: 2210 * PRODUCTION ON 2211 * /cart/add fetch fallback enabled 2212 * 2213 * Fine Label Server InitiateCheckout CAPI: 2214 * PRODUCTION ON 2215 * 2216 * Production CAPI Purchase: 2217 * Controlled separately server-side 2218 * 2219 * IMPORTANT: 2220 * Keep this FALSE while EasyStore 2221 * native Meta integration is ON. 2222 * ============================================================ 2223 */ 2224 2225 2226 var FL_META_BROWSER_EVENTS_ENABLED = false 2227 2228 2229 /* 2230 * Server-side ViewContent test sender. 2231 * 2232 * This does NOT call fbq() and does NOT 2233 * initialize the Fine Label main pixel. 2234 */ 2235 2236 var FL_META_SERVER_VIEWCONTENT_ENABLED = 2237 true; 2238 2239 2240 var FL_META_SERVER_TEST_SESSION_KEY = 2241 'FL_META_SERVER_TEST_V1'; 2242 2243 2244 /* 2245 * Server-side AddToCart test sender. 2246 * 2247 * Global switch stays FALSE. 2248 * Only a browser tab/session that explicitly 2249 * opts in can send AddToCart test events. 2250 */ 2251 2252var FL_META_SERVER_ADDTOCART_ENABLED = 2253 true; 2254 2255 2256 var FL_META_SERVER_ADDTOCART_TEST_SESSION_KEY = 2257 'FL_META_SERVER_ATC_TEST_V1'; 2258 2259 2260 /* 2261 * Server-side InitiateCheckout test sender. 2262 * 2263 * Global switch stays FALSE. 2264 * Only a browser tab/session that explicitly 2265 * opts in can send InitiateCheckout test events. 2266 */ 2267 2268 var FL_META_SERVER_INITIATECHECKOUT_ENABLED = 2269 true; 2270 2271 2272 var FL_META_SERVER_INITIATECHECKOUT_TEST_SESSION_KEY = 2273 'FL_META_SERVER_IC_TEST_V1'; 2274 2275 2276 /* 2277 * Fine Label MAIN Meta Pixel 2278 */ 2279 2280 var META_PIXEL_ID = 2281 '102469937081458'; 2282 2283 2284 /* 2285 * Fine Label Cloud Run 2286 * Browser Match endpoint 2287 */ 2288 2289 var ENDPOINT = 2290 'https://fine-label-ai-backend-72rwgpxz4a-as.a.run.app/v1/meta/browser-match'; 2291 2292 2293 /* 2294 * Fine Label Cloud Run 2295 * Server Browser Event endpoints 2296 * 2297 * TEST: 2298 * Sends with TEST45364 through the 2299 * test-tagged Cloud Run revision. 2300 * 2301 * PRODUCTION: 2302 * Sends without test_event_code through 2303 * the production-candidate tagged revision. 2304 * 2305 * All three production global switches 2306 * remain FALSE until final cutover. 2307 */ 2308 2309 var SERVER_BROWSER_EVENT_TEST_ENDPOINT = 2310 'https://meta-browser-event-test---fine-label-ai-backend-72rwgpxz4a-as.a.run.app/v1/meta/browser-event'; 2311 2312 2313 var SERVER_BROWSER_EVENT_PROD_ENDPOINT = 2314 'https://meta-browser-event-prod---fine-label-ai-backend-72rwgpxz4a-as.a.run.app/v1/meta/browser-event'; 2315 2316 2317 function getServerBrowserEventEndpoint( 2318 globalEnabled, 2319 sessionKey 2320 ) { 2321 2322 if (globalEnabled === true) { 2323 2324 return ( 2325 SERVER_BROWSER_EVENT_PROD_ENDPOINT 2326 ); 2327 2328 } 2329 2330 2331 try { 2332 2333 if ( 2334 sessionStorage.getItem( 2335 sessionKey 2336 ) === '1' 2337 ) { 2338 2339 return ( 2340 SERVER_BROWSER_EVENT_TEST_ENDPOINT 2341 ); 2342 2343 } 2344 2345 } catch (e) {} 2346 2347 2348 return ''; 2349 2350 } 2351 2352 2353 /* 2354 * ============================================================ 2355 * Session Storage Keys 2356 * ============================================================ 2357 */ 2358 2359 2360 var CART_TOKEN_KEY = 2361 'FL_META_CART_TOKEN_V1'; 2362 2363 2364 var ORDER_TOKEN_KEY = 2365 'FL_META_ORDER_TOKEN_V1'; 2366 2367 2368 var INITIATE_CHECKOUT_SENT_KEY = 2369 'FL_META_IC_SENT_CART_V1'; 2370 2371 2372 /* 2373 * ============================================================ 2374 * Runtime State 2375 * ============================================================ 2376 */ 2377 2378 2379 var lastAddToCart = 2380 null; 2381 2382 2383 var lastInitiateCheckout = 2384 null; 2385 2386 2387 var metaEventsSent = 2388 0; 2389 2390 2391 var lastMetaEvent = 2392 null; 2393 2394 2395 var pageViewSent = 2396 false; 2397 2398 2399 var lastServerViewContentKey = 2400 null; 2401 2402 2403 var serverViewContentRequests = 2404 0; 2405 2406 2407 var lastServerViewContent = 2408 null; 2409 2410 2411 var serverAddToCartRequests = 2412 0; 2413 2414 2415 var lastServerAddToCart = 2416 null; 2417 2418 2419 var lastAddToCartSource = 2420 null; 2421 2422 2423 var lastAddToCartFallback = 2424 null; 2425 2426
2427 var pendingCartAddRequests = 2428 []; 2429 2430 2431 var cartAddRequestSequence = 2432 0; 2433 2434 2435 var serverInitiateCheckoutRequests = 2436 0; 2437 2438 2439 var lastServerInitiateCheckout = 2440 null; 2441 2442 2443 /* 2444 * ============================================================ 2445 * Cookie Helper 2446 * ============================================================ 2447 */ 2448 2449 2450 function getCookie(name) { 2451 2452 var prefix = 2453 name + '='; 2454 2455 2456 var parts = 2457 document.cookie.split(';'); 2458 2459 2460 for ( 2461 var i = 0; 2462 i < parts.length; 2463 i += 1 2464 ) { 2465 2466 var item = 2467 parts[i].trim(); 2468 2469 2470 if ( 2471 item.indexOf(prefix) === 0 2472 ) { 2473 2474 return decodeURIComponent( 2475 item.substring( 2476 prefix.length 2477 ) 2478 ); 2479 2480 } 2481 2482 } 2483 2484 2485 return ''; 2486 2487 } 2488 2489 2490 /* 2491 * ============================================================ 2492 * Session Token Helpers 2493 * ============================================================ 2494 */ 2495 2496 2497 function saveTokens( 2498 cartToken, 2499 orderToken 2500 ) { 2501 2502 try { 2503 2504 if (cartToken) { 2505 2506 sessionStorage.setItem( 2507 CART_TOKEN_KEY, 2508 String(cartToken) 2509 ); 2510 2511 } 2512 2513 2514 if (orderToken) { 2515 2516 sessionStorage.setItem( 2517 ORDER_TOKEN_KEY, 2518 String(orderToken) 2519 ); 2520 2521 } 2522 2523 } catch (e) {} 2524 2525 } 2526 2527 2528 function getSavedCartToken() { 2529 2530 try { 2531 2532 return ( 2533 sessionStorage.getItem( 2534 CART_TOKEN_KEY 2535 ) || '' 2536 ); 2537 2538 } catch (e) { 2539 2540 return ''; 2541 2542 } 2543 2544 } 2545 2546 2547 function getSavedOrderToken() { 2548 2549 try { 2550 2551 return ( 2552 sessionStorage.getItem( 2553 ORDER_TOKEN_KEY 2554 ) || '' 2555 ); 2556 2557 } catch (e) { 2558 2559 return ''; 2560 2561 } 2562 2563 } 2564 2565 2566 /* 2567 * ============================================================ 2568 * Checkout URL Token 2569 * 2570 * /sf/checkout/<cart_token> 2571 * ============================================================ 2572 */ 2573 2574 2575 function checkoutTokenFromUrl() { 2576 2577 var match = 2578 location.pathname.match( 2579 /^\/sf\/checkout\/([^/?#]+)/ 2580 ); 2581 2582 2583 return match 2584 ? match[1] 2585 : ''; 2586 2587 } 2588 2589 2590 /* 2591 * ============================================================ 2592 * Browser Match Diagnostic 2593 * ============================================================ 2594 */ 2595 2596 2597 function setLastStatus(data) { 2598 2599 window 2600 .__FL_META_BROWSER_MATCH_LAST__ = 2601 data; 2602 2603 } 2604 2605 2606 /* 2607 * ============================================================ 2608 * Browser Match Capture 2609 * 2610 * THIS PART IS LIVE. 2611 * ============================================================ 2612 */ 2613 2614 2615 function capture( 2616 eventName, 2617 cartToken, 2618 orderToken 2619 ) { 2620 2621 cartToken = 2622 String( 2623 cartToken || '' 2624 ).trim(); 2625 2626 2627 orderToken = 2628 String( 2629 orderToken || '' 2630 ).trim(); 2631 2632 2633 if (!cartToken) { 2634 2635 setLastStatus({ 2636 2637 success: 2638 false, 2639 2640 skipped: 2641 true, 2642 2643 reason: 2644 'NO_CART_TOKEN', 2645 2646 event: 2647 eventName, 2648 2649 captured_at: 2650 new Date() 2651 .toISOString() 2652 2653 }); 2654 2655 2656 return; 2657 2658 } 2659 2660 2661 saveTokens( 2662 cartToken, 2663 orderToken 2664 ); 2665 2666 2667 var payload = { 2668 2669 cart_token: 2670 cartToken, 2671 2672 order_token: 2673 orderToken, 2674 2675 fbp: 2676 getCookie('_fbp'), 2677 2678 fbc: 2679 getCookie('_fbc'), 2680 2681 path: 2682 location.pathname, 2683 2684 event: 2685 eventName 2686 2687 }; 2688 2689 2690 fetch( 2691 ENDPOINT, 2692 { 2693 2694 method: 2695 'POST', 2696 2697 headers: { 2698 2699 'Content-Type': 2700 'application/json' 2701 2702 }, 2703 2704 body: 2705 JSON.stringify( 2706 payload 2707 ), 2708 2709 keepalive: 2710 true 2711 2712 } 2713 ) 2714 2715 .then( 2716 function (response) { 2717 2718 setLastStatus({ 2719 2720 success: 2721 response.ok, 2722 2723 status: 2724 response.status, 2725 2726 event: 2727 eventName, 2728 2729 has_fbp: 2730 Boolean( 2731 payload.fbp 2732 ), 2733 2734 has_fbc: 2735 Boolean( 2736 payload.fbc 2737 ), 2738 2739 captured_at: 2740 new Date() 2741 .toISOString() 2742 2743 }); 2744 2745 } 2746 ) 2747 2748 .catch( 2749 function () { 2750 2751 setLastStatus({ 2752 2753 success: 2754 false, 2755 2756 status: 2757 null, 2758 2759 event: 2760 eventName, 2761 2762 has_fbp: 2763 Boolean( 2764 payload.fbp 2765 ), 2766 2767 has_fbc: 2768 Boolean( 2769 payload.fbc 2770 ), 2771 2772 captured_at: 2773 new Date() 2774 .toISOString() 2775 2776 }); 2777 2778 } 2779 ); 2780 2781 } 2782 2783 2784 /* 2785 * ============================================================ 2786 * Meta Pixel Loader 2787 * 2788 * IMPORTANT: 2789 * 2790 * This runs ONLY when: 2791 * 2792 * FL_META_BROWSER_EVENTS_ENABLED = true 2793 * 2794 * It does NOT run now. 2795 * ============================================================ 2796 */ 2797 2798 2799 function ensureFineLabelMetaPixel() { 2800 2801 if ( 2802 !FL_META_BROWSER_EVENTS_ENABLED 2803 ) { 2804 2805 return false; 2806 2807 } 2808 2809 2810 if (!window.fbq) { 2811 2812 (function ( 2813 f, 2814 b, 2815 e, 2816 v, 2817 n, 2818 t, 2819 s 2820 ) { 2821 2822 if (f.fbq) { 2823 return; 2824 } 2825 2826 2827 n = 2828 f.fbq = 2829 function () { 2830 2831 n.callMethod 2832 ? n.callMethod 2833 .apply( 2834 n, 2835 arguments 2836 ) 2837 : n.queue 2838 .push( 2839 arguments 2840 ); 2841 2842 }; 2843 2844 2845 if (!f._fbq) { 2846 2847 f._fbq = 2848 n; 2849 2850 } 2851 2852 2853 n.push = 2854 n; 2855 2856 2857 n.loaded = 2858 true; 2859 2860 2861 n.version = 2862 '2.0'; 2863 2864 2865 n.queue = 2866 []; 2867 2868 2869 t = 2870 b.createElement( 2871 e 2872 ); 2873 2874 2875 t.async = 2876 true; 2877 2878 2879 t.src = 2880 v; 2881 2882 2883 s = 2884 b.getElementsByTagName( 2885 e 2886 )[0]; 2887 2888 2889 s.parentNode.insertBefore( 2890 t, 2891 s 2892 ); 2893 2894 })( 2895 window, 2896 document, 2897 'script', 2898 'https://connect.facebook.net/en_US/fbevents.js' 2899 ); 2900 2901 } 2902 2903 2904 /* 2905 * Fine Label controlled init. 2906 * 2907 * At final cutover: 2908 * 2909 * EasyStore Meta integration 2910 * must already be OFF. 2911 */ 2912 2913 if ( 2914 !window 2915 .__FL_META_MAIN_PIXEL_INITIALIZED__ 2916 ) { 2917 2918 window.fbq( 2919 'init', 2920 META_PIXEL_ID 2921 ); 2922 2923 2924 window 2925 .__FL_META_MAIN_PIXEL_INITIALIZED__ = 2926 true; 2927 2928 } 2929 2930 2931 return ( 2932 typeof window.fbq === 2933 'function' 2934 ); 2935 2936 } 2937 2938 2939 /* 2940 * ============================================================ 2941 * Fine Label Meta Sender 2942 * 2943 * Uses trackSingle so Fine Label 2944 * explicitly targets MAIN Pixel only. 2945 * 2946 * CURRENTLY DISABLED. 2947 * ============================================================ 2948 */ 2949 2950 2951 function sendFineLabelMetaEvent( 2952 eventName, 2953 params 2954 ) { 2955 2956 if ( 2957 !FL_META_BROWSER_EVENTS_ENABLED 2958 ) { 2959 2960 return false; 2961 2962 } 2963 2964 2965 if ( 2966 !ensureFineLabelMetaPixel() 2967 ) { 2968 2969 return false; 2970 2971 } 2972 2973 2974 try { 2975 2976 window.fbq( 2977 'trackSingle', 2978 META_PIXEL_ID, 2979 eventName, 2980 params || {} 2981 ); 2982 2983 2984 metaEventsSent += 2985 1; 2986 2987 2988 lastMetaEvent = { 2989 2990 event_name: 2991 eventName, 2992 2993 sent_at: 2994 new Date() 2995 .toISOString() 2996 2997 }; 2998 2999 3000 return true; 3001 3002 } catch (e) { 3003 3004 lastMetaEvent = { 3005 3006 event_name: 3007 eventName, 3008 3009 sent_at: 3010 null, 3011 3012 error: 3013 true 3014 3015 }; 3016 3017 3018 return false; 3019 3020 } 3021 3022 } 3023 3024 3025 /* 3026 * ============================================================ 3027 * Fine Label Server ViewContent Sender 3028 * 3029 * TEST OR PRODUCTION MODE. 3030 * 3031 * Sends directly to Cloud Run. 3032 * It does NOT call fbq(). 3033 * It does NOT initialize Pixel 1458. 3034 * 3035 * Consecutive duplicate products/viewed 3036 * events for the same product + path are 3037 * suppressed in this page runtime. 3038 * ============================================================ 3039 */ 3040 3041 3042 function getFineLabelServerViewContentEndpoint() { 3043 3044 return getServerBrowserEventEndpoint( 3045 FL_META_SERVER_VIEWCONTENT_ENABLED, 3046 FL_META_SERVER_TEST_SESSION_KEY 3047 ); 3048 3049 } 3050 3051 3052 function isFineLabelServerViewContentEnabled() { 3053 3054 return Boolean( 3055 getFineLabelServerViewContentEndpoint() 3056 ); 3057 3058 } 3059 3060 3061 function sendFineLabelServerViewContent( 3062 built 3063 ) { 3064 3065 var endpoint = 3066 getFineLabelServerViewContentEndpoint(); 3067 3068 3069 if ( 3070 !endpoint || 3071 !built || 3072 !Array.isArray( 3073 built.content_ids 3074 ) || 3075 !built.content_ids.length 3076 ) { 3077 3078 return false; 3079 3080 } 3081 3082 3083 var eventKey = 3084 location.pathname + 3085 '|' + 3086 built.content_ids.join(','); 3087 3088 3089 if ( 3090 lastServerViewContentKey === 3091 eventKey 3092 ) { 3093 3094 lastServerViewContent = { 3095 3096 event_name: 3097 'ViewContent', 3098 3099 skipped: 3100 true, 3101 3102 reason: 3103 'DUPLICATE_SAME_PAGE_PRODUCT', 3104 3105 request_count: 3106 serverViewContentRequests, 3107 3108 has_fbp: 3109 Boolean( 3110 getCookie('_fbp') 3111 ), 3112 3113 has_fbc: 3114 Boolean( 3115 getCookie('_fbc') 3116 ), 3117 3118 updated_at: 3119 new Date() 3120 .toISOString() 3121 3122 }; 3123 3124 3125 return false; 3126 3127 } 3128 3129 3130 /* 3131 * Mark before fetch so a duplicate 3132 * EasyStore event cannot race a second 3133 * request for the same product page. 3134 */ 3135 lastServerViewContentKey = 3136 eventKey; 3137 3138 3139 var payload = { 3140 3141 event_name: 3142 'ViewContent', 3143 3144 content_ids: 3145 built.content_ids, 3146 3147 content_type: 3148 built.content_type, 3149 3150 value: 3151 built.value, 3152 3153 currency: 3154 built.currency, 3155 3156 fbp: 3157 getCookie('_fbp'), 3158 3159 fbc: 3160 getCookie('_fbc'), 3161 3162 event_source_url: 3163 location.href 3164 3165 }; 3166 3167 3168 serverViewContentRequests += 3169 1; 3170 3171 3172 lastServerViewContent = { 3173 3174 event_name: 3175 'ViewContent', 3176 3177 success: 3178 null, 3179 3180 status: 3181 null, 3182 3183 skipped: 3184 false, 3185 3186 request_count: 3187 serverViewContentRequests, 3188 3189 has_fbp: 3190 Boolean( 3191 payload.fbp 3192 ), 3193 3194 has_fbc: 3195 Boolean( 3196 payload.fbc 3197 ), 3198 3199 requested_at: 3200 new Date() 3201 .toISOString() 3202 3203 }; 3204 3205 3206 fetch( 3207 endpoint, 3208 { 3209 3210 method: 3211 'POST', 3212 3213 headers: { 3214 3215 'Content-Type': 3216 'application/json' 3217 3218 }, 3219 3220 body: 3221 JSON.stringify( 3222 payload 3223 ), 3224 3225 keepalive: 3226 true 3227 3228 } 3229 ) 3230 3231 .then( 3232 function (response) { 3233 3234 lastServerViewContent = { 3235 3236 event_name: 3237 'ViewContent', 3238 3239 success: 3240 response.ok, 3241 3242 status: 3243 response.status, 3244 3245 skipped: 3246 false, 3247 3248 request_count: 3249 serverViewContentRequests, 3250 3251 has_fbp: 3252 Boolean( 3253 payload.fbp 3254 ), 3255 3256 has_fbc: 3257 Boolean( 3258 payload.fbc 3259 ), 3260 3261 completed_at: 3262 new Date() 3263 .toISOString() 3264 3265 }; 3266 3267 } 3268 ) 3269 3270 .catch( 3271 function () { 3272 3273 lastServerViewContent = { 3274 3275 event_name: 3276 'ViewContent', 3277 3278 success: 3279 false, 3280 3281 status: 3282 null, 3283 3284 skipped: 3285 false, 3286 3287 request_count: 3288 serverViewContentRequests, 3289 3290 has_fbp: 3291 Boolean( 3292 payload.fbp 3293 ), 3294 3295 has_fbc: 3296 Boolean( 3297 payload.fbc 3298 ), 3299 3300 completed_at: 3301 new Date() 3302 .toISOString() 3303 3304 }; 3305 3306 } 3307 ); 3308 3309 3310 return true; 3311 3312 } 3313 3314 3315 /* 3316 * ============================================================ 3317 * Fine Label Server AddToCart Sender 3318 * 3319 * TEST MODE ONLY. 3320 * 3321 * Sends directly to Cloud Run. 3322 * It does NOT call fbq(). 3323 * It does NOT initialize Pixel 1458. 3324 * 3325 * Each EasyStore carts/item_added event 3326 * is treated as one intentional AddToCart. 3327 * ============================================================ 3328 */ 3329 3330 3331 function getFineLabelServerAddToCartEndpoint() { 3332 3333 return getServerBrowserEventEndpoint( 3334 FL_META_SERVER_ADDTOCART_ENABLED, 3335 FL_META_SERVER_ADDTOCART_TEST_SESSION_KEY 3336 ); 3337 3338 } 3339 3340 3341 function isFineLabelServerAddToCartEnabled() { 3342 3343 return Boolean( 3344 getFineLabelServerAddToCartEndpoint() 3345 ); 3346 3347 } 3348 3349 3350 function sendFineLabelServerAddToCart( 3351 built 3352 ) { 3353 3354 var endpoint = 3355 getFineLabelServerAddToCartEndpoint(); 3356 3357 3358 if ( 3359 !endpoint || 3360 !built || 3361 !Array.isArray( 3362 built.content_ids 3363 ) || 3364 !built.content_ids.length 3365 ) { 3366 3367 return false; 3368 3369 } 3370 3371 3372 var payload = { 3373 3374 event_name: 3375 'AddToCart', 3376 3377 content_ids: 3378 built.content_ids, 3379 3380 content_type: 3381 built.content_type, 3382 3383 value: 3384 built.value, 3385 3386 currency: 3387 built.currency, 3388 3389 fbp: 3390 getCookie('_fbp'), 3391 3392 fbc: 3393 getCookie('_fbc'), 3394 3395 event_source_url: 3396 location.href 3397 3398 }; 3399 3400 3401 serverAddToCartRequests += 3402 1; 3403 3404 3405 lastServerAddToCart = { 3406 3407 event_name: 3408 'AddToCart', 3409 3410 success: 3411 null, 3412 3413 status: 3414 null, 3415 3416 request_count: 3417 serverAddToCartRequests, 3418 3419 has_fbp: 3420 Boolean( 3421 payload.fbp 3422 ), 3423 3424 has_fbc: 3425 Boolean( 3426 payload.fbc 3427 ), 3428 3429 requested_at: 3430 new Date() 3431 .toISOString() 3432 3433 }; 3434 3435 3436 fetch( 3437 endpoint, 3438 { 3439 3440 method: 3441 'POST', 3442 3443 headers: { 3444 3445 'Content-Type': 3446 'application/json' 3447 3448 }, 3449 3450 body: 3451 JSON.stringify( 3452 payload 3453 ), 3454 3455 keepalive: 3456 true 3457 3458 } 3459 ) 3460 3461 .then( 3462 function (response) { 3463 3464 lastServerAddToCart = { 3465 3466 event_name: 3467 'AddToCart', 3468 3469 success: 3470 response.ok, 3471 3472 status: 3473 response.status, 3474 3475 request_count: 3476 serverAddToCartRequests, 3477 3478 has_fbp: 3479 Boolean( 3480 payload.fbp 3481 ), 3482 3483 has_fbc: 3484 Boolean( 3485 payload.fbc 3486 ), 3487 3488 completed_at: 3489 new Date() 3490 .toISOString() 3491 3492 }; 3493 3494 } 3495 ) 3496 3497 .catch( 3498 function () { 3499 3500 lastServerAddToCart = { 3501 3502 event_name: 3503 'AddToCart', 3504 3505 success: 3506 false, 3507 3508 status: 3509 null, 3510 3511 request_count: 3512 serverAddToCartRequests, 3513 3514 has_fbp: 3515 Boolean( 3516 payload.fbp 3517 ), 3518 3519 has_fbc: 3520 Boolean( 3521 payload.fbc 3522 ), 3523 3524 completed_at: 3525 new Date() 3526 .toISOString() 3527 3528 }; 3529 3530 } 3531 ); 3532 3533 3534 return true; 3535 3536 } 3537 3538 3539 /* 3540 * ============================================================ 3541 * Fine Label Server InitiateCheckout Sender 3542 * 3543 * TEST MODE ONLY
3543. 3544 * 3545 * Sends directly to Cloud Run. 3546 * It does NOT call fbq(). 3547 * It does NOT initialize Pixel 1458. 3548 * ============================================================ 3549 */ 3550 3551 3552 function getFineLabelServerInitiateCheckoutEndpoint() { 3553 3554 return getServerBrowserEventEndpoint( 3555 FL_META_SERVER_INITIATECHECKOUT_ENABLED, 3556 FL_META_SERVER_INITIATECHECKOUT_TEST_SESSION_KEY 3557 ); 3558 3559 } 3560 3561 3562 function isFineLabelServerInitiateCheckoutEnabled() { 3563 3564 return Boolean( 3565 getFineLabelServerInitiateCheckoutEndpoint() 3566 ); 3567 3568 } 3569 3570 3571 function sendFineLabelServerInitiateCheckout( 3572 built 3573 ) { 3574 3575 var endpoint = 3576 getFineLabelServerInitiateCheckoutEndpoint(); 3577 3578 3579 if ( 3580 !endpoint || 3581 !built || 3582 !Array.isArray( 3583 built.content_ids 3584 ) || 3585 !built.content_ids.length || 3586 !Number.isFinite( 3587 Number( 3588 built.num_items 3589 ) 3590 ) || 3591 Number( 3592 built.num_items 3593 ) < 1 3594 ) { 3595 3596 return false; 3597 3598 } 3599 3600 3601 var payload = { 3602 3603 event_name: 3604 'InitiateCheckout', 3605 3606 content_ids: 3607 built.content_ids, 3608 3609 content_type: 3610 built.content_type, 3611 3612 num_items: 3613 built.num_items, 3614 3615 value: 3616 built.value, 3617 3618 currency: 3619 built.currency, 3620 3621 fbp: 3622 getCookie('_fbp'), 3623 3624 fbc: 3625 getCookie('_fbc'), 3626 3627 event_source_url: 3628 location.href 3629 3630 }; 3631 3632 3633 serverInitiateCheckoutRequests += 3634 1; 3635 3636 3637 lastServerInitiateCheckout = { 3638 3639 event_name: 3640 'InitiateCheckout', 3641 3642 success: 3643 null, 3644 3645 status: 3646 null, 3647 3648 request_count: 3649 serverInitiateCheckoutRequests, 3650 3651 has_fbp: 3652 Boolean( 3653 payload.fbp 3654 ), 3655 3656 has_fbc: 3657 Boolean( 3658 payload.fbc 3659 ), 3660 3661 requested_at: 3662 new Date() 3663 .toISOString() 3664 3665 }; 3666 3667 3668 fetch( 3669 endpoint, 3670 { 3671 3672 method: 3673 'POST', 3674 3675 headers: { 3676 3677 'Content-Type': 3678 'application/json' 3679 3680 }, 3681 3682 body: 3683 JSON.stringify( 3684 payload 3685 ), 3686 3687 keepalive: 3688 true 3689 3690 } 3691 ) 3692 3693 .then( 3694 function (response) { 3695 3696 lastServerInitiateCheckout = { 3697 3698 event_name: 3699 'InitiateCheckout', 3700 3701 success: 3702 response.ok, 3703 3704 status: 3705 response.status, 3706 3707 request_count: 3708 serverInitiateCheckoutRequests, 3709 3710 has_fbp: 3711 Boolean( 3712 payload.fbp 3713 ), 3714 3715 has_fbc: 3716 Boolean( 3717 payload.fbc 3718 ), 3719 3720 completed_at: 3721 new Date() 3722 .toISOString() 3723 3724 }; 3725 3726 } 3727 ) 3728 3729 .catch( 3730 function () { 3731 3732 lastServerInitiateCheckout = { 3733 3734 event_name: 3735 'InitiateCheckout', 3736 3737 success: 3738 false, 3739 3740 status: 3741 null, 3742 3743 request_count: 3744 serverInitiateCheckoutRequests, 3745 3746 has_fbp: 3747 Boolean( 3748 payload.fbp 3749 ), 3750 3751 has_fbc: 3752 Boolean( 3753 payload.fbc 3754 ), 3755 3756 completed_at: 3757 new Date() 3758 .toISOString() 3759 3760 }; 3761 3762 } 3763 ); 3764 3765 3766 return true; 3767 3768 } 3769 3770 3771 /* 3772 * ============================================================ 3773 * Product Viewed State 3774 * ============================================================ 3775 */ 3776 3777 3778 function saveProductViewed(data) { 3779 3780 data = 3781 data || {}; 3782 3783 3784 var product = 3785 data.product || {}; 3786 3787 3788 window.__FL_PRODUCT_VIEWED__ = { 3789 3790 product_id: 3791 product.id || 3792 product.product_id || 3793 null, 3794 3795 title: 3796 product.title || 3797 product.name || 3798 null, 3799 3800 handle: 3801 product.handle || 3802 null, 3803 3804 price: 3805 product.price || 3806 null, 3807 3808 compare_at_price: 3809 product.compare_at_price || 3810 null, 3811 3812 currency: 3813 data.store && 3814 data.store.currency 3815 ? ( 3816 data.store 3817 .currency.code || 3818 data.store 3819 .currency 3820 ) 3821 : null, 3822 3823 available: 3824 product.available, 3825 3826 variants: 3827 Array.isArray( 3828 product.variants 3829 ) 3830 ? product.variants.map( 3831 function ( 3832 variant 3833 ) { 3834 3835 return { 3836 3837 id: 3838 variant.id || 3839 null, 3840 3841 sku: 3842 variant.sku || 3843 null, 3844 3845 price: 3846 variant.price || 3847 null, 3848 3849 available: 3850 variant.available 3851 3852 }; 3853 3854 } 3855 ) 3856 : [] 3857 3858 }; 3859 3860 } 3861 3862 3863 /* 3864 * ============================================================ 3865 * PageView Builder 3866 * ============================================================ 3867 */ 3868 3869 3870 function buildPageView() { 3871 3872 return { 3873 3874 event_name: 3875 'PageView', 3876 3877 path: 3878 location.pathname 3879 3880 }; 3881 3882 } 3883 3884 3885 /* 3886 * ============================================================ 3887 * ViewContent Builder 3888 * ============================================================ 3889 */ 3890 3891 3892 function buildViewContent() { 3893 3894 var product = 3895 window 3896 .__FL_PRODUCT_VIEWED__ || 3897 null; 3898 3899 3900 if ( 3901 !product || 3902 !product.product_id 3903 ) { 3904 3905 return null; 3906 3907 } 3908 3909 3910 return { 3911 3912 event_name: 3913 'ViewContent', 3914 3915 content_ids: [ 3916 String( 3917 product.product_id 3918 ) 3919 ], 3920 3921 content_type: 3922 'product', 3923 3924 value: 3925 Number( 3926 product.price || 0 3927 ), 3928 3929 currency: 3930 'MYR' 3931 3932 }; 3933 3934 } 3935 3936 3937 /* 3938 * ============================================================ 3939 * AddToCart Builder 3940 * 3941 * Uses latest_items ONLY. 3942 * ============================================================ 3943 */ 3944 3945 3946 function buildAddToCart( 3947 cart 3948 ) { 3949 3950 cart = 3951 cart || {}; 3952 3953 3954 var latestItems = 3955 Array.isArray( 3956 cart.latest_items 3957 ) 3958 ? cart.latest_items 3959 : []; 3960 3961 3962 if (!latestItems.length) { 3963 3964 return null; 3965 3966 } 3967 3968 3969 var contentIds = 3970 latestItems 3971 .map( 3972 function (item) { 3973 3974 return item.product_id 3975 ? String( 3976 item.product_id 3977 ) 3978 : null; 3979 3980 } 3981 ) 3982 .filter(Boolean); 3983 3984 3985 if (!contentIds.length) { 3986 3987 return null; 3988 3989 } 3990 3991 3992 var value = 3993 latestItems.reduce( 3994 function ( 3995 total, 3996 item 3997 ) { 3998 3999 return ( 4000 total + 4001 ( 4002 Number( 4003 item.price || 0 4004 ) * 4005 Number( 4006 item.quantity || 1 4007 ) 4008 ) 4009 ); 4010 4011 }, 4012 0 4013 ); 4014 4015 4016 return { 4017 4018 event_name: 4019 'AddToCart', 4020 4021 content_ids: 4022 contentIds, 4023 4024 content_type: 4025 'product', 4026 4027 value: 4028 value, 4029 4030 currency: 4031 'MYR' 4032 4033 }; 4034 4035 } 4036 4037 4038 4039 /* 4040 * ============================================================ 4041 * AddToCart Fetch Fallback 4042 * 4043 * EasyStore currently performs: 4044 * 4045 * POST /cart/add?retrieve=true 4046 * 4047 * but carts/item_added is not consistently 4048 * emitted on the storefront. 4049 * 4050 * This fallback observes that successful 4051 * same-origin cart request without changing 4052 * its request or response. 4053 * 4054 * Request body gives: 4055 * - variant id 4056 * - quantity just added 4057 * 4058 * Response cart gives: 4059 * - product id 4060 * - variant price 4061 * - currency 4062 * - cart/order tokens for Browser Match 4063 * 4064 * If EasyStore does emit carts/item_added 4065 * for the same request, the pending request 4066 * is marked handled so the fallback does not 4067 * send a duplicate Server AddToCart. 4068 * ============================================================ 4069 */ 4070 4071 4072 function parseCartAddBody( 4073 body 4074 ) { 4075 4076 if (!body) { 4077 4078 return null; 4079 4080 } 4081 4082 4083 if ( 4084 typeof body === 4085 'string' 4086 ) { 4087
4088 try { 4089 4090 return JSON.parse( 4091 body 4092 ); 4093 4094 } catch (e) { 4095 4096 try { 4097 4098 var params = 4099 new URLSearchParams( 4100 body 4101 ); 4102 4103 var result = {}; 4104 4105 params.forEach( 4106 function ( 4107 value, 4108 key 4109 ) { 4110 4111 result[key] = 4112 value; 4113 4114 } 4115 ); 4116 4117 return result; 4118 4119 } catch (e2) { 4120 4121 return null; 4122 4123 } 4124 4125 } 4126 4127 } 4128 4129 4130 if ( 4131 typeof URLSearchParams !== 4132 'undefined' && 4133 body instanceof 4134 URLSearchParams 4135 ) { 4136 4137 var paramsResult = {}; 4138 4139 body.forEach( 4140 function ( 4141 value, 4142 key 4143 ) { 4144 4145 paramsResult[key] = 4146 value; 4147 4148 } 4149 ); 4150 4151 return paramsResult; 4152 4153 } 4154 4155 4156 if ( 4157 typeof FormData !== 4158 'undefined' && 4159 body instanceof 4160 FormData 4161 ) { 4162 4163 var formResult = {}; 4164 4165 body.forEach( 4166 function ( 4167 value, 4168 key 4169 ) { 4170 4171 formResult[key] = 4172 value; 4173 4174 } 4175 ); 4176 4177 return formResult; 4178 4179 } 4180 4181 4182 return null; 4183 4184 } 4185 4186 4187 function getCartAddRequestInfo( 4188 input, 4189 init 4190 ) { 4191 4192 var requestUrl = ''; 4193 4194 try { 4195 4196 requestUrl = 4197 typeof input === 4198 'string' 4199 ? input 4200 : ( 4201 input && 4202 input.url 4203 ? input.url 4204 : '' 4205 ); 4206 4207 4208 var url = 4209 new URL( 4210 requestUrl, 4211 location.href 4212 ); 4213 4214 4215 if ( 4216 url.origin !== 4217 location.origin || 4218 url.pathname !== 4219 '/cart/add' || 4220 url.searchParams.get( 4221 'retrieve' 4222 ) !== 'true' 4223 ) { 4224 4225 return null; 4226 4227 } 4228 4229 } catch (e) { 4230 4231 return null; 4232 4233 } 4234 4235 4236 var method = 4237 String( 4238 ( 4239 init && 4240 init.method 4241 ) || 4242 ( 4243 input && 4244 input.method 4245 ) || 4246 'GET' 4247 ) 4248 .trim() 4249 .toUpperCase(); 4250 4251 4252 if ( 4253 method !== 4254 'POST' 4255 ) { 4256 4257 return null; 4258 4259 } 4260 4261 4262 var parsed = 4263 parseCartAddBody( 4264 init && 4265 init.body 4266 ); 4267 4268 4269 if (!parsed) { 4270 4271 return null; 4272 4273 } 4274 4275 4276 var variantId = 4277 String( 4278 parsed.id || 4279 parsed.variant_id || 4280 '' 4281 ).trim(); 4282 4283 4284 var quantity = 4285 Number( 4286 parsed.quantity || 1 4287 ); 4288 4289 4290 if ( 4291 !variantId || 4292 !Number.isFinite( 4293 quantity 4294 ) || 4295 quantity <= 0 4296 ) { 4297 4298 return null; 4299 4300 } 4301 4302 4303 return { 4304 4305 variant_id: 4306 variantId, 4307 4308 quantity: 4309 quantity 4310 4311 }; 4312 4313 } 4314 4315 4316 function createPendingCartAdd( 4317 requestInfo 4318 ) { 4319 4320 if (!requestInfo) { 4321 4322 return null; 4323 4324 } 4325 4326 4327 cartAddRequestSequence += 4328 1; 4329 4330 4331 var pending = { 4332 4333 request_id: 4334 cartAddRequestSequence, 4335 4336 variant_id: 4337 requestInfo.variant_id, 4338 4339 quantity: 4340 requestInfo.quantity, 4341 4342 handled_by_easystore: 4343 false, 4344 4345 created_at: 4346 Date.now() 4347 4348 }; 4349 4350 4351 pendingCartAddRequests.push( 4352 pending 4353 ); 4354 4355 4356 /* 4357 * Keep the queue bounded even if a browser 4358 * extension or storefront error prevents a 4359 * response from completing. 4360 */ 4361 if ( 4362 pendingCartAddRequests.length > 4363 20 4364 ) { 4365 4366 pendingCartAddRequests = 4367 pendingCartAddRequests.slice( 4368 -20 4369 ); 4370 4371 } 4372 4373 4374 return pending; 4375 4376 } 4377 4378 4379 function markPendingCartAddHandledByEasyStore( 4380 cart 4381 ) { 4382 4383 cart = 4384 cart || {}; 4385 4386 4387 var latestItems = 4388 Array.isArray( 4389 cart.latest_items 4390 ) 4391 ? cart.latest_items 4392 : []; 4393 4394 4395 if (!latestItems.length) { 4396 4397 return; 4398 4399 } 4400 4401 4402 var variantIds = 4403 latestItems 4404 .map( 4405 function (item) { 4406 4407 return String( 4408 item.variant_id || 4409 item.variantId || 4410 item.id || 4411 '' 4412 ).trim(); 4413 4414 } 4415 ) 4416 .filter(Boolean); 4417 4418 4419 if (!variantIds.length) { 4420 4421 return; 4422 4423 } 4424 4425 4426 for ( 4427 var i = 0; 4428 i < 4429 pendingCartAddRequests.length; 4430 i += 1 4431 ) { 4432 4433 var pending = 4434 pendingCartAddRequests[i]; 4435 4436 4437 if ( 4438 !pending || 4439 pending.handled_by_easystore 4440 ) { 4441 4442 continue; 4443 4444 } 4445 4446 4447 if ( 4448 variantIds.indexOf( 4449 pending.variant_id 4450 ) !== -1 4451 ) { 4452 4453 pending.handled_by_easystore = 4454 true; 4455 4456 return; 4457 4458 } 4459 4460 } 4461 4462 } 4463 4464 4465 function removePendingCartAdd( 4466 pending 4467 ) { 4468 4469 if (!pending) { 4470 4471 return; 4472 4473 } 4474 4475 4476 pendingCartAddRequests = 4477 pendingCartAddRequests 4478 .filter( 4479 function (item) { 4480 4481 return ( 4482 item !== 4483 pending 4484 ); 4485 4486 } 4487 ); 4488 4489 } 4490 4491 4492 function buildAddToCartFromCartResponse( 4493 pending, 4494 cart 4495 ) { 4496 4497 if ( 4498 !pending || 4499 !cart 4500 ) { 4501 4502 return null; 4503 4504 } 4505 4506 4507 var items = 4508 Array.isArray( 4509 cart.items 4510 ) 4511 ? cart.items 4512 : []; 4513 4514 4515 var matchingItem = 4516 null; 4517 4518 4519 for ( 4520 var i = 0; 4521 i < items.length; 4522 i += 1 4523 ) { 4524 4525 var item = 4526 items[i] || {}; 4527 4528 4529 var variantId = 4530 String( 4531 item.variant_id || 4532 item.variantId || 4533 '' 4534 ).trim(); 4535 4536 4537 if ( 4538 variantId === 4539 pending.variant_id 4540 ) { 4541 4542 matchingItem = 4543 item; 4544 4545 break; 4546 4547 } 4548 4549 } 4550 4551 4552 if ( 4553 !matchingItem || 4554 !matchingItem.product_id 4555 ) { 4556 4557 return null; 4558 4559 } 4560 4561 4562 var quantity = 4563 Number( 4564 pending.quantity 4565 ); 4566 4567 4568 var unitPrice = 4569 Number( 4570 matchingItem.price || 0 4571 ); 4572 4573 4574 if ( 4575 !Number.isFinite( 4576 quantity 4577 ) || 4578 quantity <= 0 || 4579 !Number.isFinite( 4580 unitPrice 4581 ) || 4582 unitPrice < 0 4583 ) { 4584 4585 return null; 4586 4587 } 4588 4589 4590 var currency = 4591 'MYR'; 4592 4593 4594 if ( 4595 cart.currency && 4596 typeof cart.currency === 4597 'object' && 4598 cart.currency.code 4599 ) { 4600 4601 currency = 4602 String( 4603 cart.currency.code 4604 ); 4605 4606 } else if ( 4607 cart.currency 4608 ) { 4609 4610 currency = 4611 String( 4612 cart.currency 4613 ); 4614 4615 } 4616 4617 4618 return { 4619 4620 event_name: 4621 'AddToCart', 4622 4623 content_ids: [ 4624 String( 4625 matchingItem.product_id 4626 ) 4627 ], 4628 4629 content_type: 4630 'product', 4631 4632 value: 4633 unitPrice * 4634 quantity, 4635 4636 currency: 4637 currency 4638 4639 }; 4640 4641 } 4642 4643
4644 function processFineLabelAddToCart( 4645 cart, 4646 built, 4647 source 4648 ) { 4649 4650 if (!built) { 4651 4652 return false; 4653 4654 } 4655 4656 4657 lastAddToCart = 4658 built; 4659 4660 4661 lastAddToCartSource = 4662 source || null; 4663 4664 4665 /* 4666 * Fine Label browser fbq sender remains 4667 * disabled while EasyStore native Meta 4668 * integration is active. 4669 */ 4670 maybeSendAddToCart( 4671 built 4672 ); 4673 4674 4675 /* 4676 * Fine Label controlled Server CAPI. 4677 */ 4678 var serverSent = 4679 sendFineLabelServerAddToCart( 4680 built 4681 ); 4682 4683 4684 /* 4685 * Browser Match Capture remains live. 4686 */ 4687 capture( 4688 'carts/item_added', 4689 4690 cart && 4691 cart.token 4692 ? cart.token 4693 : '', 4694 4695 cart && 4696 cart.order_token 4697 ? cart.order_token 4698 : '' 4699 ); 4700 4701 4702 return serverSent; 4703 4704 } 4705 4706 4707 function installCartAddFetchFallback() { 4708 4709 if ( 4710 window 4711 .__FL_META_CART_ADD_FETCH_FALLBACK_INSTALLED__ 4712 ) { 4713 4714 return; 4715 4716 } 4717 4718 4719 if ( 4720 typeof window.fetch !== 4721 'function' 4722 ) { 4723 4724 return; 4725 4726 } 4727 4728 4729 window 4730 .__FL_META_CART_ADD_FETCH_FALLBACK_INSTALLED__ = 4731 true; 4732 4733 4734 var originalFetch = 4735 window.fetch; 4736 4737 4738 window.fetch = 4739 function ( 4740 input, 4741 init 4742 ) { 4743 4744 var requestInfo = 4745 getCartAddRequestInfo( 4746 input, 4747 init 4748 ); 4749 4750 4751 var pending = 4752 createPendingCartAdd( 4753 requestInfo 4754 ); 4755 4756 4757 var promise = 4758 originalFetch.apply( 4759 this, 4760 arguments 4761 ); 4762 4763 4764 if (!pending) { 4765 4766 return promise; 4767 4768 } 4769 4770 4771 return promise.then( 4772 function (response) { 4773 4774 if ( 4775 !response || 4776 !response.ok 4777 ) { 4778 4779 lastAddToCartFallback = { 4780 4781 request_id: 4782 pending.request_id, 4783 4784 success: 4785 false, 4786 4787 skipped: 4788 true, 4789 4790 reason: 4791 'CART_ADD_RESPONSE_NOT_OK', 4792 4793 handled_by_easystore: 4794 pending 4795 .handled_by_easystore, 4796 4797 updated_at: 4798 new Date() 4799 .toISOString() 4800 4801 }; 4802 4803 4804 removePendingCartAdd( 4805 pending 4806 ); 4807 4808 4809 return response; 4810 4811 } 4812 4813 4814 try { 4815 4816 response 4817 .clone() 4818 .json() 4819 .then( 4820 function (cart) { 4821 4822 /* 4823 * Give EasyStore's own 4824 * carts/item_added callback a 4825 * brief chance to run first. 4826 */ 4827 setTimeout( 4828 function () { 4829 4830 if ( 4831 pending 4832 .handled_by_easystore 4833 ) { 4834 4835 lastAddToCartFallback = { 4836 4837 request_id: 4838 pending 4839 .request_id, 4840 4841 success: 4842 true, 4843 4844 skipped: 4845 true, 4846 4847 reason: 4848 'EASYSTORE_EVENT_HANDLED', 4849 4850 handled_by_easystore: 4851 true, 4852 4853 updated_at: 4854 new Date() 4855 .toISOString() 4856 4857 }; 4858 4859 4860 removePendingCartAdd( 4861 pending 4862 ); 4863 4864 4865 return; 4866 4867 } 4868 4869 4870 var built = 4871 buildAddToCartFromCartResponse( 4872 pending, 4873 cart 4874 ); 4875 4876 4877 if (!built) { 4878 4879 lastAddToCartFallback = { 4880 4881 request_id: 4882 pending 4883 .request_id, 4884 4885 success: 4886 false, 4887 4888 skipped: 4889 true, 4890 4891 reason: 4892 'UNABLE_TO_BUILD_FROM_CART_RESPONSE', 4893 4894 handled_by_easystore: 4895 false, 4896 4897 updated_at: 4898 new Date() 4899 .toISOString() 4900 4901 }; 4902 4903 4904 removePendingCartAdd( 4905 pending 4906 ); 4907 4908 4909 return; 4910 4911 } 4912 4913 4914 var sent = 4915 processFineLabelAddToCart( 4916 cart, 4917 built, 4918 'fetch_fallback' 4919 ); 4920 4921 4922 lastAddToCartFallback = { 4923 4924 request_id: 4925 pending 4926 .request_id, 4927 4928 success: 4929 sent, 4930 4931 skipped: 4932 false, 4933 4934 reason: 4935 null, 4936 4937 handled_by_easystore: 4938 false, 4939 4940 updated_at: 4941 new Date() 4942 .toISOString() 4943 4944 }; 4945 4946 4947 removePendingCartAdd( 4948 pending 4949 ); 4950 4951 }, 4952 300 4953 ); 4954 4955 } 4956 ) 4957 .catch( 4958 function () { 4959 4960 lastAddToCartFallback = { 4961 4962 request_id: 4963 pending 4964 .request_id, 4965 4966 success: 4967 false, 4968 4969 skipped: 4970 true, 4971 4972 reason: 4973 'INVALID_CART_ADD_RESPONSE', 4974 4975 handled_by_easystore: 4976 pending 4977 .handled_by_easystore, 4978 4979 updated_at: 4980 new Date() 4981 .toISOString() 4982 4983 }; 4984 4985 4986 removePendingCartAdd( 4987 pending 4988 ); 4989 4990 } 4991 ); 4992 4993 } catch (e) { 4994 4995 lastAddToCartFallback = { 4996 4997 request_id: 4998 pending.request_id, 4999 5000 success: 5001 false, 5002 5003 skipped: 5004 true, 5005 5006 reason: 5007 'CART_ADD_FALLBACK_ERROR', 5008 5009 handled_by_easystore: 5010 pending 5011 .handled_by_easystore, 5012 5013 updated_at: 5014 new Date() 5015 .toISOString() 5016 5017 }; 5018 5019 5020 removePendingCartAdd( 5021 pending 5022 ); 5023 5024 } 5025 5026 5027 return response; 5028 5029 } 5030 ); 5031 5032 }; 5033 5034 } 5035 5036 5037 /* 5038 * ============================================================ 5039 * InitiateCheckout Builder 5040 * 5041 * Source: 5042 * 5043 * EasyStore.Checkout.data.checkout 5044 * 5045 * item_count is NOT trusted. 5046 * 5047 * num_items is calculated from 5048 * line_items.quantity. 5049 * 5050 * Shipping is excluded from value. 5051 * ============================================================ 5052 */ 5053 5054 5055 function buildInitiateCheckout( 5056 checkout 5057 ) { 5058 5059 checkout = 5060 checkout || {}; 5061 5062 5063 var items = 5064 Array.isArray( 5065 checkout.line_items 5066 ) 5067 ? checkout.line_items 5068 : []; 5069 5070 5071 if (!items.length) { 5072 5073 return null; 5074 5075 } 5076 5077 5078 var seenContentIds = {}; 5079 5080 5081 var contentIds = 5082 items 5083 .map( 5084 function (item) { 5085 5086 return item.product_id 5087 ? String( 5088 item.product_id 5089 ) 5090 : null; 5091 5092 } 5093 ) 5094 .filter( 5095 function (contentId) { 5096 5097 if ( 5098 !contentId || 5099 seenContentIds[contentId] 5100 ) { 5101 5102 return false; 5103 5104 } 5105 5106 5107 seenContentIds[contentId] = 5108 true; 5109 5110 5111 return true; 5112 5113 } 5114 ); 5115 5116 5117 if (!contentIds.length) { 5118 5119 return null; 5120 5121 } 5122 5123 5124 var numItems = 5125 items.reduce( 5126 function ( 5127 total, 5128 item 5129 ) { 5130 5131 return ( 5132 total + 5133 Number( 5134 item.quantity || 1 5135 ) 5136 ); 5137 5138 }, 5139 0 5140 ); 5141 5142 5143 var currency = 5144 'MYR'; 5145 5146 5147 if ( 5148 checkout.currency && 5149 typeof checkout.currency === 5150 'object' && 5151 checkout.currency.code 5152 ) { 5153 5154 currency = 5155 String( 5156 checkout.currency.code 5157 ); 5158 5159 } else if ( 5160 checkout.currency 5161 ) { 5162 5163 currency = 5164 String( 5165 checkout.currency 5166 ); 5167 5168 } 5169 5170 5171 var value = 5172 Number( 5173 checkout.subtotal_price || 5174 checkout 5175 .line_items_subtotal_price || 5176 checkout 5177 .total_line_items_price || 5178 0 5179 ); 5180 5181 5182 return { 5183 5184 event_name: 5185 'InitiateCheckout', 5186 5187 content_ids: 5188 contentIds, 5189 5190 content_type: 5191 'product', 5192 5193 num_items: 5194 numItems, 5195 5196 value: 5197 value, 5198 5199 currency: 5200 currency 5201 5202 }; 5203 5204 } 5205 5206 5207 /* 5208 * ============================================================ 5209 * Current EasyStore Checkout State 5210 * ============================================================ 5211 */ 5212 5213 5214 function getCurrentCheckout() { 5215 5216 try { 5217 5218 if ( 5219 window.EasyStore && 5220 EasyStore.Checkout && 5221 EasyStore.Checkout.data && 5222 EasyStore.Checkout.data.checkout 5223 ) { 5224 5225 return ( 5226 EasyStore 5227 .Checkout 5228 .data 5229 .checkout 5230 ); 5231 5232 } 5233 5234 } catch (e) {} 5235 5236 5237 return null; 5238 5239 } 5240 5241 5242 function buildCurrentInitiateCheckout() { 5243 5244 var checkout = 5245 getCurrentCheckout(); 5246 5247 5248 if (!checkout) { 5249 5250 return null; 5251 5252 } 5253 5254 5255 return ( 5256 buildInitiateCheckout( 5257 checkout 5258 ) 5259 ); 5260 5261 } 5262 5263 5264 /* 5265 * ============================================================ 5266 * InitiateCheckout Deduplication 5267 * 5268 * Avoid repeated InitiateCheckout 5269 * when same checkout page is refreshed. 5270 * ============================================================ 5271 */ 5272 5273 5274 function getCheckoutIdentity( 5275 checkout 5276 ) { 5277 5278 checkout = 5279 checkout || {}; 5280 5281 5282 return String( 5283 checkout.cart_token || 5284 checkout.token || 5285 checkout.id || 5286 checkoutTokenFromUrl() || 5287 '' 5288 ).trim(); 5289 5290 } 5291 5292 5293 function wasInitiateCheckoutSent( 5294 checkoutIdentity 5295 ) { 5296 5297 if (!checkoutIdentity) { 5298 5299 return false; 5300 5301 } 5302 5303 5304 try { 5305 5306 return ( 5307 sessionStorage.getItem( 5308 INITIATE_CHECKOUT_SENT_KEY 5309 ) === 5310 checkoutIdentity 5311 ); 5312 5313 } catch (e) { 5314 5315 return false; 5316 5317 } 5318 5319 } 5320 5321 5322 function markInitiateCheckoutSent( 5323 checkoutIdentity 5324 ) { 5325 5326 if (!checkoutIdentity) { 5327 5328 return; 5329 5330 } 5331 5332 5333 try { 5334 5335 sessionStorage.setItem( 5336 INITIATE_CHECKOUT_SENT_KEY, 5337 checkoutIdentity 5338 ); 5339 5340 } catch (e) {} 5341 5342 } 5343 5344 5345 /* 5346 * ============================================================ 5347 * Send PageView 5348 * ============================================================ 5349 */ 5350 5351 5352 function maybeSendPageView() { 5353 5354 if (pageViewSent) { 5355 5356 return; 5357 5358 } 5359 5360 5361 var sent = 5362 sendFineLabelMetaEvent( 5363 'PageView', 5364 {} 5365 ); 5366 5367 5368 if (sent) { 5369 5370 pageViewSent = 5371 true; 5372 5373 } 5374 5375 } 5376 5377 5378 /* 5379 * ============================================================ 5380 * Send ViewContent 5381 * ============================================================ 5382 */ 5383 5384 5385 function maybeSendViewContent( 5386 attempt, 5387 built, 5388 expectedPath 5389 ) { 5390 5391 attempt = 5392 attempt || 0; 5393 5394 5395 built = 5396 built || 5397 buildViewContent(); 5398 5399 5400 expectedPath = 5401 expectedPath || 5402 location.pathname; 5403 5404 5405 if (!built) { 5406 5407 return; 5408 5409 } 5410 5411 5412 /* 5413 * If navigation happened while waiting,
5414 * do not send the old product against 5415 * the new page URL. 5416 */ 5417 if ( 5418 location.pathname !== 5419 expectedPath 5420 ) { 5421 5422 return; 5423 5424 } 5425 5426 5427 /* 5428 * EasyStore products/viewed may fire 5429 * before Meta has created _fbp. 5430 * 5431 * If _fbp is not ready yet, wait up to 5432 * about 5 seconds before sending CAPI 5433 * ViewContent. 5434 * 5435 * 17 retries à 300ms = ~5.1 seconds. 5436 * 5437 * If _fbp still does not exist after 5438 * the wait, send anyway so ViewContent 5439 * is not lost. 5440 */ 5441 if ( 5442 !getCookie('_fbp') && 5443 attempt < 17 5444 ) { 5445 5446 setTimeout( 5447 function () { 5448 5449 maybeSendViewContent( 5450 attempt + 1, 5451 built, 5452 expectedPath 5453 ); 5454 5455 }, 5456 300 5457 ); 5458 5459 5460 return; 5461 5462 } 5463 5464 5465 /* 5466 * Server CAPI ViewContent. 5467 * 5468 * No fbq() call. 5469 * Fine Label main pixel remains 5470 * uninitialized in this script. 5471 */ 5472 sendFineLabelServerViewContent( 5473 built 5474 ); 5475 5476 } 5477 5478 5479 /* 5480 * ============================================================ 5481 * Send AddToCart 5482 * ============================================================ 5483 */ 5484 5485 5486 function maybeSendAddToCart( 5487 built 5488 ) { 5489 5490 if (!built) { 5491 5492 return; 5493 5494 } 5495 5496 5497 sendFineLabelMetaEvent( 5498 'AddToCart', 5499 { 5500 5501 content_ids: 5502 built.content_ids, 5503 5504 content_type: 5505 built.content_type, 5506 5507 value: 5508 built.value, 5509 5510 currency: 5511 built.currency 5512 5513 } 5514 ); 5515 5516 } 5517 5518 5519 /* 5520 * ============================================================ 5521 * Send InitiateCheckout 5522 * ============================================================ 5523 */ 5524 5525 5526 function maybeSendInitiateCheckout( 5527 checkout, 5528 built 5529 ) { 5530 5531 if ( 5532 !checkout || 5533 !built 5534 ) { 5535 5536 return; 5537 5538 } 5539 5540 5541 var checkoutIdentity = 5542 getCheckoutIdentity( 5543 checkout 5544 ); 5545 5546 5547 if ( 5548 wasInitiateCheckoutSent( 5549 checkoutIdentity 5550 ) 5551 ) { 5552 5553 return; 5554 5555 } 5556 5557 5558 var browserSent = 5559 sendFineLabelMetaEvent( 5560 'InitiateCheckout', 5561 { 5562 5563 content_ids: 5564 built.content_ids, 5565 5566 content_type: 5567 built.content_type, 5568 5569 num_items: 5570 built.num_items, 5571 5572 value: 5573 built.value, 5574 5575 currency: 5576 built.currency 5577 5578 } 5579 ); 5580 5581 5582 var serverSent = 5583 sendFineLabelServerInitiateCheckout( 5584 built 5585 ); 5586 5587 5588 if ( 5589 browserSent || 5590 serverSent 5591 ) { 5592 5593 markInitiateCheckoutSent( 5594 checkoutIdentity 5595 ); 5596 5597 } 5598 5599 } 5600 5601 5602 /* 5603 * ============================================================ 5604 * Checkout State Watcher 5605 * ============================================================ 5606 */ 5607 5608 5609 function watchCheckoutState( 5610 attempt 5611 ) { 5612 5613 attempt = 5614 attempt || 0; 5615 5616 5617 var checkout = 5618 getCurrentCheckout(); 5619 5620 5621 if (checkout) { 5622 5623 var built = 5624 buildInitiateCheckout( 5625 checkout 5626 ); 5627 5628 5629 if (built) { 5630 5631 lastInitiateCheckout = 5632 built; 5633 5634 5635 maybeSendInitiateCheckout( 5636 checkout, 5637 built 5638 ); 5639 5640 5641 return; 5642 5643 } 5644 5645 } 5646 5647 5648 if (attempt < 120) { 5649 5650 setTimeout( 5651 function () { 5652 5653 watchCheckoutState( 5654 attempt + 1 5655 ); 5656 5657 }, 5658 500 5659 ); 5660 5661 } 5662 5663 } 5664 5665 5666 /* 5667 * ============================================================ 5668 * EasyStore Event Listeners 5669 * ============================================================ 5670 */ 5671 5672 5673 function installEasyStoreListeners( 5674 attempt 5675) { 5676 5677 attempt = 5678 attempt || 0; 5679 5680 5681 /* 5682 * Prevent duplicate listener installation. 5683 */ 5684 if ( 5685 window 5686 .__FL_META_EASYSTORE_LISTENERS_INSTALLED__ 5687 ) { 5688 5689 return; 5690 5691 } 5692 5693 5694 if ( 5695 window.EasyStore && 5696 EasyStore.Event &&
5697 typeof EasyStore.Event.subscribe === 5698 'function' 5699 ) { 5700 5701 /* 5702 * Mark before subscribing so another 5703 * delayed retry cannot install twice. 5704 */ 5705 window 5706 .__FL_META_EASYSTORE_LISTENERS_INSTALLED__ = 5707 true; 5708 5709 5710 /* 5711 * ViewContent 5712 */ 5713 EasyStore.Event.subscribe( 5714 'products/viewed', 5715 5716 function () { 5717 5718 var data = 5719 arguments[0] || {}; 5720 5721 5722 saveProductViewed( 5723 data 5724 ); 5725 5726 5727 maybeSendViewContent(); 5728 5729 } 5730 ); 5731 5732 5733 /* 5734 * AddToCart 5735 */ 5736 EasyStore.Event.subscribe( 5737 'carts/item_added', 5738 5739 function () { 5740 5741 var data = 5742 arguments[0] || {}; 5743 5744 5745 var cart = 5746 ( 5747 data.cart && 5748 typeof data.cart === 5749 'object' 5750 ) 5751 ? data.cart 5752 : data; 5753 5754 5755 markPendingCartAddHandledByEasyStore( 5756 cart 5757 ); 5758 5759 5760 var built = 5761 buildAddToCart( 5762 cart 5763 ); 5764 5765 5766 processFineLabelAddToCart( 5767 cart, 5768 built, 5769 'easystore_event' 5770 ); 5771 5772 } 5773 ); 5774 5775 5776 /* 5777 * Checkout event retained 5778 * for Browser Match Capture. 5779 */ 5780 EasyStore.Event.subscribe( 5781 'checkouts/initiated', 5782 5783 function () { 5784 5785 var data = 5786 arguments[0] || {}; 5787 5788 5789 var cart = 5790 ( 5791 data.cart && 5792 typeof data.cart === 5793 'object' 5794 ) 5795 ? data.cart 5796 : data; 5797 5798 5799 /* 5800 * Save checkout identifiers only. 5801 * 5802 * Browser Match itself is sent 5803 * after navigation by the 5804 * /sf/checkout/<cart_token> 5805 * URL fallback. 5806 * 5807 * This prevents duplicate 5808 * checkouts/initiated capture. 5809 */ 5810 saveTokens( 5811 cart.token || 5812 getSavedCartToken(), 5813 5814 cart.order_token || 5815 getSavedOrderToken() 5816 ); 5817 5818 } 5819); 5820 5821 return; 5822 5823 } 5824 5825 5826 /* 5827 * EasyStore may initialise quite late. 5828 * 5829 * Retry for up to ~2 minutes: 5830 * 240 attempts à 500ms. 5831 */ 5832 if (attempt < 240) { 5833 5834 setTimeout( 5835 function () { 5836 5837 installEasyStoreListeners( 5838 attempt + 1 5839 ); 5840 5841 }, 5842 500 5843 ); 5844 5845 } 5846 5847} 5848 5849 /* 5850 * ============================================================ 5851 * Checkout URL Browser Match Fallback 5852 * ============================================================ 5853 */ 5854 5855 5856 var checkoutToken = 5857 checkoutTokenFromUrl(); 5858 5859 5860 if (checkoutToken) { 5861 5862 capture( 5863 'checkouts/initiated', 5864 5865 checkoutToken, 5866 5867 getSavedOrderToken() 5868 ); 5869 5870 } 5871 5872 5873 /* 5874 * ============================================================ 5875 * Start 5876 * ============================================================ 5877 */ 5878 5879 5880 installCartAddFetchFallback(); 5881 5882 5883 installEasyStoreListeners(0); 5884 5885 5886 /* 5887 * PageView sender. 5888 * 5889 * Currently does nothing because 5890 * browser events flag is FALSE. 5891 */ 5892 5893 maybeSendPageView(); 5894 5895 5896 /* 5897 * Checkout state watcher. 5898 */ 5899 5900 if (checkoutToken) { 5901 5902 watchCheckoutState(0); 5903 5904 } 5905 5906 5907 /* 5908 * ============================================================ 5909 * Browser Match Safe Diagnostic 5910 * ============================================================ 5911 */ 5912 5913 5914 window.FLMetaBrowserMatch = { 5915 5916 status: 5917 function () { 5918 5919 return ( 5920 window 5921 .__FL_META_BROWSER_MATCH_LAST__ || 5922 { 5923 5924 success: 5925 null, 5926 5927 status: 5928 null, 5929 5930 event: 5931 null, 5932 5933 has_fbp: 5934 Boolean( 5935 getCookie('_fbp') 5936 ), 5937 5938 has_fbc: 5939 Boolean( 5940 getCookie('_fbc') 5941 ) 5942 5943 } 5944 ); 5945 5946 } 5947 5948 }; 5949 5950 5951 /* 5952 * ============================================================ 5953 * Fine Label Browser Event Inspector 5954 * 5955 * Safe to use in production. 5956 * 5957 * Does NOT expose raw fbp/fbc. 5958 * ============================================================ 5959 */ 5960 5961 5962 window.FLMetaBrowserEvents = { 5963 5964 inspect: 5965 function () { 5966 5967 var checkout = 5968 getCurrentCheckout(); 5969 5970 5971 var liveInitiateCheckout = 5972 checkout 5973 ? buildInitiateCheckout( 5974 checkout 5975 ) 5976 : null; 5977 5978 5979 if (liveInitiateCheckout) { 5980 5981 lastInitiateCheckout = 5982 liveInitiateCheckout; 5983 5984 } 5985 5986 5987 return { 5988 5989 browser_events_enabled: 5990 FL_META_BROWSER_EVENTS_ENABLED, 5991 5992 server_view_content_enabled: 5993 isFineLabelServerViewContentEnabled(), 5994 5995 server_view_content_mode: 5996 FL_META_SERVER_VIEWCONTENT_ENABLED === true 5997 ? 'production' 5998 : ( 5999 isFineLabelServerViewContentEnabled() 6000 ? 'test' 6001 : 'off' 6002 ), 6003 6004 server_view_content_global_enabled: 6005 FL_META_SERVER_VIEWCONTENT_ENABLED, 6006 6007 server_view_content_requests: 6008 serverViewContentRequests, 6009 6010 last_server_view_content: 6011 lastServerViewContent, 6012 6013 server_add_to_cart_enabled: 6014 isFineLabelServerAddToCartEnabled(), 6015 6016 server_add_to_cart_mode: 6017 FL_META_SERVER_ADDTOCART_ENABLED === true 6018 ? 'production' 6019 : ( 6020 isFineLabelServerAddToCartEnabled() 6021 ? 'test' 6022 : 'off' 6023 ), 6024 6025 server_add_to_cart_global_enabled: 6026 FL_META_SERVER_ADDTOCART_ENABLED, 6027 6028 server_add_to_cart_requests: 6029 serverAddToCartRequests, 6030 6031 last_server_add_to_cart: 6032 lastServerAddToCart, 6033 6034 add_to_cart_source: 6035 lastAddToCartSource, 6036 6037 last_add_to_cart_fallback: 6038 lastAddToCartFallback, 6039 6040 cart_add_fetch_fallback_installed: 6041 Boolean( 6042 window 6043 .__FL_META_CART_ADD_FETCH_FALLBACK_INSTALLED__ 6044 ), 6045 6046 pending_cart_add_requests: 6047 pendingCartAddRequests.length, 6048 6049 server_initiate_checkout_enabled: 6050 isFineLabelServerInitiateCheckoutEnabled(), 6051 6052 server_initiate_checkout_mode: 6053 FL_META_SERVER_INITIATECHECKOUT_ENABLED === true 6054 ? 'production' 6055 : ( 6056 isFineLabelServerInitiateCheckoutEnabled() 6057 ? 'test' 6058 : 'off' 6059 ), 6060 6061 server_initiate_checkout_global_enabled: 6062 FL_META_SERVER_INITIATECHECKOUT_ENABLED, 6063 6064 server_initiate_checkout_requests: 6065 serverInitiateCheckoutRequests, 6066 6067 last_server_initiate_checkout: 6068 lastServerInitiateCheckout, 6069 6070 pixel_id: 6071 META_PIXEL_ID, 6072 6073 page_view: 6074 buildPageView(), 6075 6076 view_content: 6077 buildViewContent(), 6078 6079 add_to_cart: 6080 lastAddToCart, 6081 6082 initiate_checkout: 6083 lastInitiateCheckout, 6084 6085 checkout_source_ready: 6086 Boolean( 6087 checkout 6088 ), 6089 6090 checkout_line_items: 6091 checkout && 6092 Array.isArray( 6093 checkout.line_items 6094 ) 6095 ? checkout 6096 .line_items 6097 .length 6098 : 0, 6099 6100 meta_events_sent: 6101 metaEventsSent, 6102 6103 last_meta_event: 6104 lastMetaEvent 6105 6106 }; 6107 6108 } 6109 6110 }; 6111 6112 6113})(); 6114</script>
6114 6115<!-- /Snippet --> 6116 6117<!-- Snippet:global/body_end: Product Description Tabs --> 6118<style> 6119 .product-description-tabs{ 6120 color: #000000; 6121 } 6122 .prod-desc-tab-container { 6123 padding: 15px; 6124 margin-right: auto; 6125 margin-left: auto; 6126 background-color: #ffffff; 6127 } 6128 /* Style the list */ 6129 .prod-desc-tab { 6130 list-style-type: none; 6131 margin: 0; 6132 padding: 0; 6133 border-bottom: solid 1px #ddd; 6134 /*overflow: hidden; 6135 border: 1px solid #e6e6e6; 6136 background-color: #fefefe; 6137 border-bottom: 1px solid #ddd;*/ 6138 } 6139 6140 /* Float the list items side by side */ 6141 li.tablinks { 6142 display: inline-block; 6143 border-top: 3px solid transparent; 6144 border-left: 1px solid transparent; 6145 border-right: 1px solid transparent; 6146 margin-bottom: -1px; 6147 margin-right: 3px; 6148 border-radius: 4px 4px 0 0; 6149 background-color: rgba(212,212,212,0.3); 6150 } 6151 6152 /* Style the links inside the list items */ 6153 li.tablinks a { 6154 color: #000000; 6155 cursor: pointer; 6156 margin-right: 2px; 6157 line-height: 1.42857143; 6158 display: block; 6159 padding: 10px 15px; 6160 } 6161 6162 /* Change background color of links on hover */ 6163 li.tablinks:hover { 6164 background-color: #ffffff; 6165 border-bottom-color: transparent; 6166 border-top: 3px solid #ddd; 6167 border-left: 1px solid #ddd; 6168 border-right: 1px solid #ddd; 6169 } /*rgba(212,212,212,0.3)*/ 6170 6171 /* Create an active/current tablink class */ 6172 li.tablinks a:focus, li.tablinks.active{ 6173 border-top: 3px solid #ddd; 6174 border-left: 1px solid #ddd; 6175 border-right: 1px solid #ddd; 6176 background-color: #ffffff; 6177 } 6178 6179 /* Style the tab content */ 6180 .prod-desc-tab-content { 6181 display: none; 6182 padding: 15px 15px; 6183 /*border: 1px solid #ccc;*/ 6184 border-top: none; 6185 clear: both; 6186 -webkit-animation: fadeEffect 1s; 6187 animation: fadeEffect 1s; /* Fading effect takes 1 second */ 6188 } 6189 .prod-desc-tab-content ul{ 6190 list-style-type: disc; 6191 } 6192 6193 /* if user select is_show_title is true, the title will be displayed; if not, hide the title*/ 6194 .prod-desc-tab-content > h2{ 6195 display:block; 6196 } 6197 6198 @-webkit-keyframes fadeEffect { 6199 from {opacity: 0;} 6200 to {opacity: 1;} 6201 } 6202 6203 @keyframes fadeEffect { 6204 from {opacity: 0;} 6205 to {opacity: 1;} 6206 } 6207 6208 .tab_drawer_heading { display: none; } 6209 6210 @media screen and (max-width: 480px) { 6211 .prod-desc-tab { 6212 display: none; 6213 } 6214 .tab_drawer_heading { 6215 background-color: transparent; 6216 color: #000000; 6217 border-bottom: 1px solid #ddd; 6218 margin: 0; 6219 padding: 6px 12px; 6220 display: block; 6221 cursor: pointer; 6222 -webkit-touch-callout: none; 6223 -webkit-user-select: none; 6224 -khtml-user-select: none; 6225 -moz-user-select: none; 6226 -ms-user-select: none; 6227 user-select: none; 6228 position: relative; 6229 } 6230 .tab_drawer_heading:after { 6231 content: ''; 6232 top: 50%; 6233 right: 40px; 6234 position: absolute; 6235 border: solid black; 6236 border-width: 0 3px 3px 0; 6237 display: inline-block; 6238 width: 10px; 6239 height: 10px; 6240 margin-top: -5px; 6241 transform: rotate(45deg); 6242 -webkit-transform: rotate(45deg); 6243 -webkit-transition: all 200ms linear; 6244 -moz-transition: all 200ms linear; 6245 -ms-transition: all 200ms linear; 6246 -o-transition: all 200ms linear; 6247 transition: all 200ms linear; 6248 } 6249 .d_active { 6250 background-color: rgba(212,212,212,0.3); 6251 } 6252 .d_active.tab_drawer_heading:after{ 6253 margin-top: -3px; 6254 transform: rotate(-135deg); 6255 -webkit-transform: rotate(-135deg); 6256 } 6257 } 6258</style> 6259
6260<script type="text/javascript"> 6261 // If the page has no jquery 6262 // if(typeof jQuery == 'undefined'){ 6263 // console.log(456); 6264 // var script = document.createElement('script'); 6265 // script.type = "text/javascript"; 6266 // script.src = "https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"; 6267 // console.log(script); 6268 // document.getElementsByTagName('head')[0].appendChild(script); 6269 // } 6270 6271 /* if in tab mode */ 6272 $("body").on("click", ".tablinks", function(){ 6273 var id = $(this).data("prod-desc-id"); 6274 6275 // Get all elements with class="prod-desc-tab-content" and hide them 6276 $(".prod-desc-tab-content").hide(); 6277 6278 // Get all elements with class="tablinks" and remove the class "active" 6279 $(".tablinks").removeClass("active"); 6280 6281 // Show the current tab, and add an "active" class to the link that opened the tab 6282 $("#"+id+"_desc_tab_content").show(); 6283 $(this).addClass("active"); 6284 6285 // make mobile view(drawer mode) also in same tab 6286 $(".tab_drawer_heading").removeClass("d_active"); 6287 $(".tab_drawer_heading[data-prod-desc-id='"+id+"']").addClass("d_active"); 6288 6289 }); 6290 6291 /* if in drawer mode */ 6292 $("body").on("click", ".tab_drawer_heading", function() { 6293 var id = $(this).data("prod-desc-id") 6294 var active = $(this).hasClass("d_active") 6295 6296 $(".prod-desc-tab-content").hide(); 6297 $(".tab_drawer_heading").removeClass("d_active"); 6298 6299 if(!active){ 6300 $("#"+id+"_desc_tab_content").show(); 6301 $(this).addClass("d_active"); 6302 window.scroll({ 6303 top: $(this).offset().top - 120, 6304 left: 0, 6305 behavior: 'smooth' 6306 }); 6307 } 6308 6309 $(".tablinks").removeClass("active"); 6310 $(".tablinks[data-prod-desc-id='"+id+"']").addClass("active"); 6311 }); 6312 6313 $(function(){ 6314 var x = $(".product-description").find("h1, h2, h3, h4, h5, h6"); 6315 var title = null; 6316 var body = null; 6317 6318 if(x.length > 1){ 6319 $(".product-description").after("<link href='/assets/css/froala_style.min.css' rel='stylesheet' type='text/css'/><div class='product-description-tabs fr-view rte'><ul class='prod-desc-tab' style='margin: auto;'></ul><div class='prod-desc-tab-container'></div></div>"); 6320 $(x).each(function(k, v){ 6321 title = $(v).text(); 6322 6323 if(k == 0){ 6324 body = $(x[0]).nextUntil(x[1]).clone(); 6325 $(".prod-desc-tab").append('<li class="tablinks active" data-prod-desc-id="'+k+'"><a>'+title+'</a></li>'); 6326 $(".prod-desc-tab-container").append('<h3 class="tab_drawer_heading d_active" data-prod-desc-id="'+k+'">'+title+'</h3><div id="'+k+'_desc_tab_content" class="prod-desc-tab-content" style="display:block"><h2>'+title+'</h2></div>'); 6327 $("#"+k+"_desc_tab_content h2").after(body); 6328 } 6329 else if(k == x.length){ 6330 body = $(x[k]).nextAll().clone(); 6331 $(".prod-desc-tab").append('<li class="tablinks" data-prod-desc-id="'+k+'"><a>'+title+'</a></li>'); 6332 $(".prod-desc-tab-container").append('<h3 class="tab_drawer_heading" data-prod-desc-id="'+k+'">'+title+'</h3><div id="'+k+'_desc_tab_content" class="prod-desc-tab-content" style="display:none"><h2>'+title+'</h2></div>'); 6333 $("#"+k+"_desc_tab_content h2").after(body); 6334 } 6335 else{ 6336 body = $(x[k]).nextUntil(x[k+1]).clone(); 6337 $(".prod-desc-tab").append('<li class="tablinks" data-prod-desc-id="'+k+'"><a>'+title+'</a></li>'); 6338 $(".prod-desc-tab-container").append('<h3 class="tab_drawer_heading" data-prod-desc-id="'+k+'">'+title+'</h3><div id="'+k+'_desc_tab_content" class="prod-desc-tab-content" style="display:none"><h2>'+title+'</h2></div>'); 6339 $("#"+k+"_desc_tab_content h2").after(body); 6340 } 6341 }); 6342 $(".product-description").hide(); 6343 } 6344 6345 }); 6346</script>
6346 6347<!-- /Snippet -->
6348<script> 6349 6350 /* 6351 6352 ------ 6353 Events 6354 ------ 6355 6356 pages/viewed 6357 6358 customers/signup 6359 6360 customers/login 6361 6362 products/searched 6363 6364 collections/viewed 6365 6366 products/viewed 6367 6368 products/shared 6369 6370 wishlists/item_added 6371 6372 carts/items_added 6373 6374 carts/viewed 6375 6376 carts/item_removed 6377 6378 checkouts/initiated 6379 6380 checkouts/shipping_info_added 6381 6382 checkouts/payment_info_added 6383 6384 checkouts/completed 6385 6386 orders/placed 6387 6388 orders/purchased 6389 6390 payments/captured 6391 6392 payments/failed 6393 6394 */ 6395 6396 window.__latest_cart = window.__latest_cart || null; 6397 6398 const _selector = document.querySelector.bind(document), 6399 _selectorAll = document.querySelectorAll.bind(document) 6400 6401 document.addEventListener('DOMContentLoaded', (event) => { 6402 6403 const default_currency = getCookie("currency") 6404 const customer_id = window.__st.cid 6405 const template = window.__st.p 6406 6407 onPageView() 6408 6409 //-----------------------// 6410 // Configs // 6411 //-----------------------// 6412 6413 window.dataLayer = window.dataLayer || [] 6414 6415 Array.prototype.last = Array.prototype.last || function() { 6416 return this[this.length - 1] || null 6417 } 6418 6419 Array.prototype.first = Array.prototype.first || function() { 6420 return this[0] || null 6421 } 6422 6423 //----------------------// 6424 // Triggers // 6425 //----------------------// 6426 6427 const XHR = window.XMLHttpRequest 6428 6429 function xhr() { 6430 6431 const xhr = new XHR() 6432 6433 xhr.addEventListener("readystatechange", function() { 6434 6435 if(xhr.readyState != 4) return 6436 6437 try { 6438 6439 const response_url = xhr.responseURL 6440 6441 if (response_url.includes("cart/add")) { 6442 6443 window.__latest_cart = JSON.parse(xhr.response) 6444 6445 onCartItemsAdded() // ok 6446 6447 } 6448 6449 if (response_url.includes("cart/remove_item_quantity")) { 6450 6451 window.__latest_cart = JSON.parse(xhr.response) 6452 6453 onCartItemRemoved() // ok 6454 6455 } 6456 6457 if (response_url.includes("new_cart?retrieve=true")) { 6458 6459 result = JSON.parse(xhr.response) 6460 6461 if (result.cart) { 6462 window.__latest_cart = result.cart 6463 } 6464 6465 } 6466 6467 } catch(e) { 6468 6469 console.error(e) 6470 6471 } 6472 6473 }, false); 6474 6475 return xhr 6476 6477 } 6478 6479 window.XMLHttpRequest = xhr 6480 6481 switch(template) { 6482 6483 case 'cart': onCartView() // ok 6484 break 6485 case 'product': onProductView() // ok 6486 break 6487 case 'collection': onCollectionView() // ok 6488 break 6489 case 'blog': onBlogView() 6490 break 6491 case 'article': onArticleView() 6492 break 6493 case 'payment_completed': onOrderPlace() 6494 break 6495 case 'payment_fail': onPaymentFail() 6496 break 6497 6498 } 6499 6500 // _selector('form[action="/checkout/payments"]').addEventListener("submit", onPaymentInfoAdded(_selector('form[action="/checkout/payments"]'))) 6501 if (_selector('form[action="/checkout/detail"]')) _selector('form[action="/checkout/detail"]').addEventListener("submit", ()=>{onShippingInfoAdded(_selector('form[action="/checkout/detail"]'))}) // ok 6502 if (_selector('form[action="/checkout/shipping"]')) _selector('form[action="/checkout/shipping"]').addEventListener("submit", ()=>{onShippingInfoAdded(_selector('form[action="/checkout/shipping"]'))}) // ok 6503 if (_selector('form[action="/account/register"]')) _selector('form[action="/account/register"]').addEventListener("submit", onSignUp) // ok 6504 if (_selector('form[action="/account/login"]')) _selector('form[action="/account/login"]').addEventListener("submit", onLogin) // ok 6505 if (_selector('form[action="/search"]')) _selector('form[action="/search"]').addEventListener("submit", onProductSearch()) // ok 6506 if (_selector('#line-login-btn')) _selector('#line-login-btn').addEventListener('click', onLineLogin) // ok 6507 if (_selector('#PlaceOrder')) _selector('#PlaceOrder').addEventListener("click", ()=>{onCheckoutComplete(_selector('form[action="/checkout/payments"]'))}); // ok 6508 if (_selector('#add_wishlist')) _selector('#add_wishlist').addEventListener("click", onWishlistItemAdded) // ok 6509 if (_selectorAll('a[href="/account/logout"]')) _selectorAll('a[href="/account/logout"]').forEach(logoutButton => logoutButton.addEventListener("click", onLogout)) // ok 6510 if (_selector('.CartDrawerTrigger.cart-page-link')) _selector('.CartDrawerTrigger.cart-page-link').addEventListener("click", onCartView) // ok 6511 if (_selector('.CartDrawerTrigger.cart-page-link.mobile-cart-page-link')) _selector('.CartDrawerTrigger.cart-page-link.mobile-cart-page-link').addEventListener("click", onCartView) // ok 6512 if (_selectorAll('a[class^="share-"]')) _selectorAll('a[class^="share-"]').forEach(shareButton => shareButton.addEventListener("click", ()=>{onProductShare(shareButton)}) ) // ok 6513 if (_selectorAll('[name$="checkout"]')) _selectorAll('[name$="checkout"]').forEach(checkoutButton => checkoutButton.addEventListener("click", onCheckoutInitiate)) 6514 6515 // For append elements 6516 const bodyMutationObserver = new MutationObserver(() => { 6517 if (_selector('#form__spc #PlaceOrder')) _selector('#form__spc #PlaceOrder').addEventListener("click", onSinglePageCheckout) // ok 6518 if (_selectorAll('[name$="checkout"]')) _selectorAll('[name$="checkout"]').forEach(checkoutButton => checkoutButton.addEventListener("click", onCheckoutInitiate)) 6519 }); 6520 bodyMutationObserver.observe(_selector("body"), {subtree: true, childList: true}); 6521 6522 //------------------------------// 6523 // Event handlers // 6524 //------------------------------// 6525 6526 function onPageView() { 6527 6528 EasyStore.Event.dispatch('pages/viewed', { 6529 page: { 6530 type: template, 6531 title: document.title, 6532 description: _selector('meta[name=description]') ? _selector('meta[name=description]').getAttribute('content') : null, 6533 url: location.href, 6534 } 6535 }) 6536 6537 } 6538 6539 function onSignUp() { 6540 6541 const email_regex = new RegExp(/^[a-zA-Z0-9.!#$%&"*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/) 6542 const phone_regex = new RegExp(/^(\+?6?01)[0-46-9]-*[0-9]{7,8}$/) 6543 6544 const email_or_phone = _selector(`input[name="customer[email_or_phone]"]`).value.replace("+", "").replace(/\s+/, "") 6545 6546 let data = {} 6547 6548 if (email_regex.test(email_or_phone)) { 6549 data.method = 'email' 6550 data.email = email_or_phone 6551 } 6552 6553 if (phone_regex.test(email_or_phone)) { 6554 data.method = "phone" 6555 data.phone = email_or_phone 6556 } 6557 6558 if (!data.method) return 6559 6560 EasyStore.Event.dispatch('customers/signup', data) 6561 6562 } 6563 6564 function onLogin() { 6565 6566 const email_regex = new RegExp(/^[a-zA-Z0-9.!#$%&"*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/) 6567 const phone_regex = new RegExp(/^(\+?6?01)[0-46-9]-*[0-9]{7,8}$/) 6568 6569 const email_or_phone = _selector(`input[name="customer[email_or_phone]"]`).value.replace("+", "").replace(/\s+/, "") 6570 6571 let data = {} 6572 6573 if (email_regex.test(email_or_phone)) { 6574 data.method = 'email' 6575 data.email = email_or_phone 6576 } 6577 6578 if (phone_regex.test(email_or_phone)) { 6579 data.method = "phone" 6580 data.phone = email_or_phone 6581 } 6582 6583 if (!data.method) return 6584 6585 EasyStore.Event.dispatch('customers/login', data) 6586 6587 } 6588 6589 function onLineLogin() { 6590 6591 EasyStore.Event.dispatch('customers/login', { method: 'line' }) 6592 6593 } 6594 6595 function onLogout() { 6596 6597 EasyStore.Event.dispatch('customers/logout', { customer_id }) 6598 6599 } 6600 6601 function onBlogView() { 6602 6603 const blog = "" 6604 6605 EasyStore.Event.dispatch('blogs/viewed', { blog }) 6606 6607 } 6608 6609 function onArticleView() { 6610 6611 const article = "" 6612 6613 EasyStore.Event.dispatch('articles/viewed', { article }) 6614 6615 } 6616 6617 function onProductSearch() { 6618 6619 const query = _selector('input[name=q]').value 6620 6621 if (!query) return 6622 6623 EasyStore.Event.dispatch('products/searched', { query }) 6624 6625 } 6626 6627 function onCollectionView() { 6628 6629 const collection = "" 6630 6631 EasyStore.Event.dispatch('collections/viewed', { collection }) 6632 6633 } 6634 6635 function onProductView() { 6636 6637 const product = "" 6638 6639 EasyStore.Event.dispatch("products/viewed", { product }) 6640 6641 } 6642 6643 function onProductShare(el) { 6644 6645 const product = "" 6646 6647 const channel = el.getAttribute("class").split("-").last() 6648 6649 EasyStore.Event.dispatch('products/shared', { product, channel }) 6650 6651 } 6652 6653 function onWishlistItemAdded() { 6654 6655 let product = "" 6656 const quantity = _selector("#Quantity").value 6657 6658 EasyStore.Event.dispatch('wishlists/item_added', { product }) 6659 6660 } 6661 6662 async function onCartItemsAdded() { 6663 6664 const cart = await getCart() 6665 6666 if(cart && cart.items != undefined && cart.items.length > 0) { 6667 6668 EasyStore.Event.dispatch('carts/item_added', { cart }) 6669 6670 } 6671 6672 } 6673 6674 async function onCartView() { 6675 6676 const cart = await getCart() 6677 6678 EasyStore.Event.dispatch('carts/viewed', { cart }) 6679 6680 } 6681 6682 async function onCartItemRemoved() { 6683 6684 const cart = await getCart() 6685 6686 cart.items = [] 6687 6688 if(cart) { 6689 6690 EasyStore.Event.dispatch('carts/item_removed', { cart }) 6691 6692 } 6693 6694 } 6695 6696 async function onCheckoutInitiate() { 6697 6698 const cart = await getCart() 6699 6700 EasyStore.Event.dispatch('checkouts/initiated', { cart }) 6701 6702 } 6703 6704 async function onShippingInfoAdded(form) { 6705 let checkout = getCheckout() 6706 6707 let form_data = {} 6708 new FormData(form).forEach((value, key) => form_data[key] = value) 6709 6710 let shipping_tier = null 6711 6712 if (form_data.type && form_data.type.startsWith("r_pickup")) { 6713 shipping_tier = `Pickup - ${form_data.pick_location}` 6714 } 6715 6716 if (form_data.s_id) {
6717 shipping_tier = _selector(`input[id='`+form_data.s_id+`']`).getAttribute("data-shipping-name") 6718 } 6719 6720 if (!shipping_tier) return 6721 6722 EasyStore.Event.dispatch('checkouts/shipping_info_added', { checkout, shipping_tier }) 6723 6724 } 6725 6726 async function onPaymentInfoAdded(form) { 6727 6728 let checkout = getCheckout() 6729 6730 let payment_type = new FormData(form).get('payment_method') 6731 6732 EasyStore.Event.dispatch("checkouts/payment_info_added", { checkout, payment_type }) 6733 6734 } 6735 6736 async function onOrderPlace() { 6737 6738 let cart_token = getCookie("cart_js") 6739 let previous_cart_token = getCookie("previous_cart_ga4_js") 6740 6741 if (previous_cart_token && previous_cart_token == cart_token) { 6742 // Prevent duplicate purchase tracking 6743 return 6744 } 6745 6746 let order = getOrder() 6747 6748 EasyStore.Event.dispatch('orders/placed', { order }) 6749 6750 if(order.is_manual_payment) { 6751 6752 EasyStore.Event.dispatch('orders/purchased', { order }) 6753 6754 } 6755 6756 const last_transaction = order.transactions.last() 6757 6758 if (last_transaction.status) { 6759 6760 EasyStore.Event.dispatch('orders/purchased', { order }) 6761 EasyStore.Event.dispatch('payments/captured', { order }) 6762 6763 } 6764 6765 } 6766 6767 async function onPaymentFail() { 6768 6769 const order = getOrder() 6770 6771 EasyStore.Event.dispatch('payments/failed', { order }) 6772 6773 } 6774 6775 async function onCheckoutComplete(form) { 6776 6777 onPaymentInfoAdded(form) 6778 6779 const checkout = getCheckout() 6780 6781 EasyStore.Event.dispatch('checkouts/completed', { checkout }) 6782 6783 } 6784 6785 async function onSinglePageCheckout() { 6786 6787 // let checkout = $("[data-app-checkout]").data("app-checkout") 6788 6789 let checkout = getCheckout() 6790 6791 // const payment_type = _selector("#app_spc_payment_method").find(`[class*="label-content"]`).first().find("b").first().text() 6792 const payment_type = _selector("#app_spc_payment_method").getElementsByClassName('label-content')[0].getElementsByTagName('b')[0].innerHTML 6793 6794 const shipping_method = _selector("#delivery_method").value 6795 6796 let shipping_tier = null 6797 let app_spc_customer_info_label = _selector("#app_spc_customer_info").getElementsByClassName('label-content') 6798 if (shipping_method == "shipping") { 6799 shipping_tier = app_spc_customer_info_label[app_spc_customer_info_label.length - 1].getElementsByTagName('b')[0].innerHTML 6800 } 6801 6802 if (shipping_method == "pickup") { 6803 shipping_tier = app_spc_customer_info_label[0].innerHTML 6804 } 6805 6806 if (shipping_tier) { 6807 6808 EasyStore.Event.dispatch('checkouts/shipping_info_added', { 6809 checkout, 6810 shipping_tier, 6811 }) 6812 6813 } 6814 6815 if (payment_type) { 6816 6817 EasyStore.Event.dispatch('checkouts/payment_info_added', { 6818 checkout, 6819 payment_type, 6820 }) 6821 6822 } 6823 6824 EasyStore.Event.dispatch('checkouts/completed', { checkout }) 6825 6826 } 6827 6828 }) 6829 6830 6831 //-----------------------// 6832 // Functions // 6833 //-----------------------// 6834 6835 function getCookie(name) { 6836 6837 name += "=" 6838 6839 decodedCookie = decodeURIComponent(document.cookie) 6840 6841 ca = decodedCookie.split(";") 6842 6843 for(i = 0; i < ca.length; i++) { 6844 c = ca[i] 6845 while (c.charAt(0) == " ") { 6846 c = c.substring(1) 6847 } 6848 if (c.indexOf(name) == 0) { 6849 return c.substring(name.length, c.length) 6850 } 6851 } 6852 6853 return "" 6854 6855 } 6856 6857 const parsePrice = price => { 6858 if(typeof price === 'string') { 6859 return parseFloat(price.split(',').join('')) 6860 } 6861 6862 return price 6863 } 6864 6865 const requestCart = async (method, data) => { 6866 6867 let response = await fetch('/cart.json', {method: 'GET',headers: {'Content-Type': 'application/json'}}) 6868 result = await response.json() 6869 6870 return result.cart 6871 6872 } 6873 const getCart = async (latest = false) => { 6874 6875 let cart = window.__latest_cart 6876 6877 const cart_invalid = !cart || !cart.items || (cart.total_price > 0 && !cart.items.length) 6878 6879 if(latest || cart_invalid) { 6880 cart = await requestCart() 6881 } 6882 6883 cart.items = cart.items || [] 6884 6885 return mapCart(cart) 6886 6887 } 6888 6889 const getCheckout = async () => { 6890 mapCheckout(window.__latest_cart || await requestCart()) 6891 } 6892 6893 const getOrder = async () => { 6894 mapOrder(window.__latest_cart || await requestCart()) 6895 } 6896 6897 //-----------------------// 6898 // Mappers // 6899 //-----------------------// 6900 6901 const fallbackAttribute = (object, attribute) => { 6902 6903 if(!Array.isArray(attribute)) { 6904 attribute = [attribute] 6905 } 6906 6907 let final_value = null 6908 6909 do { 6910 6911 final_value = object[attribute.shift()] 6912 6913 } while(!final_value && attribute.length) 6914 6915 return final_value 6916 6917 } 6918 6919 const map = (object, mapper) => { 6920 6921 const newObject = {} 6922 6923 for(const key in mapper) { 6924 6925 // Handle different keys between new and old format
6926 newObject[key] = fallbackAttribute(object, mapper[key]) 6927 6928 // Cast price to float 6929 if(newObject[key] && ['price', 'amount', 'discount', 'discounts'].some(x => key.endsWith(x))) { 6930 newObject[key] = parsePrice(newObject[key]) 6931 } 6932 6933 if(newObject[key] === undefined) { 6934 delete newObject[key] 6935 } 6936 6937 } 6938 6939 return newObject 6940 6941 } 6942 6943 const mapCart = cart => { 6944 6945 const oldCart = cart 6946 6947 const mapper = { 6948 id: 'id', 6949 currency: 'currency', 6950 item_count: 'item_count', 6951 items: 'items', 6952 total_price: 'total_price', 6953 latest_items: 'latest_items' 6954 } 6955 6956 cart = map(cart, mapper) 6957 6958 const discounts = (oldCart.storewide_discounts || []).concat((oldCart.voucher_discounts || [])) 6959 6960 cart.discount_applications = discounts.map(discount => ({ 6961 title: discount.voucher_code || null, 6962 value: parsePrice(discount.amount), 6963 })) 6964 6965 cart.original_total_price = cart.items.length 6966 ? cart.items 6967 .map(item => item.original_price) 6968 .reduce((sum, price) => sum + price) 6969 : 0 6970 6971 cart.total_discount = cart.discount_applications.length 6972 ? cart.discount_applications 6973 .map(discount => parsePrice(discount.value)) 6974 .reduce((sum, value) => sum + value) 6975 : 0 6976 6977 cart.items = cart.items.map(item => mapLineItem(item)) 6978 6979 return cart 6980 6981 } 6982 6983 const mapCheckout = checkout => { 6984 6985 const mapper = { 6986 note: 'note', 6987 attributes: 'note_attributes', 6988 billing_address: 'billing_address', 6989 currency: 'currency', 6990 customer_id: 'customer_id', 6991 discounts_amount: 'total_discount', 6992 id: 'id', 6993 line_items: 'order_item', 6994 order_number: 'order_number', 6995 shipping_address: 'shipping_address', 6996 shipping_price: 'shipping_tax', 6997 shipping_method: 'shipping_method_name', 6998 tax_price: 'total_tax', 6999 } 7000 7001 checkout = map(checkout, mapper) 7002 7003 checkout.requires_shipping = checkout.line_items.some(item => item.shipping_required) 7004 7005 checkout.line_items = checkout.line_items.map(item => mapLineItem(item)) 7006 7007 return checkout 7008 7009 } 7010 7011 const mapOrder = order => { 7012 7013 const mapper = { 7014 attributes: 'note_attributes', 7015 billing_address: 'billing_address', 7016 cancelled: 'is_cancelled', 7017 cancelled_at: 'cancelled_at', 7018 created_at: 'created_at', 7019 customer_id: 'customer_id', 7020 // discount_applications: 'discount_applications', 7021 email: 'email', 7022 financial_status: 'financial_status', 7023 fulfillment_status: 'fulfillment_status', 7024 line_items: 'order_item', 7025 note: 'note', 7026 order_number: 'order_number', 7027 phone: 'phone', 7028 shipping_address: 'shipping_address', 7029 shipping_methods: 'shipping_methods', 7030 shipping_price: 'total_shipping', 7031 subtotal_price: 'subtotal_price', 7032 // tax_lines: 'tax_lines', 7033 tax_price: 'total_tax', 7034 total_discounts: 'total_discount', 7035 total_net_amount: 'total_amount_include_transaction', 7036 total_price: 'total_price', 7037 transactions: 'transaction_records', 7038 is_manual_payment: 'is_manual_payment', 7039 } 7040 7041 order.email = order.billing_address.email 7042 order.phone = order.billing_address.phone 7043 order.shipping_method = order.shipping_method_name 7044 7045 order = map(order, mapper) 7046 7047 order.line_items = order.line_items.map(item => mapLineItem(item)) 7048 order.transactions = order.transactions.map(transaction => mapTransaction(transaction)) 7049 7050 return order 7051 7052 } 7053 7054 const mapLineItem = line_item => { 7055 7056 const old_line_item = line_item 7057 7058 const mapper = { 7059 final_price: 'price', 7060 image: 'img_url', 7061 message: 'message', 7062 original_line_price: 'original_line_price', 7063 original_price: 'original_price', 7064 properties: 'properties', 7065 quantity: 'quantity', 7066 requires_shipping: 'shipping_required', 7067 sku: 'sku', 7068 taxable: 'taxable', 7069 title: 'product_name', 7070 product_name: 'product_name', 7071 url: 'url', 7072 product_id: 'product_id', 7073 variant_id: 'variant_id', 7074 id: ['id', 'i_id'], 7075 } 7076 7077 line_item = map(line_item, mapper) 7078 7079 line_item.product_id = line_item.product_id || (old_line_item.product && old_line_item.product.id) || null 7080 line_item.variant_id = line_item.variant_id || (old_line_item.variant && old_line_item.variant.id) || null 7081 line_item.image = line_item.image || (old_line_item.image && old_line_item.image.url) || null 7082 7083 return line_item 7084 7085 } 7086 7087 const mapTransaction = transaction => { 7088 7089 const mapper = { 7090 amount: 'amount', 7091 created_at: 'created_at', 7092 gateway: 'gateway_type', 7093 id: 'id', 7094 status: 'status', 7095 } 7096 7097 transaction = map(transaction, mapper) 7098 7099 return transaction 7100 7101 } 7102 7103 7104</script>
7104 7105 7106</body> 7107</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.