PageSourceSearch

https://mgsco.org/

html mgsco.org collected 2026-10-10 00:37:55 UTC 827,429 bytes, 24,671 lines download raw bytes

1
2<!DOCTYPE html>
3<html lang="id">
4<head>
5 
6  <meta charset="UTF-8">
7  <title>BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler</title>
8  <meta name="description" content="BOYTOTO hadir sebagai showroom situs boy toto kita game online yang menyediakan beragam pilihan permainan menarik dari provider ternama.">
9  <meta name="robots" content="index,follow">
10  <link rel="canonical" href="https://mgsco.org/">
11  <link rel="amphtml" href="https://mgsco.org/boykita/">
12  <link rel="alternate" href="https://mgsco.org/boykita/"> 
13  <link rel="alternate" href="https://mgsco.org/boykita/" hreflang="id">
14  <link rel="alternate" href="https://mgsco.org/boykita/" hreflang="en">
15  <link rel="alternate" href="https://mgsco.org/boykita/" hreflang="x-default">
16  <link rel="preconnect" href="https://www.googletagmanager.com" crossorigin>
vendor: 67 bytes, lines 16-17
16
17  <script async src="https://www.googletagmanager.com/gtag/js?id=
17G-0LSM1YJSPZ
vendor: 48 bytes, lines 17-18
17" nonce="se3iyzEHMc9FQEB4wSyd0w=="></script>
18  
18<script nonce="se3iyzEHMc9FQEB4wSyd0w==">window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}gtag("consent","default",{ad_storage:"denied",ad_user_data:"denied",ad_personalization:"denied",analytics_storage:"granted",wait_for_update:500});gtag("js",new Date);gtag("config","G-0LSM1YJSPZ",{send_page_view:!0,linker:{domains:["plazait.co.id","www.plazait.co.id"]}});window.ga4Ecomm={viewItem:function(e){if(!window.gtag)return;try{gtag("event","view_item",{currency:e.currency||"IDR",value:e.value||0,items:[{item_id:e.item_id||"",item_name:e.item_name||"",item_brand:e.item_brand||"",item_category:e.item_category||"",price:e.price||0,quantity:1}]})}catch(t){}},addToCart:function(e){if(!window.gtag)return;try{gtag("event","add_to_cart",{currency:e.currency||"IDR",value:(e.price||0)*(e.quantity||1),items:[{item_id:e.item_id||"",item_name:e.item_name||"",item_brand:e.item_brand||"",item_category:e.item_category||"",price:e.price||0,quantity:e.quantity||1}]})}catch(t){}}};</script>
18
19  <meta property="og:locale" content="id_ID">
20  <meta property="og:site_name" content="BOYTOTO">
21  <meta property="og:type" content="product">
22  <meta property="og:title" content="BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler">
23  <meta property="og:description" content="BOYTOTO hadir sebagai showroom situs boy toto kita game online yang menyediakan beragam pilihan permainan menarik dari provider ternama.">
24  <meta property="og:url" content="https://mgsco.org/">
25  <meta property="og:image" content="https://ik.imagekit.io/boytoto/banner235.png">
26  <meta property="og:image:alt" content="BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler">
27<meta property="product:price:amount" content="5000">
28<meta property="product:price:currency" content="IDR">
29<meta property="product:availability" content="out of stock">
30<meta name="twitter:card" content="summary_large_image">
31<meta name="twitter:title" content="BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler">
32<meta name="twitter:description" content="BOYTOTO hadir sebagai showroom situs boy toto kita game online yang menyediakan beragam pilihan permainan menarik dari provider ternama.">
33<meta name="twitter:image" content="https://ik.imagekit.io/boytoto/banner235.png">
34<meta name="google-site-verification" content="g4W1PGUKVco2_WmXJTlQv6-SYfpDi8_hcXGanUbSfWs" />
35<meta name="google-site-verification" content="Dfr9owS-fAjxuqIA9xOdZ7g5MhqB859UKXLp-Ba5Ih0" />
36<script type="application/ld+json">
37      {
38        "@context": "http://schema.org",
39        "@type": "BreadcrumbList",
40        "itemListElement": [
41          {
42            "@type": "ListItem",
43            "item": {
44              "@id": "https://mgsco.org/",
45              "name": "BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler"
46            },
47            "position": 1
48          }
49        ]
50      }
51    </script>
51
52    
52<script type="application/ld+json">
53{
54  "@context": "https://schema.org",
55  "@type": "WebPage",
56  "@id": "https://mgsco.org/#webpage",
57  "url": "https://mgsco.org/",
58  "name": "BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler",
59  "description": "BOYTOTO hadir sebagai showroom situs boy toto kita game online yang menyediakan beragam pilihan permainan menarik dari provider ternama.",
60  "inLanguage": "id-ID",
61  "primaryImageOfPage": {
62    "@type": "ImageObject",
63    "url": "https://ik.imagekit.io/boytoto/banner235.png"
64  }
65}
66</script>
66
67    
67<script type="application/ld+json">
68      {
69        "@context": "http://schema.org/",
70        "@type": "WebPage",
71        "speakable": {
72          "@type": "SpeakableSpecification",
73          "cssSelector": [".description"],
74          "xpath": ["/html/head/title"]
75        }
76      }
77    </script>
77
78    <!-- ZS -->
79    <!-- Schema JSON untuk SBO -->
80<script type="application/ld+json">
81{
82  "@context": "https://schema.org",
83  "@type": "WebSite",
84  "@id": "https://mgsco.org/#website",
85  "url": "https://mgsco.org/",
86  "name": "BOYTOTO",
87  "description": "BOYTOTO hadir sebagai showroom situs boy toto kita game online yang menyediakan beragam pilihan permainan menarik dari provider ternama.",
88  "inLanguage": "id-ID"
89}
90</script>
90
91
92<script type="application/ld+json">
93{
94  "@context": "https://schema.org",
95  "@type": "Article",
96  "@id": "https://mgsco.org/#article",
97  "url": "https://mgsco.org/",
98  "headline": "BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler",
99  "description": "BOYTOTO hadir sebagai showroom situs boy toto kita game online yang menyediakan beragam pilihan permainan menarik dari provider ternama.",
100  "image": "https://ik.imagekit.io/boytoto/banner235.png",
101  "inLanguage": "id-ID",
102  "publisher": {
103    "@type": "Organization",
104    "name": "BOYTOTO",
105    "logo": {
106      "@type": "ImageObject",
107      "url": "https://ik.imagekit.io/boytoto/logo.webp"
108    }
109  }
110}
111</script>
111
112
113<script type="application/ld+json" id="bv-jsonld-bvloader-summary">
114{
115  "@context": "https://schema.org",
116  "@type": "Organization",
117  "@id": "https://mgsco.org/#organization",
118  "name": "BOYTOTO",
119  "url": "https://mgsco.org/",
120  "logo": "https://ik.imagekit.io/boytoto/logo.webp",
121  "description": "BOYTOTO hadir sebagai showroom situs boy toto kita game online yang menyediakan beragam pilihan permainan menarik dari provider ternama."
122}
123</script>
123
124
125<script type="application/ld+json">
126{
127"@context": "https://schema.org",
128"@type": "BreadcrumbList",
129"itemListElement": [
130  {
131    "@type": "ListItem",
132    "position": 1,
133    "item": {
134      "@id": "https://mgsco.org/",
135      "name": "Game Online"
136    }
137  },
138  {
139    "@type": "ListItem",
140    "position": 2,
141    "item": {
142      "@id": "https://mgsco.org/",
143      "name": "BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler"
144    }
145  },
146  {
147    "@type": "ListItem",
148    "position": 3,
149    "item": {
150      "@id": "https://mgsco.org/",
151      "name": "Hiburan Interaktif"
152    }
153  },
154  {
155    "@type": "ListItem",
156    "position": 4,
157    "item": {
158      "@id": "https://mgsco.org/",
159      "name": "BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler"
160    }
161  }
162]
163}
164</script>
164
165
166<!-- Review rich-result markup intentionally omitted: this page is an informational article, not a reviewable Product. -->
167<script type="application/ld+json">
168{
169  "@context": "https://schema.org",
170  "@type": "FAQPage",
171  "mainEntity": [
172    {
173      "@type": "Question",
174      "name": "Apa yang membuat keseruan BOYTOTO berbeda?",
175      "acceptedAnswer": {
176        "@type": "Answer",
177        "text": "BOYTOTO menghadirkan hiburan di balik langit dengan ragam pilihan game seru kekinian. Platform ini dirancang interaktif agar setiap pengguna bisa menikmati sensasi bermain yang menyegarkan kapan saja."
178      }
179    },
180    {
181      "@type": "Question",
182      "name": "Bagaimana cara mulai menikmati permainan di BOYTOTO hari ini?",
183      "acceptedAnswer": {
184        "@type": "Answer",
185        "text": "Pilih kategori game favorit Anda dari daftar yang tersedia, lalu sesuaikan mode permainan yang diinginkan. Akses yang fleksibel memudahkan Anda langsung menjelajahi tantangan seru secara instan."
186      }
187    },
188    {
189      "@type": "Question",
190      "name": "Apakah ada banyak pilihan mode permainan yang bisa dimainkan?",
191      "acceptedAnswer": {
192        "@type": "Answer",
193        "text": "Ya. BOYTOTO menyediakan berbagai variasi permainan mulai dari tingkat santai hingga tantangan seru, sehingga setiap pemain dapat memilih mode yang paling sesuai dengan selera."
194      }
195    },
196    {
197      "@type": "Question",
198      "name": "Untuk apa variasi game dihadirkan di platform ini?",
199      "acceptedAnswer": {
200        "@type": "Answer",
201        "text": "Pilihan game yang beragam berfungsi memberikan referensi hiburan yang kaya, sehingga pengguna tidak merasa bosan dan dapat terus merasakan pengalaman bermain dengan nuansa kekinian."
202      }
203    },
204    {
205      "@type": "Question",
206      "name": "Apakah ada jaminan kemenangan dalam setiap game?",
207      "acceptedAnswer": {
208        "@type": "Answer",
209        "text": "Tidak. Setiap permainan bergantung pada keberuntungan serta strategi masing-masing pemain. Pilihan game di sini hadir sebagai media hiburan untuk dinikmati dengan seru dan menyenangkan."
210      }
211    }
212  ]
213}
214</script>
214
215  <!-- User Session Data -->
216    <meta name="csrf-token" content="9ce6e4a17f7cc189893df9e47789b13e98829450d516a0cae5ca7b5f43e5dadc">
217  <meta name="user-logged-in" content="false">
218  <meta name="user-id" content="0">
219
220  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
221
222  <link rel="shortcut icon" href="https://ik.imagekit.io/boytoto/favicon1.png" type="image/x-icon">
223  <style data-inlined="style.css">
224@font-face {
225    font-family: 'Segoe UI';
226    src: url('/assets/fonts/segoeui.woff2') format('woff2'),
227         url('/assets/fonts/segoeui.woff') format('woff');
228    font-weight: 400;
229    font-style: normal;
230}
231
232:root {
233  --brand-50:#FFF5E6;
234  --brand-100:#FFE7CC;
235  --brand-500:#F28C1A;
236  --brand-600:#D97809;
237  --gray-900:#1F1F23;
238  --gray-700:#3D3F45;
239  --gray-500:#6F737B;
240  --gray-300:#C5C9CF;
241  --gray-100:#F3F5F7;
242  --border:#E4E7EB;
243  --radius:8px;
244  --transition: .18s ease;
245  /* font-family: "Inter", system-ui, sans-serif; */
246  font-family: 'Segoe UI', sans-serif;
247}
248
249* { box-sizing: border-box; }
250body { 
251  margin:0; 
252  padding-bottom: 0; /* Reset for desktop */
253  color:var(--gray-900); 
254  background:#fff; 
255}
256img { max-width:100%; display:block; }
257
258.container {
259  width:100%;
260  max-width:1280px;
261  margin:0 auto;
262  padding:10px 0;
263}
264
265.topbar {
266  background:#111;
267  color:#fff;
268  font-size:13px;
269}
270.topbar-inner {
271  display:flex;
272  justify-content:space-between;
273  align-items:center;
274  min-height:36px;
275}
276.topbar a { color:#fff; text-decoration:none; margin-left:12px; }
277
278/* ========== Desktop base (Glassmorphism Modern with Warm Gradient) ========== */
279.navbar {
280  background:linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(255,245,230,0.85) 100%);
281  backdrop-filter:blur(12px);
282  -webkit-backdrop-filter:blur(12px);
283  border-bottom:1px solid rgba(228,231,235,0.6);
284  box-shadow:0 2px 16px -4px rgba(0,0,0,0.08);
285  position:sticky;
286  top:0;
287  z-index:500;
288  transition:all .3s ease;
289}
290
291/* Accent line gradient orange */
292.navbar::after {
293  content:'';
294  position:absolute;
295  bottom:0;
296  left:0;
297  right:0;
298  height:2px;
299  background:linear-gradient(90deg,transparent 0%, var(--brand-500) 20%, var(--brand-600) 50%, var(--brand-500) 80%, transparent 100%);
300  opacity:0.5;
301}
302
303.nav-inner {
304  display:flex;
305  align-items:center;
306  gap:24px;
307  min-height:70px;
308}
309.nav-inner .search .m-search-ico { display: none; }
310
311/* Secara default (desktop) ikon login disembunyikan; teks muncul */
312.login-btn .login-ico { display:none; }
313.navbar-logo {
314  display: none;
315  background:linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(255,245,230,0.85) 100%);
316  backdrop-filter:blur(12px);
317  -webkit-backdrop-filter:blur(12px);
318  border-bottom:1px solid rgba(228,231,235,0.6);
319  box-shadow:0 2px 16px -4px rgba(0,0,0,0.08);
320  position:sticky;
321  top:0;
322  z-index:500;
323}
324
325/* Hide bottom navigation on desktop - only show on mobile */
326.bottom-nav {
327  display: none;
328}
329
330/* ==============================================================
331   MOBILE (<=640px)
332   ============================================================== */
333@media (max-width:640px){
334  .container { padding:10px 16px; }
335
336  .topbar { display:none !important; }
337
338  /* Add padding bottom for sticky bottom nav */
339  body {
340    padding-bottom: 68px; /* Height of bottom nav + safe area */
341  }
342
343  /* Mobile Top Navbar - Clean & Minimal */
344  .navbar {
345    background:linear-gradient(135deg, rgba(255,255,255,0.97) 0%, rgba(255,245,230,0.9) 100%);
346    backdrop-filter:blur(16px);
347    -webkit-backdrop-filter:blur(16px);
348    box-shadow:0 2px 12px -3px rgba(0,0,0,0.08);
349    border: 0;
350  }
351
352  /* Simple grid layout - Logo, Search, Category */
353  .nav-inner {
354    display:grid;
355    grid-template-columns:1fr;
356    gap:10px;
357    padding:12px 16px 0 12px;
358    min-height:auto;
359  }
360
361  /* Logo centered at top */
362  .nav-inner .logo {
363    justify-self:center;
364    margin:0;
365    display:flex;
366  }
367  .nav-inner .logo img { height:36px; }
368
369  /* Search bar - full width */
370  .nav-inner .search {
371    position:relative;
372    display:flex;
373    align-items:center;
374    gap:0;
375    width:100%;
376    height:44px;
377    background:#fff;
378    border:1.5px solid #e5e7eb;
379    border-radius:12px;
380    padding:0 12px;
381    box-shadow:0 1px 3px rgba(0,0,0,0.04);
382    transition:all .25s;
383  }
384  .nav-inner .search:focus-within {
385    border-color:#fb923c;
386    box-shadow:0 0 0 3px rgba(251, 146, 60, 0.1);
387  }
388
389  .nav-inner .search-icon-btn {
390    color:#9ca3af;
391    padding:4px;
392    margin-right:4px;
393  }
394
395  .nav-inner .search-icon-btn svg {
396    width:18px;
397    height:18px;
398  }
399
400  .nav-inner .search input {
401    flex:1;
402    min-width:0;
403    border:none;
404    outline:none;
405    background:transparent;
406    font-size:14px;
407    padding:0 8px;
408    height:100%;
409  }
410
411  .nav-inner .ai-bot-toggle {
412    padding:6px;
413    margin-left:0;
414  }
415
416  .nav-inner .ai-bot-toggle .bot-icon {
417    width:20px;
418    height:20px;
419  }
420
421  .nav-inner .search-suggest {
422    left:0;
423    right:0;
424    width:calc(100vw - 32px);
425    margin-top:6px;
426    border-radius:12px;
427    max-height:70vh;
428  }
429
430  /* Hide desktop nav actions but keep structure for userMenu */
431  .nav-inner .nav-actions {
432    display:flex !important; /* Keep flex to allow userMenu positioning */
433    visibility:hidden; /* Hide visually but keep in DOM */
434    pointer-events:none; /* Disable interactions with hidden elements */
435    position:absolute; /* Remove from flow */
436    right:0;
437    top:0;
438  }
439
440  /* Re-enable userMenu for logged-in users */
441  .nav-inner .nav-actions #userMenu {
442    visibility:visible !important;
443    pointer-events:auto !important;
444  }
445
446  .nav-inner .logo { display: none; }
447  .navbar-logo {
448    display: block;
449    /* background:linear-gradient(135deg, rgba(255,255,255,0.97) 0%, rgba(255,245,230,0.9) 100%); */
450    backdrop-filter:blur(16px);
451    -webkit-backdrop-filter:blur(16px);
452    position: static; /* Remove sticky on mobile */
453    box-shadow: none;
454    border-bottom: 0;
455    padding-top: .6rem;
456  }
457  .nav-inner .logo-mobile { display: block; }
458
459  /* Category menu wrapper will handle visibility */
460
461  /* ==============================================================
462     BOTTOM NAVIGATION - Styles moved to bottom-nav.css
463     ============================================================== */
464  /* All bottom-nav styles now in /assets/css/bottom-nav.css */
465
466  /* Hide desktop elements that now moved to bottom nav */
467  #wishlistBtn,
468  #notifBtn,
469  #loginBtn { display:none !important; }
470
471  /* Hide desktop user menu on mobile - use bottom sheet instead */
472  .navbar #userMenu { 
473    display:none !important;
474  }
475
476  .nav-actions .cart-indicator,
477  .nav-actions .compare-indicator {
478    display:none !important;
479  }
480
481  /* Hide category button from bottom nav */
482  #bottomCategoryBtn {
483    display: none !important;
484  }
485
486  /* User menu button styling for mobile - scoped to navbar only */
487  .navbar #userMenu .login-btn.logged-in {
488    width:40px;
489    height:40px;
490    padding:0;
491    border-radius:50%;
492    display:flex;
493    align-items:center;
494    justify-content:center;
495    background:rgba(255,255,255,0.95);
496    backdrop-filter:blur(12px);
497    -webkit-backdrop-filter:blur(12px);
498    border:1px solid rgba(228,231,235,0.6);
499    box-shadow:0 2px 12px -3px rgba(0,0,0,0.12);
500  }
501
502  .navbar #userMenu .login-btn.logged-in .login-text { 
503    display:none; 
504  }
505
506  .navbar #userMenu .login-btn.logged-in .login-ico {
507    display:inline;
508    font-size:20px;
509    line-height:1;
510  }
511
512  /* User dropdown positioning for mobile */
513  .navbar #userMenu .user-dropdown {
514    position:fixed !important;
515    top:60px !important;
516    right:16px !important;
517    left:auto !important;
518    min-width:200px;
519    background:rgba(255,255,255,0.98);
520    backdrop-filter:blur(20px);
521    -webkit-backdrop-filter:blur(20px);
522    border:1px solid rgba(228,231,235,0.6);
523    box-shadow:0 8px 24px -8px rgba(0,0,0,0.2);
524    z-index:1002 !important;
525    border-radius:12px;
526    padding:8px 0;
527  }
528
529  /* Force visibility when not hidden attribute */
530  .navbar #userMenu .user-dropdown:not([hidden]) {
531    display:block !important;
532    visibility:visible !important;
533    opacity:1 !important;
534  }
535
536  /* Container untuk kategori dan tombol kategori sejajar */
537  .navbar .container:last-child {
538    display:flex !important;
539    align-items:center !important;
540    padding:4px 16px 6px !important;
541    gap:5px;
542    overflow:visible;
543  }
544
545  /* Wrapper untuk mega-cats dengan fade gradient (mobile only) */
546  .mega-cats-wrapper {
547    position:relative;
548    flex:1;
549    overflow:hidden;
550    min-height:34px; /* Ensure content is visible */
551    display:flex; /* Ensure wrapper is flex container */
552  }
553
554  /* Fade gradient indicator di ujung kanan (mobile only) */
555  .mega-cats-fade {
556    position:absolute;
557    top:0;
558    right:0;
559    bottom:0;
560    width:80px; /* Increased from 60px for better visibility */
561    background:linear-gradient(to left, 
562      rgba(255,245,230,1) 0%, 
563      rgba(255,245,230,0.98) 15%,
564      rgba(255,245,230,0.85) 35%, 
565      transparent 100%);
566    pointer-events:none;
567    z-index:5; /* Increased z-index */
568    transition:opacity 0.3s ease;
569  }
570
571  /* Kategori menu di mobile */
572  .mega-cats.container {
573    display:flex !important;
574    gap:12px;
575    overflow-x:auto;
576    overflow-y:visible;
577    -webkit-overflow-scrolling:touch;
578    scrollbar-width:none;
579    flex:1;
580    padding:0 !important;
581    margin:0 !important;
582    position:relative; /* For z-index stacking */
583    z-index:1; /* Below fade gradient */
584  }
585  .mega-cats.container::-webkit-scrollbar { display:none; }
586
587  /* Styling untuk menu items di mobile */
588  .mega-cats a {
589    font-size:13px;
590    padding:6px 10px;
591    white-space:nowrap;
592    flex-shrink:0;
593    position:relative; /* Ensure proper stacking */
594  }
595  .mega-cats a.active {
596    background:var(--brand-600);
597    color:#fff;
598    font-weight:600;
599    border-radius:6px;
600  }
601
602  /* Tombol kategori di mobile */
603  .mm-wp {
604    flex-shrink:0;
605  }
606  .mm2-trigger {
607    display:flex;
608    align-items:center;
609    gap:6px;
610    padding:6px 12px;
611    background:var(--gray-100);
612    color:var(--gray-700);
613    text-decoration:none;
614    border-radius:6px;
615    font-size:13px;
616    white-space:nowrap;
617  }
618
619  .mm2-trigger .label-cat {display: none; }
620  /* Tombol more kategori */
621  .mega-more-btn {
622    border:1px solid var(--border);
623    background:#fff;
624    border-radius:10px;
625    padding:6px 14px;
626    font-size:18px;
627    cursor:pointer;
628  }
629  .mega-cats-more.active > .mega-more-btn {
630    background:var(--brand-600);
631    color:#fff;
632    border-color:var(--brand-600);
633  }
634  .mega-more-dropdown {
635    top:calc(100% + 10px);
636    right:0;
637    min-width:85vw;
638    max-width:90vw;
639    border-radius:16px;
640  }
641  .mega-more-grid {
642    grid-template-columns:repeat(auto-fill,minmax(120px,1fr));
643  }
644
645  /* ==============================================================
646     MOBILE USER BOTTOM SHEET
647     ============================================================== */
648  .mobile-user-sheet {
649    position:fixed;
650    top:0;
651    left:0;
652    right:0;
653    bottom:0;
654    z-index:10000;
655    pointer-events:none;
656  }
657
658  .mobile-user-sheet.active {
659    pointer-events:auto;
660  }
661
662  .mobile-user-sheet-backdrop {
663    position:absolute;
664    top:0;
665    left:0;
666    right:0;
667    bottom:0;
668    background:rgba(0,0,0,0.5);
669    opacity:0;
670    transition:opacity .3s ease;
671  }
672
673  .mobile-user-sheet.active .mobile-user-sheet-backdrop {
674    opacity:1;
675  }
676
677  .mobile-user-sheet-content {
678    position:absolute;
679    bottom:0;
680    left:0;
681    right:0;
682    background:#fff;
683    border-radius:20px 20px 0 0;
684    box-shadow:0 -4px 24px rgba(0,0,0,0.15);
685    transform:translateY(100%);
686    transition:transform .3s cubic-bezier(0.4, 0.0, 0.2, 1);
687    padding-bottom:calc(68px + env(safe-area-inset-bottom));
688  }
689
690  .mobile-user-sheet.active .mobile-user-sheet-content {
691    transform:translateY(0);
692  }
693
694  .mobile-user-sheet-header {
695    padding:16px 20px 12px;
696    border-bottom:1px solid #e5e7eb;
697  }
698
699  .mobile-user-sheet-handle {
700    width:40px;
701    height:4px;
702    background:#d1d5db;
703    border-radius:2px;
704    margin:0 auto 16px;
705  }
706
707  .mobile-user-info {
708    display:flex;
709    align-items:center;
710    gap:12px;
711  }
712
713  .mobile-user-avatar {
714    width:48px;
715    height:48px;
716    border-radius:50%;
717    background:linear-gradient(135deg, #FFE7CC, #FFD9A8);
718    display:flex;
719    align-items:center;
720    justify-content:center;
721    font-size:24px;
722    flex-shrink:0;
723  }
724
725  .mobile-user-details {
726    flex:1;
727    min-width:0;
728  }
729
730  .mobile-user-name {
731    font-size:16px;
732    font-weight:600;
733    color:#111827;
734    margin-bottom:2px;
735    white-space:nowrap;
736    overflow:hidden;
737    text-overflow:ellipsis;
738  }
739
740  .mobile-user-email {
741    font-size:13px;
742    color:#6b7280;
743    white-space:nowrap;
744    overflow:hidden;
745    text-overflow:ellipsis;
746  }
747
748  .mobile-user-sheet-body {
749    padding:8px 0;
750  }
751
752  .mobile-user-sheet-item {
753    display:flex;
754    align-items:center;
755    gap:16px;
756    padding:14px 20px;
757    text-decoration:none;
758    color:#374151;
759    font-size:15px;
760    transition:background .2s;
761    border:none;
762    background:none;
763    width:100%;
764    text-align:left;
765    cursor:pointer;
766  }
767
768  .mobile-user-sheet-item:active {
769    background:#f3f4f6;
770  }
771
772  .mobile-user-sheet-icon {
773    font-size:20px;
774    width:24px;
775    text-align:center;
776    flex-shrink:0;
777  }
778
779  .mobile-user-sheet-label {
780    flex:1;
781    font-weight:500;
782  }
783
784  .mobile-user-sheet-divider {
785    height:1px;
786    background:#e5e7eb;
787    margin:8px 0;
788  }
789}
790
791/* END NAVBAR */
792
793.logo img { display:block; }
794.search {
795  flex:1;
796  position:relative;
797  display:flex;
798  align-items:center;
799  gap:0;
800  background:#fff;
801  border:1.5px solid #e5e7eb;
802  border-radius:12px;
803  padding:0 16px;
804  height:38px;
805  transition:all 0.3s ease;
806}
807.search:focus-within {
808  border-color:#fb923c;
809  box-shadow:0 0 0 3px rgba(251, 146, 60, 0.1);
810}
811.search input {
812  flex:1;
813  border:none;
814  outline:none;
815  background:transparent;
816  font-size:15px;
817  padding:0 12px;
818  height:100%;
819  color:#1f2937;
820}
821.search input::placeholder {
822  color:#9ca3af;
823}
824.search-icon-btn {
825  background:transparent;
826  border:none;
827  padding:0;
828  display:flex;
829  align-items:center;
830  justify-content:center;
831  cursor:pointer;
832  color:#6b7280;
833  transition:color 0.2s ease;
834  flex-shrink:0;
835}
836.search-icon-btn:hover {
837  color:#fb923c;
838}
839.search-suggest {
840  position:absolute;
841  left:0; right:0; top:100%;
842  background:#fff;
843  border:1px solid var(--border);
844  border-radius:var(--radius);
845  padding:8px 0;
846  box-shadow:0 8px 20px rgba(0,0,0,.08);
847  margin-top:4px;
848  font-size:14px;
849  max-height:260px;
850  overflow:auto;
851}
852.search-suggest div {
853  padding:6px 12px;
854  cursor:pointer;
855}
856.search-suggest div:hover { background:var(--gray-100); }
857
858.nav-actions { display:flex; gap:10px; align-items:center; }
859.icon-btn, .login-btn {
860  background:linear-gradient(135deg, rgba(255,255,255,0.95), rgba(249,250,251,0.9));
861  border:1px solid rgba(228,231,235,0.6);
862  padding:8px 12px;
863  border-radius:var(--radius);
864  cursor:pointer;
865  font-size:14px;
866  transition:all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
867  height: 38px;
868  position:relative;
869  box-shadow:
870    0 1px 3px rgba(0,0,0,0.06),
871    0 0 0 1px rgba(255,255,255,0.5) inset;
872  backdrop-filter:blur(8px);
873  -webkit-backdrop-filter:blur(8px);
874}
875.icon-btn:hover, .login-btn:hover { 
876  background:linear-gradient(135deg, rgba(249,250,251,1), rgba(243,244,246,0.95));
877  border-color:rgba(209,213,219,0.8);
878  box-shadow:
879    0 2px 8px rgba(0,0,0,0.08),
880    0 0 0 1px rgba(255,255,255,0.6) inset;
881  transform:translateY(-1px);
882}
883.cart-indicator, .compare-indicator { position:relative; }
884.badge {
885  background:linear-gradient(135deg, #EF4444, #DC2626);
886  color:#fff;
887  font-size:10.5px;
888  font-weight:600;
889  padding:2px 6px;
890  border-radius:999px;
891  position:absolute;
892  top:-6px; 
893  right:-6px;
894  min-width:18px;
895  height:18px;
896  display:flex;
897  align-items:center;
898  justify-content:center;
899  box-shadow:
900    0 2px 8px rgba(239,68,68,0.4),
901    0 0 0 2px #fff,
902    0 0 12px -2px rgba(239,68,68,0.6);
903  transition:all 0.3s ease;
904}
905.icon-btn:hover .badge {
906  transform:scale(1.08);
907  box-shadow:
908    0 4px 12px rgba(239,68,68,0.5),
909    0 0 0 2px #fff,
910    0 0 16px -2px rgba(239,68,68,0.7);
911}
912.badge.is-empty { display:none; }
913
914/* User Menu Dropdown */
915.user-menu {
916  position:relative;
917}
918.user-dropdown {
919  position:absolute;
920  top:calc(100% + 8px);
921  right:0;
922  background:#fff;
923  border:1px solid var(--border);
924  border-radius:12px;
925  box-shadow:0 10px 40px rgba(0,0,0,0.12);
926  min-width:200px;
927  padding:8px 0;
928  z-index:9999;
929}
930
931/* Ensure dropdown is visible when not hidden */
932.user-dropdown:not([hidden]) {
933  display:block !important;
934  visibility:visible !important;
935  opacity:1 !important;
936}
937
938/* Mobile: Force user dropdown to be visible above everything */
939@media (max-width:640px) {
940  .user-dropdown:not([hidden]) {
941    position:fixed !important;
942    z-index:9999 !important;
943    top:60px !important;
944    right:16px !important;
945  }
946}
947
948.dropdown-item {
949  display:flex;
950  align-items:center;
951  gap:12px;
952  padding:10px 16px;
953  text-decoration:none;
954  color:var(--gray-700);
955  font-size:14px;
956  transition:var(--transition);
957  border:none;
958  background:none;
959  width:100%;
960  text-align:left;
961  cursor:pointer;
962}
963.dropdown-item:hover {
964  background:var(--gray-100);
965  color:var(--gray-900);
966}
967.dropdown-icon {
968  font-size:16px;
969  opacity:0.7;
970}
971.dropdown-separator {
972  height:1px;
973  background:var(--border);
974  margin:8px 0;
975}
976.login-btn.logged-in {
977  background:linear-gradient(135deg, rgba(255,245,230,0.8), rgba(255,231,204,0.6));
978  border:1px solid rgba(242,140,26,0.2);
979  color:var(--brand-700);
980  max-width:200px; /* Prevent button from getting too wide */
981  display:inline-flex;
982  align-items:center;
983  gap:10px;
984  padding:4px 12px 4px 4px;
985  transition:all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
986  position:relative;
987  overflow:hidden;
988  box-shadow:
989    0 1px 3px rgba(242,140,26,0.08),
990    0 0 0 1px rgba(255,255,255,0.5) inset,
991    0 0 12px -6px rgba(242,140,26,0.1);
992  backdrop-filter:blur(8px);
993  -webkit-backdrop-filter:blur(8px);
994}
995/* Subtle background glow effect */
996.login-btn.logged-in::before {
997  content:'';
998  position:absolute;
999  top:50%;
1000  left:0;
1001  width:32px;
1002  height:32px;
1003  background:radial-gradient(circle, rgba(242,140,26,0.15), transparent 70%);
1004  transform:translateY(-50%);
1005  opacity:0;
1006  transition:opacity 0.3s ease;
1007}
1008.login-btn.logged-in:hover::before {
1009  opacity:1;
1010}
1011.login-btn.logged-in .user-avatar {
1012  width:32px;
1013  height:32px;
1014  border-radius:50%;
1015  background:linear-gradient(135deg, #FFE7CC 0%, #FFD9A8 50%, #FFCC80 100%);
1016  color:#D97809;
1017  font-size:11.5px;
1018  font-weight:700;
1019  display:flex;
1020  align-items:center;
1021  justify-content:center;
1022  flex-shrink:0;
1023  letter-spacing:0.3px;
1024  position:relative;
1025  box-shadow:
1026    0 2px 8px rgba(242,140,26,0.25),
1027    0 1px 3px rgba(0,0,0,0.12),
1028    inset 0 1px 0 rgba(255,255,255,0.5);
1029  transition:all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1030}
1031/* Ring gradient border */
1032.login-btn.logged-in .user-avatar::before {
1033  content:'';
1034  position:absolute;
1035  inset:-2px;
1036  border-radius:50%;
1037  padding:2px;
1038  background:linear-gradient(135deg, #F28C1A, #FFD9A8, #F28C1A);
1039  -webkit-mask:linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
1040  -webkit-mask-composite:xor;
1041  mask:linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
1042  mask-composite:exclude;
1043  opacity:0;
1044  transition:opacity 0.3s ease;
1045}
1046.login-btn.logged-in:hover .user-avatar::before {
1047  opacity:1;
1048}
1049.login-btn.logged-in:hover .user-avatar {
1050  transform:scale(1.08) rotate(-3deg);
1051  box-shadow:
1052    0 4px 12px rgba(242,140,26,0.35),
1053    0 2px 6px rgba(0,0,0,0.15),
1054    inset 0 1px 0 rgba(255,255,255,0.6);
1055}
1056.login-btn.logged-in .login-text {
1057  overflow:hidden;
1058  text-overflow:ellipsis;
1059  white-space:nowrap;
1060  flex:1;
1061  min-width:0;
1062  font-weight:500;
1063  font-size:14px;
1064  transition:color 0.2s ease;
1065}
1066.login-btn.logged-in:hover .login-text {
1067  color:var(--brand-600);
1068}
1069.login-btn.logged-in:hover {
1070  background:linear-gradient(135deg, rgba(255,245,230,0.95), rgba(255,231,204,0.85));
1071  border-color:rgba(242,140,26,0.3);
1072  box-shadow:
1073    0 4px 16px -4px rgba(242,140,26,0.25),
1074    0 0 0 1px rgba(242,140,26,0.1) inset,
1075    0 1px 2px rgba(0,0,0,0.05);
1076  transform:translateY(-1px);
1077}
1078
1079/* Desktop: hide wrapper elements, show menu normally */
1080@media (min-width: 761px) {
1081  .mega-cats-wrapper {
1082    display:contents; /* Remove wrapper from layout */
1083  }
1084  .mega-cats-fade {
1085    display:none !important; /* Hide fade on desktop */
1086  }
1087}
1088
1089.mega-cats {
1090  display:flex;
1091  gap:5px;
1092  /* padding:6px 16px 10px; */
1093  scrollbar-width:none;
1094  position: relative;
1095  z-index: 510;
1096  overflow-x:auto;
1097  -webkit-overflow-scrolling:touch;
1098  flex:1;
1099}
1100.mega-cats::-webkit-scrollbar { 
1101  display:none; 
1102}
1103.mega-cats a {
1104  text-decoration:none;
1105  font-size:14px;
1106  color:var(--gray-700);
1107  padding:6px 10px;
1108  border-radius:6px;
1109  white-space:nowrap;
1110  transition:var(--transition);
1111}
1112.mega-cats a:hover { background:var(--brand-100); color:var(--brand-600); }
1113/*.mega-cats::after {
1114  content:'';
1115  position:absolute;
1116  right:0;
1117  top:0;
1118  width:40px;
1119  height:100%;
1120  background:linear-gradient(to right,rgba(255,255,255,0),#fff);
1121  pointer-events:none;
1122}*/
1123
1124/* Tambahan untuk tombol More & dropdown.
1125   Style dasar .mega-cats dan .mega-cats a tetap pakai definisi Anda. */
1126
1127.mega-cats-more {
1128  position:relative;
1129  display:flex;
1130  align-items:center;
1131}
1132
1133.mega-cats-more.active > .mega-more-btn {
1134  background:var(--brand-600);
1135  color:#fff;
1136}
1137
1138.mega-more-btn {
1139  cursor:pointer;
1140  border:none;
1141  background:var(--gray-100,#f5f5f5);
1142  color:var(--gray-700,#333);
1143  padding:6px 12px;
1144  font-size:16px;
1145  line-height:1;
1146  border-radius:6px;
1147  display:flex;
1148  align-items:center;
1149  justify-content:center;
1150  transition:var(--transition);
1151}
1152.mega-more-btn:hover {
1153  background:var(--brand-100);
1154  color:var(--brand-600);
1155}
1156
1157.mega-more-dropdown {
1158  position:absolute;
1159  top:100%;
1160  right:0;
1161  margin-top:8px;
1162  background:#fff;
1163  border:1px solid var(--gray-200,#e3e6e9);
1164  border-radius:10px;
1165  padding:12px 14px 14px;
1166  min-width:400px;
1167  max-width:520px;
1168  max-height:420px;
1169  box-shadow:0 12px 32px -8px rgba(25,40,65,.25);
1170  z-index: 3000; 
1171}
1172
1173.mega-more-grid {
1174  display:grid;
1175  grid-template-columns:repeat(auto-fill,minmax(120px,1fr));
1176  gap:8px 10px;
1177  overflow-x: hidden;
1178  max-height:380px;
1179  overscroll-behavior:contain;
1180  scrollbar-width:thin;
1181}
1182.mega-more-grid::-webkit-scrollbar {
1183  width:6px;
1184}
1185.mega-more-grid::-webkit-scrollbar-thumb {
1186  background:var(--gray-300,#c7ccd1);
1187  border-radius:3px;
1188}
1189
1190/* Link di dropdown mewarisi style .mega-cats a (warna, padding, radius).
1191   Kita tambahkan state active agar konsisten. */
1192.mega-more-grid a.active {
1193  background:var(--brand-600);
1194  color:#fff;
1195  font-weight:600;
1196}
1197.hero {
1198  background:linear-gradient(135deg,var(--brand-50), #fff);
1199}
1200.hero-inner {
1201  display:grid;
1202  grid-template-columns:repeat(auto-fit,minmax(320px,1fr));
1203  gap:48px;
1204  align-items:center;
1205}
1206.hero-text h1 {
1207  font-size:48px;
1208  line-height:1.1;
1209  margin:0;
1210  font-weight:600;
1211  font-family:"Poppins",sans-serif;
1212}
1213.hero-text .text-brand { color:var(--brand-500); }
1214.lead { font-size:18px; color:var(--gray-700); margin-top:16px; }
1215.hero-cta { margin-top:28px; display:flex; gap:16px; }
1216.hero-bullets { margin-top:28px; display:flex; gap:28px; font-size:14px; color:var(--gray-600); }
1217.hero-box {
1218  background:#fff;
1219  padding:10px;
1220  border-radius:16px;
1221  box-shadow:0 4px 18px rgba(0,0,0,.08);
1222  /* min-height:320px; */
1223  display:flex;
1224  align-items:center;
1225  justify-content:center;
1226}
1227.hero-box img {border-radius: 10px; }
1228
1229@media (max-width:680px) {
1230    .hero-inner { gap: 25px; }    
1231}
1232
1233.usp-section {
1234  position:relative;
1235  background:
1236    linear-gradient(180deg,#FFFDFB 0%, #FFFFFF 60%) ;
1237  border-top:1px solid var(--border);
1238  border-bottom:1px solid var(--border);
1239  padding:10px 0 10px;
1240}
1241
1242.usp-list {
1243  list-style:none;
1244  margin:0;
1245  padding:0;
1246  display:grid;
1247  gap:26px;
1248}
1249
1250.usp-list.variant-soft {
1251  grid-template-columns:repeat(auto-fit,minmax(230px,1fr));
1252}
1253
1254.usp-item {
1255  display:flex;
1256  gap:16px;
1257  align-items:flex-start;
1258  background:#fff;
1259  padding:18px 18px 18px 16px;
1260  border:1px solid rgba(226,226,226,0.7);
1261  border-radius:18px;
1262  position:relative;
1263  overflow:hidden;
1264  isolation:isolate;
1265  box-shadow:0 2px 4px -1px rgba(0,0,0,.04), 0 6px 18px -6px rgba(0,0,0,.08);
1266  transition: .35s cubic-bezier(.16,.8,.32,1);
1267}
1268
1269.usp-item::before {
1270  content:"";
1271  position:absolute;
1272  inset:0;
1273  background:radial-gradient(circle at 18% 22%, rgba(242,140,26,.18), transparent 60%);
1274  opacity:.65;
1275  transition:.5s;
1276  z-index:-1;
1277}
1278
1279.usp-item:hover {
1280  transform:translateY(-6px);
1281  box-shadow:0 10px 26px -8px rgba(0,0,0,.15), 0 3px 8px -2px rgba(0,0,0,.08);
1282  border-color:rgba(242,140,26,.35);
1283}
1284
1285.usp-item:hover::before {
1286  background:radial-gradient(circle at 70% 40%, rgba(242,140,26,.22), transparent 65%);
1287}
1288
1289.usp-icon {
1290  flex:0 0 56px;
1291  width:56px;
1292  height:56px;
1293  border-radius:16px;
1294  display:flex;
1295  align-items:center;
1296  justify-content:center;
1297  background:
1298    linear-gradient(135deg,#FFE7CC,#FFD9A8);
1299  position:relative;
1300  box-shadow:inset 0 0 0 1px rgba(255,255,255,.6), 0 4px 10px -3px rgba(0,0,0,.15);
1301  transition:.45s;
1302}
1303
1304.usp-item:hover .usp-icon {
1305  transform:rotate(-4deg) scale(1.06);
1306  box-shadow:inset 0 0 0 1px rgba(255,255,255,.8), 0 6px 14px -4px rgba(0,0,0,.20);
1307}
1308
1309.usp-icon svg {
1310  width:28px;
1311  height:28px;
1312  display:block;
1313  filter:drop-shadow(0 2px 2px rgba(0,0,0,.18));
1314}
1315
1316.usp-text h3 {
1317  font-size:15px;
1318  font-weight:600;
1319  margin:2px 0 6px;
1320  letter-spacing:.2px;
1321}
1322
1323.usp-text p {
1324  font-size:13px;
1325  line-height:1.3;
1326  margin:0;
1327  color:var(--gray-600);
1328}
1329
1330@media (max-width:680px) {
1331  .usp-section { padding: 5px 0 5px; margin-bottom: 0.75rem;}
1332  .usp-item { padding:16px 14px; }
1333  .usp-icon { width:50px;height:50px;border-radius:14px; }
1334  .usp-text h3 { font-size:14px; margin-bottom:4px; }
1335  .usp-text p { font-size:12.5px; }
1336  .usp-list { gap: 10px; }
1337}
1338
1339.flash { padding:50px 0 30px; background:#fff; }
1340.flash-head {
1341  display:flex; align-items:center;
1342  justify-content:space-between;
1343  margin-bottom:20px;
1344}
1345.flash-head h2 { margin:0; font-size:24px; font-family:"Poppins"; }
1346.flash .countdown {
1347  font-family:monospace;
1348  background:#111;
1349  color:#fff;
1350  padding:8px 14px;
1351  border-radius:8px;
1352  font-size:16px;
1353  letter-spacing:1px;
1354}
1355.flash-scroll {
1356  display:flex;
1357  gap:16px;
1358  overflow-x:auto;
1359  padding-bottom:6px;
1360  scrollbar-width:none;
1361}
1362.flash-scroll::-webkit-scrollbar { display:none; }
1363
1364.products { padding:60px 0; background:#fff; }
1365.products-head {
1366  display:flex; justify-content:space-between; align-items:center;
1367  margin-bottom:24px;
1368  flex-wrap:wrap;
1369  gap:16px;
1370}
1371.products-head h2 { margin:0; font-size:24px; font-family:"Poppins"; }
1372.product-filters input {
1373  padding:8px 12px;
1374  border:1px solid var(--border);
1375  border-radius:8px;
1376  font-size:14px;
1377  width:220px;
1378}
1379
1380.product-grid {
1381  display:grid;
1382  gap:24px;
1383  grid-template-columns:repeat(auto-fill,minmax(180px,1fr));
1384}
1385
1386.prod-card {
1387  background:#fff;
1388  border:1px solid var(--border);
1389  padding:12px;
1390  border-radius:12px;
1391  position:relative;
1392  display:flex;
1393  flex-direction:column;
1394  transition:var(--transition);
1395}
1396.prod-card.small { min-width:160px; }
1397.prod-card:hover { box-shadow:0 8px 20px rgba(0,0,0,.12); transform:translateY(-3px); }
1398.prod-card .img-box {
1399  background:var(--gray-100);
1400  border-radius:10px;
1401  aspect-ratio:1/1;
1402  display:flex;
1403  align-items:center;
1404  justify-content:center;
1405  overflow:hidden;
1406}
1407.prod-card .img-box img { width:100%; height:100%; object-fit:contain; transition:var(--transition); }
1408.prod-card:hover .img-box img { transform:scale(1.04); }
1409.prod-card .name {
1410  font-size:13.5px;
1411  font-weight:500;
1412  margin:10px 0 6px;
1413  min-height:36px;
1414  line-height:1.3;
1415}
1416.price {
1417  display:flex;
1418  gap:6px;
1419  align-items:center;
1420  flex-wrap:wrap;
1421}
1422.price strong { color:var(--brand-600); font-size:14px; }
1423.price .old {
1424  font-size:11px;
1425  color:var(--gray-500);
1426  text-decoration:line-through;
1427}
1428.tag.discount {
1429  position:absolute;
1430  top:8px; left:8px;
1431  background:#E54848;
1432  color:#fff;
1433  font-size:11px;
1434  padding:4px 6px;
1435  border-radius:6px;
1436  font-weight:600;
1437}
1438.btn {
1439  border:none;
1440  cursor:pointer;
1441  font-weight:500;
1442  font-size:14px;
1443  padding:12px 22px;
1444  border-radius:10px;
1445  background:var(--gray-900);
1446  color:#fff;
1447  transition:var(--transition);
1448  text-decoration:none;
1449  text-align:center;
1450  display:inline-flex;
1451  align-items:center;
1452  justify-content:center;
1453  gap:6px;
1454}
1455.btn:hover { background:#000; }
1456.btn-primary { background:var(--brand-500); }
1457.btn-primary:hover { background:var(--brand-600); }
1458.btn-outline {
1459  background:#fff;
1460  color:var(--gray-900);
1461  border:1px solid var(--border);
1462}
1463.btn-outline:hover { background:var(--gray-100); }
1464.btn-sm { padding:8px 14px; font-size:13px; }
1465.btn-mini { padding:6px 10px; font-size:12px; }
1466.add-cart { margin-top:auto; background:var(--brand-500); color:#fff; }
1467.add-cart:hover { background:var(--brand-600); }
1468
1469/* ==== FOOTER COMPACT FIX PATCH (final) ==== */
1470
1471/* 1. Pastikan grid aktif */
1472
1473.site-footer.footer-compact .ft-grid {
1474  display:grid;
1475  grid-template-columns:repeat(auto-fit,minmax(240px,1fr));
1476  gap:30px 40px;
1477  align-items:start;
1478}
1479
1480/* 2. Spacing atas bawah dipadatkan */
1481.site-footer.footer-compact .ft-top {
1482  padding:42px 0 26px;
1483}
1484
1485/* 3. Judul & teks */
1486.site-footer.footer-compact .ft-title {
1487  margin:0 0 14px;
1488  font-size:14px;
1489  letter-spacing:.8px;
1490  text-transform:uppercase;
1491  font-weight:600;
1492  color:#82b1c9;
1493}
1494
1495.site-footer.footer-compact .ft-desc,
1496.site-footer.footer-compact .ft-text {
1497  margin:0 0 16px;
1498  font-size:13.5px;
1499  line-height:1.45;
1500  color:#c6d6e0;
1501  max-width:330px;
1502}
1503.site-footer.footer-compact .ft-desc.sm-gap { margin-top:18px; }
1504
1505/* 4. Reset ikon SVG agar tidak membesar */
1506.site-footer.footer-compact svg {
1507  width:20px;
1508  height:20px;
1509  flex:0 0 20px;
1510  display:inline-block;
1511}
1512.site-footer.footer-compact .ft-social svg { width:18px; height:18px; }
1513.site-footer.footer-compact .ft-wa-btn svg { width:18px; height:18px; }
1514
1515/* Jika ada aturan global svg { width:100%; } — paksa override */
1516svg { max-width:100%; }
1517.site-footer.footer-compact svg {
1518  width:20px !important;
1519  height:20px !important;
1520}
1521
1522/* 5. Link list */
1523.site-footer.footer-compact .ft-links {
1524  list-style:none;
1525  margin:0 0 12px;
1526  padding:0;
1527  display:flex;
1528  flex-direction:column;
1529  gap:4px;
1530}
1531.site-footer.footer-compact .ft-links li { margin:0; }
1532.site-footer.footer-compact .ft-links a {
1533  display:flex;
1534  align-items:center;
1535  gap:8px;
1536  font-size:13.5px;
1537  padding:5px 2px;
1538  text-decoration:none;
1539  color:#c6d6e0;
1540  border-radius:8px;
1541  transition:.25s;
1542}
1543.site-footer.footer-compact .ft-links a:hover {
1544  background:rgba(255,255,255,.06);
1545  padding-left:6px;
1546  color:#fff;
1547}
1548
1549.site-footer.footer-compact .ico-s {
1550  width:24px;
1551  height:24px;
1552  background:#103346;
1553  color:#89b8cf;
1554  border-radius:6px;
1555  display:flex;
1556  align-items:center;
1557  justify-content:center;
1558  flex:0 0 24px;
1559  font-size:12px;
1560  transition:.3s;
1561}
1562.site-footer.footer-compact .ft-links a:hover .ico-s {
1563  background:#F28C1A;
1564  color:#fff;
1565  transform:rotate(-4deg);
1566}
1567
1568/* 6. WhatsApp button */
1569.site-footer.footer-compact .ft-wa-btn {
1570  display:inline-flex;
1571  align-items:center;
1572  gap:8px;
1573  background:#fff;
1574  color:#062437;
1575  padding:8px 18px;
1576  font-size:13.5px;
1577  border-radius:22px;
1578  font-weight:500;
1579  text-decoration:none;
1580  box-shadow:0 3px 8px -3px rgba(0,0,0,.45);
1581  transition:.25s;
1582}
1583.site-footer.footer-compact .ft-wa-btn:hover {
1584  transform:translateY(-2px);
1585  box-shadow:0 6px 14px -6px rgba(0,0,0,.55);
1586}
1587
1588/* 7. Sosial */
1589.site-footer.footer-compact .ft-social {
1590  display:flex;
1591  gap:12px;
1592  margin-top:4px;
1593}
1594.site-footer.footer-compact .ft-social a {
1595  width:34px;
1596  height:34px;
1597  background:rgba(255,255,255,.08);
1598  display:flex;
1599  align-items:center;
1600  justify-content:center;
1601  border-radius:10px;
1602  color:#c6d6e0;
1603  transition:.25s;
1604}
1605.site-footer.footer-compact .ft-social a:hover {
1606  background:#F28C1A;
1607  color:#fff;
1608}
1609
1610/* 8. Alamat */
1611.site-footer.footer-compact .ft-address {
1612  font-size:12.5px;
1613  line-height:1.44;
1614  color:#8fa4b1;
1615  max-width:330px;
1616  margin-top:12px;
1617}
1618.site-footer.footer-compact .ft-address .company {
1619  margin:0 0 4px;
1620  font-weight:600;
1621  color:#e5f2f7;
1622  font-size:12.8px;
1623}
1624
1625/* 9. Payments kecil & rapi */
1626.site-footer.footer-compact .ft-payments {
1627  margin-top:14px;
1628  display:flex;
1629  flex-direction:column;
1630  gap:8px;
1631  max-width:350px;
1632}
1633.site-footer.footer-compact .ft-payments .pay-row {
1634  display:flex;
1635  gap:8px;
1636  flex-wrap:wrap;
1637}
1638.site-footer.footer-compact .ft-payments img {
1639  width:46px;
1640  height:32px;
1641  padding:3px 5px;
1642  background:#fff;
1643  border-radius:7px;
1644  object-fit:contain;
1645  box-shadow:0 2px 6px -3px rgba(0,0,0,.45);
1646  transition:.25s;
1647}
1648.site-footer.footer-compact .ft-payments img:hover {
1649  transform:translateY(-3px);
1650  box-shadow:0 5px 12px -6px rgba(0,0,0,.55);
1651}
1652
1653/* 10. Bottom bar */
1654.site-footer.footer-compact .ft-bottom {
1655  border-top:1px solid rgba(255,255,255,.08);
1656  background:#041f31;
1657  padding:12px 0 16px;
1658  margin-top:4px;
1659}
1660.site-footer.footer-compact .ft-copy {
1661  font-size:11.5px;
1662  color:#8fa7b5;
1663  display:flex;
1664  flex-wrap:wrap;
1665  gap:12px;
1666  justify-content:center;
1667  line-height:1.3;
1668  margin:0;
1669}
1670.site-footer.footer-compact .ft-copy span {
1671  position:relative;
1672  padding:0 10px;
1673}
1674.site-footer.footer-compact .ft-copy span+span:before {
1675  content:"";
1676  position:absolute;
1677  left:0;
1678  top:50%;
1679  width:1px;
1680  height:12px;
1681  background:rgba(255,255,255,.14);
1682  transform:translateY(-50%);
1683}
1684@media (max-width:580px){
1685  .site-footer.footer-compact .ft-top { padding:34px 0 18px; }
1686  .site-footer.footer-compact .ft-grid { gap:26px 20px; }
1687  .site-footer.footer-compact .ft-copy span { padding:0; }
1688  .site-footer.footer-compact .ft-copy span+span:before { display:none; }
1689}
1690.copy {
1691  margin-top:40px;
1692  border-top:1px solid #222;
1693  text-align:center;
1694  padding:18px 0;
1695  font-size:12px;
1696  color:#666;
1697}
1698
1699@media (max-width:760px) {
1700  .hero-text h1 { font-size:38px; }
1701  .hero { padding:0px 0 30px; }
1702  .topbar-inner { flex-direction:column; gap:4px; padding:6px 0; }
1703  .nav-inner { flex-wrap:wrap; }
1704  .mega-cats { gap:10px; }
1705  .product-grid { gap:16px; }
1706  .prod-card { padding:10px; }
1707}
1708
1709/* === PLAZA IT DARK ORANGE FOOTER THEME === */
1710.site-footer.footer-compact {
1711  --ft-bg-0:#051722;
1712  --ft-bg-1:#072534;
1713  --ft-bg-2:#0B2F40;
1714  --ft-bg-3:#103A4E;
1715  --ft-orange:#F28C1A;
1716  --ft-orange-dark:#D97809;
1717  --ft-text:#E9F2F6;
1718  --ft-text-sec:#B8CAD3;
1719  --ft-text-muted:#8FA4B1;
1720  --ft-divider:rgba(255,255,255,0.08);
1721  --ft-glow:rgba(242,140,26,0.18);
1722  --ft-glow-fade:rgba(242,140,26,0);
1723  color:var(--ft-text);
1724}
1725
1726/* BASE TEXT / LINKS (override sebelumnya jika perlu) */
1727.site-footer.footer-compact .ft-title { color:#9CC5D7; }
1728.site-footer.footer-compact .ft-links a { color:var(--ft-text-sec); }
1729.site-footer.footer-compact .ft-links a:hover { color:#fff; }
1730.site-footer.footer-compact .ft-address { color:var(--ft-text-muted); }
1731.site-footer.footer-compact .ft-copy { color:#9AB2BD; }
1732
1733/* ICON BACKGROUND (kubu gelap) */
1734.site-footer.footer-compact .ico-s {
1735  background:#0F3647;
1736  color:#9FC7DA;
1737}
1738.site-footer.footer-compact .ft-links a:hover .ico-s {
1739  background:var(--ft-orange);
1740  color:#fff;
1741}
1742
1743/* WhatsApp button biarkan putih agar kontras */
1744.site-footer.footer-compact .ft-wa-btn {
1745  box-shadow:0 4px 12px -5px rgba(0,0,0,.6), 0 0 0 3px rgba(255,255,255,0.05);
1746}
1747.site-footer.footer-compact .ft-wa-btn:hover {
1748  box-shadow:0 8px 20px -8px rgba(0,0,0,.65), 0 0 0 3px rgba(242,140,26,0.25);
1749}
1750
1751/* Payment cards border halus */
1752.site-footer.footer-compact .ft-payments img {
1753  box-shadow:0 2px 6px -3px rgba(0,0,0,.55), 0 0 0 1px rgba(255,255,255,0.05);
1754}
1755.site-footer.footer-compact .ft-payments img:hover {
1756  box-shadow:0 6px 14px -6px rgba(0,0,0,.65), 0 0 0 1px rgba(242,140,26,0.4);
1757}
1758
1759/* ========== VARIANT A: DIAGONAL EMBER (AKTIF) ========== */
1760.site-footer.footer-compact.variant-ember {
1761  background:
1762    radial-gradient(circle at 78% 82%, var(--ft-glow) 0%, var(--ft-glow-fade) 55%),
1763    linear-gradient(140deg,var(--ft-bg-1) 0%, var(--ft-bg-2) 55%, var(--ft-bg-0) 100%);
1764}
1765.site-footer.footer-compact.variant-ember .ft-bottom {
1766  background:rgba(0,0,0,0.25);
1767  backdrop-filter:blur(2px);
1768}
1769
1770/* ========== VARIANT B: TOP BAR ORANGE ========== */
1771.site-footer.footer-compact.variant-bar {
1772  background:var(--ft-bg-1);
1773  position:relative;
1774}
1775.site-footer.footer-compact.variant-bar::before {
1776  content:"";
1777  position:absolute;
1778  left:0;top:0;
1779  width:100%;height:3px;
1780  background:linear-gradient(90deg,var(--ft-orange-dark),var(--ft-orange));
1781  box-shadow:0 0 12px -2px var(--ft-orange);
1782}
1783.site-footer.footer-compact.variant-bar .ft-bottom {
1784  background:#041c28;
1785}
1786
1787/* ========== VARIANT C: RADIAL GLOW BAWAH ========== */
1788.site-footer.footer-compact.variant-radial {
1789  background:
1790    radial-gradient(circle at 70% 90%, rgba(242,140,26,0.22) 0%, rgba(242,140,26,0.04) 38%, rgba(242,140,26,0) 65%),
1791    linear-gradient(150deg,#052131 0%, #062d3f 50%, #041924 100%);
1792}
1793.site-footer.footer-compact.variant-radial .ft-bottom {
1794  background:#041c29;
1795}
1796
1797/* ========== VARIANT D: LAYERED STRIPES ========== */
1798.site-footer.footer-compact.variant-stripes {
1799  background:
1800    repeating-linear-gradient(180deg, rgba(255,255,255,0.015) 0 38px, rgba(255,255,255,0) 38px 76px),
1801    linear-gradient(130deg,#052636 0%, #062a3c 60%, #041d28 100%);
1802  position:relative;
1803}
1804.site-footer.footer-compact.variant-stripes::after {
1805  content:"";
1806  position:absolute;
1807  left:0;bottom:0;
1808  width:100%;height:110px;
1809  background:linear-gradient(0deg,rgba(0,0,0,.35),rgba(0,0,0,0));
1810  pointer-events:none;
1811}
1812.site-footer.footer-compact.variant-stripes .ft-bottom {
1813  background:#031821;
1814}
1815
1816/* Garis aksen oranye tipis di atas bottom */
1817.site-footer.footer-compact.variant-stripes .ft-bottom {
1818  position:relative;
1819}
1820.site-footer.footer-compact.variant-stripes .ft-bottom::before {
1821  content:"";
1822  position:absolute;
1823  left:0;top:0;
1824  width:100%;height:2px;
1825  background:linear-gradient(90deg,rgba(242,140,26,0),rgba(242,140,26,.65),rgba(242,140,26,0));
1826}
1827
1828/* ========== VARIANT E: GLASS PANEL ========== */
1829.site-footer.footer-compact.variant-glass {
1830  background:linear-gradient(145deg,#051c29 0%, #041620 65%, #03121b 100%);
1831}
1832.site-footer.footer-compact.variant-glass .ft-grid {
1833  position:relative;
1834  padding:32px clamp(10px,2vw,24px);
1835  border-radius:22px;
1836  background:linear-gradient(140deg,rgba(255,255,255,0.06),rgba(255,255,255,0.02));
1837  border:1px solid rgba(255,255,255,0.08);
1838  box-shadow:0 12px 28px -12px rgba(0,0,0,0.45), 0 0 0 1px rgba(255,255,255,0.04) inset;
1839  backdrop-filter:blur(8px);
1840  -webkit-backdrop-filter:blur(8px);
1841}
1842.site-footer.footer-compact.variant-glass .ft-bottom {
1843  background:#02141d;
1844}
1845
1846/* HOVER ACCENTS */
1847.site-footer.footer-compact a {
1848  transition:color .25s, background .25s, box-shadow .25s;
1849}
1850.site-footer.footer-compact .ft-links a:hover {
1851  box-shadow:0 2px 6px -3px rgba(0,0,0,.5),
1852             0 0 0 1px rgba(242,140,26,0.55);
1853}
1854.site-footer.footer-compact .ft-social a {
1855  background:#0E3445;
1856  color:#bcd4dd;
1857}
1858.site-footer.footer-compact .ft-social a:hover {
1859  background:var(--ft-orange);
1860  color:#fff;
1861  box-shadow:0 0 0 3px rgba(242,140,26,0.28);
1862}
1863
1864/* Divider di atas footer (opsional) */
1865.footer-divider-top {
1866  height:1px;
1867  background:linear-gradient(90deg,transparent,rgba(255,255,255,0.15),transparent);
1868  width:100%;
1869  margin:0 0 4px;
1870}
1871
1872/* ==== MOBILE FOOTER COLLAPSE (Non-Homepage Only) ==== */
1873
1874/* Desktop: wrapper tidak mengganggu grid layout */
1875.ft-collapsible-content {
1876  display: contents;
1877}
1878
1879@media (max-width: 768px) {
1880  /* Wrapper untuk konten yang bisa collapse */
1881  .ft-top.ft-collapsible .ft-collapsible-content {
1882    display: block;
1883    max-height: 80px;
1884    overflow: hidden;
1885    position: relative;
1886    transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1);
1887  }
1888
1889  /* Fade gradient overlay */
1890  .ft-top.ft-collapsible .ft-collapsible-content::after {
1891    content: '';
1892    position: absolute;
1893    bottom: 0;
1894    left: 0;
1895    right: 0;
1896    height: 70px;
1897    background: linear-gradient(to bottom, transparent 0%, rgba(6, 43, 69, 0.6) 40%, #062b45 100%);
1898    pointer-events: none;
1899    opacity: 1;
1900    transition: opacity 0.3s ease;
1901    z-index: 1;
1902  }
1903
1904  /* State: Expanded */
1905  .ft-top.ft-collapsible.expanded .ft-collapsible-content {
1906    max-height: 1500px;
1907  }
1908
1909  .ft-top.ft-collapsible.expanded .ft-collapsible-content::after {
1910    opacity: 0;
1911  }
1912
1913  /* Expand Button - Di luar collapsible, selalu visible */
1914  .ft-expand-btn {
1915    display: flex;
1916    align-items: center;
1917    justify-content: center;
1918    gap: 8px;
1919    width: 100%;
1920    margin: -8px 0 0;
1921    padding: 12px 20px;
1922    background: rgba(255,255,255,0.12);
1923    border: 1px solid rgba(255,255,255,0.25);
1924    border-radius: 10px;
1925    color: #e5f2f7;
1926    font-size: 13.5px;
1927    font-weight: 500;
1928    cursor: pointer;
1929    transition: all 0.3s ease;
1930    outline: none;
1931    position: relative;
1932    z-index: 10;
1933  }
1934
1935  .ft-expand-btn:hover {
1936    background: rgba(255,255,255,0.18);
1937    border-color: rgba(255,255,255,0.35);
1938    color: #fff;
1939    transform: translateY(-2px);
1940  }
1941
1942  .ft-expand-btn:active {
1943    transform: scale(0.98);
1944  }
1945
1946  .ft-expand-btn svg {
1947    width: 18px;
1948    height: 18px;
1949    transition: transform 0.3s ease;
1950    fill: currentColor;
1951  }
1952
1953  .ft-top.ft-collapsible.expanded .ft-expand-btn {
1954    margin-top: 15px;
1955  }
1956
1957  .ft-top.ft-collapsible.expanded .ft-expand-btn svg {
1958    transform: rotate(180deg);
1959  }
1960}
1961
1962/* Desktop: hide expand button */
1963@media (min-width: 769px) {
1964  .ft-expand-btn {
1965    display: none !important;
1966  }
1967}
1968
1969/* RESPONSIVE (jika background panel butuh penyesuaian) */
1970@media (max-width:640px){
1971  .site-footer.footer-compact.variant-glass .ft-grid {
1972    padding:26px 18px;
1973  }
1974}
1975
1976</style>
1977  <style data-inlined="bottom-nav.css">
1978/* Mobile Bottom Navigation */
1979.bottom-nav {
1980  position: fixed;
1981  bottom: 0;
1982  left: 0;
1983  right: 0;
1984  background: #ffffff;
1985  border-top: 1px solid #e5e7eb;
1986  z-index: 9998; /* Below chat widget */
1987  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
1988  display: none; /* Hidden on desktop */
1989}
1990
1991.bottom-nav-inner {
1992  display: flex;
1993  justify-content: space-around;
1994  align-items: center;
1995  padding: 2px 0;
1996  max-width: 640px;
1997  margin: 0 auto;
1998}
1999
2000.bottom-nav-item {
2001  flex: 1;
2002  display: flex;
2003  flex-direction: column;
2004  align-items: center;
2005  justify-content: center;
2006  gap: 4px;
2007  padding: 8px 8px;
2008  background: none;
2009  border: none;
2010  cursor: pointer;
2011  text-decoration: none;
2012  color: #6b7280;
2013  transition: all 0.3s ease;
2014  position: relative;
2015}
2016
2017.bottom-nav-item:hover {
2018  color: #F28C1A;
2019}
2020
2021.bottom-nav-item.active {
2022  flex-direction: row;
2023  gap: 8px;
2024  background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
2025  color: white;
2026  border-radius: 20px;
2027  padding: 8px 16px;
2028  box-shadow: 0 2px 8px rgba(242, 140, 26, 0.3);
2029}
2030
2031.bottom-nav-item-icon {
2032  width: 24px;
2033  height: 24px;
2034  position: relative;
2035}
2036
2037.bottom-nav-item-icon svg {
2038  width: 100%;
2039  height: 100%;
2040}
2041
2042/* Icon variant switching */
2043.bottom-nav-item .icon-outlined {
2044  display: block;
2045}
2046
2047.bottom-nav-item .icon-filled {
2048  display: none;
2049}
2050
2051.bottom-nav-item.active .icon-outlined {
2052  display: none;
2053}
2054
2055.bottom-nav-item.active .icon-filled {
2056  display: block;
2057}
2058
2059.bottom-nav-item-label {
2060  font-size: 12px;
2061  font-weight: 600;
2062  line-height: 1;
2063  display: none;
2064  white-space: nowrap;
2065}
2066
2067.bottom-nav-item.active .bottom-nav-item-label {
2068  display: block;
2069}
2070
2071/* Avatar styles for bottom-nav */
2072.bottom-nav-avatar-img,
2073.bottom-nav-avatar-initials {
2074  width: 24px;
2075  height: 24px;
2076  border-radius: 50%;
2077  display: block;
2078}
2079
2080.bottom-nav-avatar-img {
2081  object-fit: cover;
2082}
2083
2084.bottom-nav-avatar-initials {
2085  background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
2086  color: white;
2087  font-size: 10px;
2088  font-weight: 700;
2089  display: flex;
2090  align-items: center;
2091  justify-content: center;
2092  line-height: 1;
2093  letter-spacing: 0.5px;
2094}
2095
2096/* Hide SVG when avatar is shown */
2097.bottom-nav-item-icon:has(.bottom-nav-avatar-img) .bottom-nav-svg-icon,
2098.bottom-nav-item-icon:has(.bottom-nav-avatar-initials) .bottom-nav-svg-icon {
2099  display: none;
2100}
2101
2102/* Active state adjustments for avatar */
2103.bottom-nav-item.active .bottom-nav-avatar-initials {
2104  background: white;
2105  color: #F28C1A;
2106  border: 2px solid rgba(255, 255, 255, 0.8);
2107  box-sizing: border-box;
2108}
2109
2110.bottom-nav-item.active .bottom-nav-avatar-img {
2111  border: 2px solid rgba(255, 255, 255, 0.9);
2112  box-sizing: border-box;
2113}
2114
2115.bottom-nav-item-badge {
2116  position: absolute;
2117  top: -4px;
2118  right: -4px;
2119  background: #ef4444;
2120  color: white;
2121  border-radius: 10px;
2122  padding: 2px 5px;
2123  font-size: 10px;
2124  font-weight: 600;
2125  min-width: 18px;
2126  height: 18px;
2127  display: flex;
2128  align-items: center;
2129  justify-content: center;
2130  line-height: 1;
2131}
2132
2133/* Show on mobile only */
2134@media (max-width: 640px) {
2135  .bottom-nav {
2136    display: block;
2137  }
2138
2139  /* Add bottom padding to body to prevent content being hidden */
2140  body {
2141    padding-bottom: 60px;
2142  }
2143}
2144
2145/* Mobile User Sheet - Bottom Sheet for Profile Menu */
2146.mobile-user-sheet {
2147  position: fixed;
2148  bottom: 0;
2149  left: 0;
2150  right: 0;
2151  z-index: 10000;
2152  pointer-events: none;
2153}
2154
2155.mobile-user-sheet:not([hidden]) {
2156  pointer-events: auto;
2157}
2158
2159.mobile-user-sheet-backdrop {
2160  position: fixed;
2161  inset: 0;
2162  background: rgba(0, 0, 0, 0.5);
2163  opacity: 0;
2164  transition: opacity 0.2s ease-out;
2165}
2166
2167.mobile-user-sheet.active .mobile-user-sheet-backdrop {
2168  opacity: 1;
2169}
2170
2171.mobile-user-sheet-content {
2172  position: absolute;
2173  bottom: 0;
2174  left: 0;
2175  right: 0;
2176  background: white;
2177  border-radius: 16px 16px 0 0;
2178  max-height: 80vh;
2179  overflow-y: auto;
2180  transform: translateY(100%);
2181  transition: transform 0.3s ease-out;
2182}
2183
2184.mobile-user-sheet.active .mobile-user-sheet-content {
2185  transform: translateY(0);
2186}
2187
2188.mobile-user-sheet-handle {
2189  width: 40px;
2190  height: 4px;
2191  background: #d1d5db;
2192  border-radius: 2px;
2193  margin: 12px auto 16px;
2194}
2195
2196.mobile-user-sheet-header {
2197  padding: 0 20px 16px;
2198  border-bottom: 1px solid #e5e7eb;
2199}
2200
2201.mobile-user-info {
2202  display: flex;
2203  align-items: center;
2204  gap: 12px;
2205}
2206
2207.mobile-user-avatar {
2208  width: 48px;
2209  height: 48px;
2210  background: linear-gradient(135deg, #FFE7CC 0%, #FFD9A8 50%, #FFCC80 100%);
2211  border-radius: 50%;
2212  display: flex;
2213  align-items: center;
2214  justify-content: center;
2215  font-size: 24px;
2216  color: #D97809;
2217  font-weight: 600;
2218}
2219
2220.mobile-user-details {
2221  flex: 1;
2222}
2223
2224.mobile-user-name {
2225  font-weight: 600;
2226  font-size: 16px;
2227  color: #1f2937;
2228  margin-bottom: 4px;
2229}
2230
2231.mobile-user-email {
2232  font-size: 13px;
2233  color: #6b7280;
2234}
2235
2236.mobile-user-sheet-body {
2237  padding: 8px 0;
2238}
2239
2240.mobile-user-sheet-item {
2241  display: flex;
2242  align-items: center;
2243  gap: 12px;
2244  padding: 14px 20px;
2245  background: none;
2246  border: none;
2247  width: 100%;
2248  text-align: left;
2249  cursor: pointer;
2250  text-decoration: none;
2251  color: #374151;
2252  transition: background 0.2s;
2253}
2254
2255.mobile-user-sheet-item:hover {
2256  background: #f9fafb;
2257}
2258
2259.mobile-user-sheet-icon {
2260  font-size: 20px;
2261  width: 24px;
2262  text-align: center;
2263}
2264
2265.mobile-user-sheet-label {
2266  font-size: 15px;
2267  font-weight: 500;
2268}
2269
2270.mobile-user-sheet-divider {
2271  height: 1px;
2272  background: #e5e7eb;
2273  margin: 8px 0;
2274}
2275
2276@keyframes fadeIn {
2277  from {
2278    opacity: 0;
2279  }
2280  to {
2281    opacity: 1;
2282  }
2283}
2284
2285@keyframes slideUp {
2286  from {
2287    transform: translateY(100%);
2288  }
2289  to {
2290    transform: translateY(0);
2291  }
2292}
2293
2294/* Ensure chat widget works well with bottom nav */
2295@media (max-width: 640px) {
2296  .chat-widget {
2297    bottom: 0 !important; /* Chat opens from bottom */
2298  }
2299
2300  .chat-popup {
2301    /* Chat popup should be above bottom nav */
2302    z-index: 9999;
2303  }
2304}
2305
2306</style>
2307  <style data-inlined="search-suggest.css">
2308.search { 
2309  position: relative; 
2310}
2311
2312/* AI Bot Toggle Button */
2313.ai-bot-toggle {
2314  display: flex;
2315  align-items: center;
2316  justify-content: center;
2317  background: transparent;
2318  border: none;
2319  padding: 8px;
2320  cursor: pointer;
2321  border-radius: 8px;
2322  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
2323  margin-left: 4px;
2324  flex-shrink: 0;
2325  display: none;
2326}
2327
2328.ai-bot-toggle .bot-icon {
2329  color: #9ca3af;
2330  transition: all 0.3s ease;
2331}
2332
2333.ai-bot-toggle:hover .bot-icon {
2334  color: #6b7280;
2335  transform: scale(1.1);
2336}
2337
2338.ai-bot-toggle.active .bot-icon {
2339  color: #fb923c;
2340  animation: botPulse 2s ease-in-out infinite;
2341}
2342
2343.ai-bot-toggle.active {
2344  background: rgba(251, 146, 60, 0.1);
2345}
2346
2347.ai-bot-toggle.active:hover .bot-icon {
2348  color: #f97316;
2349}
2350
2351@keyframes botPulse {
2352  0%, 100% { transform: scale(1); }
2353  50% { transform: scale(1.12); }
2354}
2355
2356/* Search Suggest Box */
2357.search-suggest {
2358  position: absolute;
2359  top: 100%; left: 0; right: 0;
2360  background: #fff;
2361  border: 1px solid #e1e4e8;
2362  border-top: none;
2363  box-shadow: 0 4px 12px rgba(0,0,0,0.08);
2364  max-height: 400px;
2365  overflow-y: auto;
2366  z-index: 100;
2367  padding: 0;
2368  font-size: 14px;
2369  border-radius: 0 0 8px 8px;
2370}
2371
2372.suggest-item {
2373  display: flex;
2374  align-items: center;
2375  gap: 8px;
2376  padding: 10px 16px;
2377  cursor: pointer;
2378  transition: all 0.2s ease;
2379  border-bottom: 1px solid #f1f5f9;
2380}
2381
2382.suggest-item:last-child {
2383  border-bottom: none;
2384}
2385
2386.suggest-item:hover,
2387.suggest-item.active {
2388  background: linear-gradient(90deg, #f8fafc 0%, #f1f5f9 100%);
2389  padding-left: 20px;
2390}
2391
2392.suggest-thumb {
2393  width: 42px;
2394  height: 42px;
2395  object-fit: cover;
2396  border: 1px solid #ddd;
2397  border-radius: 4px;
2398  background: #fafafa;
2399}
2400
2401.suggest-meta { flex: 1; min-width: 0; }
2402
2403.suggest-name {
2404  font-weight: 500;
2405  color: #222;
2406  line-height: 1.2;
2407  white-space: nowrap;
2408  overflow: hidden;
2409  text-overflow: ellipsis;
2410}
2411
2412.suggest-price {
2413  color: #d32f2f;
2414  font-size: 12px;
2415  margin-top: 2px;
2416}
2417
2418/* AI Insights */
2419.suggest-ai-insight {
2420  display: flex;
2421  align-items: center;
2422  gap: 8px;
2423  padding: 8px 12px;
2424  background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
2425  border-bottom: 1px solid #bae6fd;
2426  color: #0369a1;
2427  font-size: 12px;
2428  font-weight: 500;
2429  line-height: 1.4;
2430}
2431
2432.suggest-ai-insight svg {
2433  flex-shrink: 0;
2434  opacity: 0.8;
2435}
2436
2437.suggest-ai-insight span {
2438  flex: 1;
2439}
2440
2441/* AI Progress States */
2442.ai-progress-container {
2443  padding: 20px 16px;
2444  background: linear-gradient(135deg, #fafbff 0%, #f5f7ff 100%);
2445}
2446
2447.ai-progress-header {
2448  display: flex;
2449  align-items: center;
2450  gap: 10px;
2451  margin-bottom: 16px;
2452}
2453
2454.ai-progress-icon {
2455  width: 36px;
2456  height: 36px;
2457  display: flex;
2458  align-items: center;
2459  justify-content: center;
2460  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
2461  border-radius: 50%;
2462  color: #fff;
2463  font-size: 18px;
2464  animation: aiIconSpin 3s linear infinite;
2465}
2466
2467@keyframes aiIconSpin {
2468  0% { transform: rotate(0deg); }
2469  100% { transform: rotate(360deg); }
2470}
2471
2472.ai-progress-text {
2473  flex: 1;
2474}
2475
2476.ai-progress-title {
2477  font-weight: 600;
2478  color: #1e293b;
2479  font-size: 14px;
2480  margin-bottom: 2px;
2481}
2482
2483.ai-progress-subtitle {
2484  font-size: 12px;
2485  color: #64748b;
2486}
2487
2488/* Progress Steps */
2489.ai-progress-steps {
2490  display: flex;
2491  flex-direction: column;
2492  gap: 10px;
2493  margin-top: 16px;
2494}
2495
2496.ai-progress-step {
2497  display: flex;
2498  align-items: center;
2499  gap: 10px;
2500  padding: 8px 12px;
2501  background: #fff;
2502  border-radius: 8px;
2503  border: 1px solid #e2e8f0;
2504  transition: all 0.3s ease;
2505}
2506
2507.ai-progress-step.active {
2508  border-color: #667eea;
2509  background: #f8f9ff;
2510  box-shadow: 0 2px 8px rgba(102, 126, 234, 0.1);
2511}
2512
2513.ai-progress-step.completed {
2514  border-color: #10b981;
2515  background: #f0fdf4;
2516}
2517
2518.ai-step-icon {
2519  width: 20px;
2520  height: 20px;
2521  display: flex;
2522  align-items: center;
2523  justify-content: center;
2524  border-radius: 50%;
2525  font-size: 11px;
2526  flex-shrink: 0;
2527}
2528
2529.ai-progress-step.active .ai-step-icon {
2530  background: #667eea;
2531  color: #fff;
2532  animation: pulse 1.5s ease-in-out infinite;
2533}
2534
2535.ai-progress-step.completed .ai-step-icon {
2536  background: #10b981;
2537  color: #fff;
2538}
2539
2540.ai-progress-step.pending .ai-step-icon {
2541  background: #e2e8f0;
2542  color: #94a3b8;
2543}
2544
2545.ai-step-text {
2546  flex: 1;
2547  font-size: 13px;
2548  color: #475569;
2549  font-weight: 500;
2550}
2551
2552.ai-progress-step.completed .ai-step-text {
2553  color: #059669;
2554}
2555
2556/* AI Analysis Result Banner */
2557.ai-result-banner {
2558  padding: 12px 16px;
2559  background: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%);
2560  border-bottom: 1px solid #86efac;
2561  display: flex;
2562  align-items: center;
2563  gap: 10px;
2564}
2565
2566.ai-result-icon {
2567  font-size: 20px;
2568  animation: bounceIn 0.5s ease;
2569}
2570
2571@keyframes bounceIn {
2572  0% { transform: scale(0); opacity: 0; }
2573  50% { transform: scale(1.1); }
2574  100% { transform: scale(1); opacity: 1; }
2575}
2576
2577.ai-result-text {
2578  flex: 1;
2579}
2580
2581.ai-result-title {
2582  font-weight: 600;
2583  color: #065f46;
2584  font-size: 13px;
2585  margin-bottom: 2px;
2586}
2587
2588.ai-result-description {
2589  font-size: 12px;
2590  color: #047857;
2591  opacity: 0.9;
2592}
2593
2594/* Product Items Section Header */
2595.suggest-section-header {
2596  padding: 10px 16px;
2597  background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
2598  border-bottom: 1px solid #e2e8f0;
2599}
2600
2601.suggest-header-main {
2602  display: flex;
2603  align-items: center;
2604  gap: 6px;
2605  margin-bottom: 4px;
2606}
2607
2608.suggest-header-icon {
2609  font-size: 14px;
2610}
2611
2612.suggest-header-text {
2613  font-size: 12px;
2614  font-weight: 600;
2615  color: #64748b;
2616  text-transform: uppercase;
2617  letter-spacing: 0.5px;
2618}
2619
2620.suggest-header-hint {
2621  font-size: 11px;
2622  color: #94a3b8;
2623  font-weight: 400;
2624  text-transform: none;
2625  letter-spacing: 0;
2626  display: flex;
2627  align-items: center;
2628  gap: 4px;
2629}
2630
2631.suggest-header-hint kbd {
2632  display: inline-block;
2633  padding: 2px 6px;
2634  font-size: 10px;
2635  font-weight: 600;
2636  line-height: 1;
2637  color: #475569;
2638  background: #fff;
2639  border: 1px solid #cbd5e1;
2640  border-radius: 4px;
2641  box-shadow: 0 1px 2px rgba(0,0,0,0.05);
2642  font-family: 'Segoe UI', monospace;
2643}
2644
2645/* Footer Hint */
2646.suggest-footer-hint {
2647  display: flex;
2648  align-items: center;
2649  justify-content: center;
2650  padding: 12px 16px;
2651  background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
2652  border-top: 1px solid #fbbf24;
2653  color: #92400e;
2654  font-size: 12px;
2655  font-weight: 500;
2656  text-align: center;
2657  cursor: default;
2658}
2659
2660.suggest-footer-hint svg {
2661  color: #f59e0b;
2662  flex-shrink: 0;
2663}
2664
2665.suggest-footer-hint kbd {
2666  display: inline-block;
2667  padding: 3px 7px;
2668  margin: 0 3px;
2669  font-size: 11px;
2670  font-weight: 600;
2671  line-height: 1;
2672  color: #78350f;
2673  background: #fff;
2674  border: 1px solid #f59e0b;
2675  border-radius: 4px;
2676  box-shadow: 0 1px 3px rgba(245, 158, 11, 0.2);
2677  font-family: 'Segoe UI', monospace;
2678}
2679
2680.suggest-footer-link {
2681  color: #ea580c;
2682  font-weight: 600;
2683  text-decoration: underline;
2684  text-underline-offset: 2px;
2685  text-decoration-thickness: 1px;
2686  cursor: pointer;
2687  transition: all 0.2s ease;
2688}
2689
2690.suggest-footer-link:hover {
2691  color: #c2410c;
2692  text-decoration-thickness: 2px;
2693  text-shadow: 0 0 8px rgba(234, 88, 12, 0.3);
2694}
2695
2696/* Loading state */
2697.suggest-loading {
2698  display: flex;
2699  align-items: center;
2700  justify-content: center;
2701  gap: 8px;
2702  padding: 16px 12px;
2703  color: #64748b;
2704  font-size: 13px;
2705}
2706
2707@keyframes pulse {
2708  0%, 100% { opacity: 1; }
2709  50% { opacity: 0.5; }
2710}
2711
2712.suggest-loading {
2713  animation: pulse 1.5s ease-in-out infinite;
2714}
2715
2716/* Responsive Mobile Styles */
2717@media (max-width: 768px) {
2718  .ai-bot-toggle {
2719    padding: 6px;
2720    margin-left: 0;
2721  }
2722
2723  .ai-bot-toggle .bot-icon {
2724    width: 20px;
2725    height: 20px;
2726  }
2727
2728  .search-icon-btn {
2729    margin-right: 4px;
2730  }
2731
2732  .search-icon-btn svg {
2733    width: 18px;
2734    height: 18px;
2735  }
2736
2737  .search-suggest {
2738    max-height: 350px;
2739  }
2740
2741  /* Mobile hint adjustments */
2742  .suggest-header-hint {
2743    font-size: 10px;
2744  }
2745
2746  .suggest-header-hint kbd {
2747    padding: 1px 4px;
2748    font-size: 9px;
2749  }
2750
2751  .suggest-footer-hint {
2752    padding: 10px 12px;
2753    font-size: 11px;
2754  }
2755
2756  .suggest-footer-hint svg {
2757    width: 14px;
2758    height: 14px;
2759  }
2760
2761  .suggest-footer-hint kbd {
2762    padding: 2px 5px;
2763    font-size: 10px;
2764  }
2765
2766  .ai-progress-container {
2767    padding: 16px 12px;
2768  }
2769
2770  .ai-progress-icon {
2771    width: 32px;
2772    height: 32px;
2773    font-size: 16px;
2774  }
2775
2776  .ai-progress-title {
2777    font-size: 13px;
2778  }
2779
2780  .ai-progress-subtitle {
2781    font-size: 11px;
2782  }
2783
2784  .ai-progress-step {
2785    padding: 6px 10px;
2786  }
2787
2788  .ai-step-text {
2789    font-size: 12px;
2790  }
2791
2792  .suggest-item {
2793    padding: 8px 12px;
2794  }
2795
2796  .suggest-thumb {
2797    width: 36px;
2798    height: 36px;
2799  }
2800
2801  .suggest-name {
2802    font-size: 13px;
2803  }
2804
2805  .suggest-price {
2806    font-size: 11px;
2807  }
2808}
2809
2810@media (max-width: 480px) {
2811  .ai-bot-toggle {
2812    padding: 4px;
2813  }
2814
2815  .ai-bot-toggle .bot-icon {
2816    width: 18px;
2817    height: 18px;
2818  }
2819
2820  .search-icon-btn svg {
2821    width: 16px;
2822    height: 16px;
2823  }
2824
2825  /* Ultra-compact hints for small screens */
2826  .suggest-header-hint {
2827    display: none; /* Hide header hint on very small screens */
2828  }
2829
2830  .suggest-footer-hint {
2831    padding: 8px 10px;
2832    font-size: 10px;
2833    flex-direction: column;
2834    gap: 4px;
2835  }
2836
2837  .suggest-footer-hint svg {
2838    display: none; /* Hide icon to save space */
2839  }
2840
2841  .suggest-footer-hint kbd {
2842    padding: 2px 4px;
2843    font-size: 9px;
2844  }
2845
2846  .ai-progress-steps {
2847    gap: 8px;
2848  }
2849
2850  .ai-step-text {
2851    font-size: 11px;
2852  }
2853}
2854
2855/* AI Search Prompt Box */
2856.ai-search-prompt {
2857  padding: 24px;
2858  background: linear-gradient(135deg, #fff5f0 0%, #ffffff 100%);
2859}
2860
2861.ai-prompt-header {
2862  display: flex;
2863  align-items: center;
2864  gap: 12px;
2865  margin-bottom: 16px;
2866}
2867
2868.ai-prompt-icon {
2869  width: 48px;
2870  height: 48px;
2871  background: linear-gradient(135deg, #fb923c 0%, #f97316 100%);
2872  border-radius: 12px;
2873  display: flex;
2874  align-items: center;
2875  justify-content: center;
2876  font-size: 24px;
2877  box-shadow: 0 4px 12px rgba(251, 146, 60, 0.3);
2878}
2879
2880.ai-prompt-text {
2881  flex: 1;
2882}
2883
2884.ai-prompt-title {
2885  font-size: 16px;
2886  font-weight: 700;
2887  color: #1f2937;
2888  margin-bottom: 4px;
2889}
2890
2891.ai-prompt-subtitle {
2892  font-size: 13px;
2893  color: #6b7280;
2894}
2895
2896.ai-prompt-query {
2897  background: white;
2898  border: 2px solid #fb923c;
2899  border-radius: 8px;
2900  padding: 12px 16px;
2901  margin-bottom: 16px;
2902}
2903
2904.ai-query-label {
2905  font-size: 11px;
2906  font-weight: 600;
2907  color: #fb923c;
2908  text-transform: uppercase;
2909  letter-spacing: 0.5px;
2910  margin-bottom: 4px;
2911}
2912
2913.ai-query-text {
2914  font-size: 15px;
2915  font-weight: 600;
2916  color: #1f2937;
2917}
2918
2919.ai-search-trigger-btn {
2920  width: 100%;
2921  padding: 14px 20px;
2922  background: linear-gradient(135deg, #fb923c 0%, #f97316 100%);
2923  color: white;
2924  border: none;
2925  border-radius: 10px;
2926  font-size: 15px;
2927  font-weight: 700;
2928  cursor: pointer;
2929  display: flex;
2930  align-items: center;
2931  justify-content: center;
2932  gap: 8px;
2933  transition: all 0.3s ease;
2934  box-shadow: 0 4px 12px rgba(251, 146, 60, 0.4);
2935}
2936
2937.ai-search-trigger-btn:hover {
2938  transform: translateY(-2px);
2939  box-shadow: 0 6px 20px rgba(251, 146, 60, 0.5);
2940}
2941
2942.ai-search-trigger-btn:active {
2943  transform: translateY(0);
2944}
2945
2946.ai-btn-icon {
2947  font-size: 18px;
2948  animation: sparkle 2s ease-in-out infinite;
2949}
2950
2951@keyframes sparkle {
2952  0%, 100% { opacity: 1; transform: scale(1); }
2953  50% { opacity: 0.7; transform: scale(1.2); }
2954}
2955
2956.ai-btn-text {
2957  font-size: 15px;
2958}
2959
2960.ai-prompt-footer {
2961  margin-top: 12px;
2962  text-align: center;
2963  font-size: 12px;
2964  color: #9ca3af;
2965  font-style: italic;
2966}
2967
2968/* ==================== TRENDING SEARCH STYLES ==================== */
2969
2970.search-suggest.trending-mode {
2971  border-top: 3px solid #fb923c;
2972  max-width: 900px;
2973  min-width: 600px;
2974}
2975
2976/* Two-column container */
2977.trending-container {
2978  display: flex;
2979  gap: 0;
2980  padding: 0 !important;
2981}
2982
2983.trending-column {
2984  flex: 1;
2985  min-width: 0;
2986  padding: 0 !important;
2987}
2988
2989.trending-column.trending-searches {
2990  border-right: 2px solid #e2e8f0; padding: 0 !important;
2991}
2992
2993/* Mobile: stack columns vertically */
2994@media (max-width: 768px) {
2995  .search-suggest.trending-mode {
2996    min-width: auto;
2997    max-width: 100%;
2998  }
2999
3000  .trending-container {
3001    flex-direction: column;
3002  }
3003
3004  .trending-column.trending-searches {
3005    border-right: none;
3006    border-bottom: 2px solid #e2e8f0;
3007  }
3008}
3009
3010.trending-header {
3011  display: flex;
3012  align-items: center;
3013  gap: 8px;
3014  padding: 14px 16px;
3015  border-bottom: 1px solid #e2e8f0;
3016  background: linear-gradient(135deg, #fb923c 0%, #f97316 100%);
3017  color: white;
3018  position: sticky;
3019  top: 0;
3020  z-index: 10;
3021}
3022
3023.trending-icon {
3024  font-size: 20px;
3025  animation: trendingPulse 2s infinite;
3026}
3027
3028@keyframes trendingPulse {
3029  0%, 100% { 
3030    transform: scale(1); 
3031    filter: drop-shadow(0 0 0 rgba(251, 146, 60, 0));
3032  }
3033  50% { 
3034    transform: scale(1.15); 
3035    filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.8));
3036  }
3037}
3038
3039.trending-title {
3040  font-weight: 700;
3041  font-size: 14px;
3042  letter-spacing: 0.3px;
3043}
3044
3045.trending-subtitle {
3046  font-size: 11px;
3047  opacity: 0.9;
3048  margin-left: auto;
3049  font-style: italic;
3050}
3051
3052/* Trending searches item */
3053.trending-item {
3054  display: flex;
3055  align-items: center;
3056  gap: 12px;
3057  padding: 12px 16px;
3058  cursor: pointer;
3059  transition: all 0.2s ease;
3060  border-bottom: 1px solid #f1f5f9;
3061  position: relative;
3062}
3063
3064.trending-item:hover {
3065  background: linear-gradient(90deg, #fff7ed 0%, #ffedd5 100%);
3066  padding-left: 20px;
3067}
3068
3069.trending-item:hover::before {
3070  content: '';
3071  position: absolute;
3072  left: 0;
3073  top: 0;
3074  bottom: 0;
3075  width: 4px;
3076  background: linear-gradient(180deg, #fb923c 0%, #f97316 100%);
3077}
3078
3079.trending-item:last-child {
3080  border-bottom: none;
3081}
3082
3083.trending-rank {
3084  font-size: 20px;
3085  flex-shrink: 0;
3086  width: 32px;
3087  text-align: center;
3088}
3089
3090.trending-query {
3091  flex: 1;
3092  font-size: 14px;
3093  color: #1f2937;
3094  font-weight: 500;
3095  line-height: 1.4;
3096}
3097
3098.trending-category {
3099  font-size: 11px;
3100  color: #f97316;
3101  background: #fff7ed;
3102  padding: 3px 10px;
3103  border-radius: 12px;
3104  white-space: nowrap;
3105  font-weight: 500;
3106  border: 1px solid #fed7aa;
3107}
3108
3109/* Trending products item */
3110.trending-product-item {
3111  display: flex;
3112  align-items: center;
3113  gap: 12px;
3114  padding: 12px 16px;
3115  cursor: pointer;
3116  transition: all 0.2s ease;
3117  border-bottom: 1px solid #f1f5f9;
3118  position: relative;
3119}
3120
3121.trending-product-item:hover {
3122  background: linear-gradient(90deg, #fef3c7 0%, #fde68a 100%);
3123  padding-left: 20px;
3124}
3125
3126.trending-product-item:hover::before {
3127  content: '';
3128  position: absolute;
3129  left: 0;
3130  top: 0;
3131  bottom: 0;
3132  width: 4px;
3133  background: linear-gradient(180deg, #fbbf24 0%, #f59e0b 100%);
3134}
3135
3136.trending-product-item:last-child {
3137  border-bottom: none;
3138}
3139
3140.trending-product-thumb {
3141  width: 48px;
3142  height: 48px;
3143  object-fit: cover;
3144  border-radius: 8px;
3145  border: 1px solid #e5e7eb;
3146  flex-shrink: 0;
3147  background: white;
3148}
3149
3150.trending-product-info {
3151  flex: 1;
3152  min-width: 0;
3153}
3154
3155.trending-product-name {
3156  font-size: 13px;
3157  font-weight: 400;
3158  color: #1f2937;
3159  line-height: 1.4;
3160  margin-bottom: 0;
3161  padding: 0 !important;
3162  overflow: hidden;
3163  text-overflow: ellipsis;
3164  display: -webkit-box;
3165  -webkit-line-clamp: 2;
3166  -webkit-box-orient: vertical;
3167}
3168
3169.trending-product-price {
3170  font-size: 12px;
3171  font-weight: 700;
3172  color: #f59e0b;
3173  padding: 4px 0 !important;
3174}
3175.trending-product { padding: 0;}
3176/* ==================== END TRENDING STYLES ==================== */
3177
3178</style>
3179  <style data-inlined="product-detail.css">
3180/* === PRODUCT DETAIL v3 (Tokopedia-like) — Full stylesheet === */
3181
3182/* Root / Container */
3183.product-single {
3184  --pd-gap: 2rem;
3185  --pd-border: #e2e8f0;
3186  --pd-bg: #ffffff;
3187  --pd-muted: #64748b;
3188  --pd-dark: #0f172a;
3189  --pd-accent: #0d6efd; /* tema utama situs */
3190  --pd-radius: 12px;
3191  --pd-soft: #f8fafc;
3192  --pd-soft-border: #e5e7eb;
3193  --pd-danger: #dc2626;
3194  --pd-success-bg: #dcfce7;
3195  --pd-success-text: #166534;
3196  --pd-danger-bg: #fee2e2;
3197  --pd-danger-text: #b91c1c;
3198  --pd-shadow: 0 6px 20px -10px rgba(0,0,0,.12);
3199  --pd-transition: .25s cubic-bezier(.4,.0,.2,1);
3200  max-width:1280px;
3201  margin-inline:auto;
3202}
3203
3204/* === Sticky Product Header Bar (muncul saat scroll) === */
3205.pd-sticky-header {
3206  position: fixed;
3207  top: 0;
3208  left: 0;
3209  right: 0;
3210  z-index: 998;
3211  background: rgba(255, 255, 255, 0.98);
3212  backdrop-filter: blur(12px);
3213  -webkit-backdrop-filter: blur(12px);
3214  border-bottom: 1px solid rgba(226, 232, 240, 0.8);
3215  box-shadow: 0 4px 16px -4px rgba(0, 0, 0, 0.08);
3216  transform: translateY(-100%);
3217  opacity: 0;
3218  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), 
3219              opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);
3220  pointer-events: none;
3221}
3222
3223.pd-sticky-header.visible {
3224  transform: translateY(0);
3225  opacity: 1;
3226  pointer-events: auto;
3227}
3228
3229.pd-sticky-header-container {
3230  max-width: 1280px;
3231  margin: 0 auto;
3232  padding: 12px 20px;
3233  display: flex;
3234  align-items: center;
3235  gap: 16px;
3236}
3237
3238.pd-sticky-thumb {
3239  width: 48px;
3240  height: 48px;
3241  flex-shrink: 0;
3242  border-radius: 8px;
3243  border: 1px solid #e2e8f0;
3244  overflow: hidden;
3245  background: #f8fafc;
3246}
3247
3248.pd-sticky-thumb img {
3249  width: 100%;
3250  height: 100%;
3251  object-fit: cover;
3252  display: block;
3253}
3254
3255.pd-sticky-info {
3256  flex: 1;
3257  min-width: 0;
3258  display: flex;
3259  flex-direction: column;
3260  gap: 4px;
3261}
3262
3263.pd-sticky-title {
3264  font-size: 0.9rem;
3265  font-weight: 600;
3266  color: #0f172a;
3267  line-height: 1.3;
3268  white-space: nowrap;
3269  overflow: hidden;
3270  text-overflow: ellipsis;
3271  margin: 0;
3272}
3273
3274.pd-sticky-price-row {
3275  display: flex;
3276  align-items: center;
3277  gap: 8px;
3278  flex-wrap: wrap;
3279}
3280
3281.pd-sticky-price {
3282  font-size: 1.1rem;
3283  font-weight: 700;
3284  color: #0d6efd;
3285  line-height: 1;
3286}
3287
3288.pd-sticky-price-original {
3289  font-size: 0.75rem;
3290  color: #94a3b8;
3291  text-decoration: line-through;
3292  line-height: 1;
3293}
3294
3295.pd-sticky-discount {
3296  background: #dc2626;
3297  color: #fff;
3298  font-size: 0.65rem;
3299  font-weight: 600;
3300  padding: 2px 6px;
3301  border-radius: 4px;
3302  letter-spacing: 0.3px;
3303  line-height: 1;
3304}
3305
3306.pd-sticky-actions {
3307  display: flex;
3308  gap: 8px;
3309  flex-shrink: 0;
3310  align-items: center;
3311}
3312
3313.pd-sticky-share-btn {
3314  padding: 8px 16px;
3315  border-radius: 8px;
3316  border: 1px solid #e2e8f0;
3317  background: #fff;
3318  color: #64748b;
3319  cursor: pointer;
3320  transition: all 0.2s ease;
3321  display: inline-flex;
3322  align-items: center;
3323  justify-content: center;
3324  flex-shrink: 0;
3325}
3326
3327.pd-sticky-share-btn:hover {
3328  background: #f8fafc;
3329  border-color: #cbd5e1;
3330  color: #0f172a;
3331  transform: translateY(-1px);
3332}
3333
3334.pd-sticky-share-btn:active {
3335  transform: translateY(0);
3336}
3337
3338.pd-sticky-share-btn svg {
3339  display: block;
3340  width: 16px;
3341  height: 16px;
3342}
3343
3344.pd-sticky-btn-chat svg {
3345  width: 16px !important;
3346  height: 16px !important;
3347  max-width: none !important;
3348  margin-right: 4px;
3349  flex-shrink: 0;
3350}
3351
3352.pd-sticky-btn {
3353  padding: 8px 16px;
3354  border-radius: 8px;
3355  font-size: 0.85rem;
3356  font-weight: 600;
3357  cursor: pointer;
3358  transition: all 0.2s ease;
3359  border: 0;
3360  display: inline-flex;
3361  align-items: center;
3362  justify-content: center;
3363  white-space: nowrap;
3364}
3365
3366.pd-sticky-btn-cart {
3367  background: #fff;
3368  color: #0d6efd;
3369  border: 1px solid #0d6efd;
3370}
3371
3372.pd-sticky-btn-cart:hover {
3373  background: rgba(13, 110, 253, 0.06);
3374}
3375
3376.pd-sticky-btn-chat {
3377  background: #fff;
3378  color: #64748b;
3379  border: 1px solid #cbd5e1;
3380  flex: 0.8;
3381}
3382
3383.pd-sticky-btn-chat:hover {
3384  background: #f8fafc;
3385  border-color: #94a3b8;
3386  color: #475569;
3387}
3388
3389.pd-sticky-btn-buy {
3390  background: #0d6efd;
3391  color: #fff;
3392  border: 1px solid #0d6efd;
3393  box-shadow: 0 2px 8px -2px rgba(13, 110, 253, 0.4);
3394}
3395
3396.pd-sticky-btn-buy:hover {
3397  filter: brightness(0.95);
3398}
3399
3400.pd-sticky-btn:disabled {
3401  opacity: 0.5;
3402  cursor: not-allowed;
3403}
3404
3405/* Hide on mobile (use existing FAB instead) */
3406@media (max-width: 880px) {
3407  .pd-sticky-header {
3408    display: none;
3409  }
3410}
3411
3412/* === PRINT STYLES (for PDF Export) === */
3413@media print {
3414  /* Reset & Base */
3415  * {
3416    -webkit-print-color-adjust: exact !important;
3417    print-color-adjust: exact !important;
3418  }
3419
3420  body {
3421    background: white !important;
3422    margin: 0;
3423    padding: 0;
3424  }
3425
3426  /* Hide all navigation, buttons, and interactive elements */
3427  nav, header, footer,
3428  nav *, header *, footer *,
3429  .navbar, .navbar *,
3430  .nav-item, .nav-link,
3431  .pd-sticky-header,
3432  .pd-sticky-header.visible,
3433  .pd-breadcrumb,
3434  .pd-sidebar,
3435  .pd-mobile-fab,
3436  .pd-share-btn,
3437  .pd-sticky-share-btn,
3438  #pd-share-btn,
3439  .share-button,
3440  [data-share-btn],
3441  button,
3442  .btn-add-cart,
3443  .btn-buy,
3444  .pd-actions,
3445  .pd-compare-section,
3446  .pd-compare-btn,
3447  .pd-compare-info,
3448  #pd-compare-btn,
3449  .compare-note,
3450  .compare-container,
3451  .compare-dropdown,
3452  .compare-panel,
3453  .compare-wrapper,
3454  [data-compare],
3455  [id*="compare"],
3456  [class*="compare"],
3457  .pd-thumb-gallery,
3458  .pd-installment-card,
3459  .pd-installment-card2,
3460  .inst-alt-banner--compact,
3461  .qoala-banner-card,
3462  .tradein-compact-card,
3463  .pwp-deals-section,
3464  #pwp-deals-section,
3465  .pwp-trigger-badge,
3466  .pd-related-full,
3467  #pd-similar,
3468  .pd-similar-strip,
3469  .pd-scroll-top,
3470  .stock-info-btn,
3471  .stock-popover,
3472  .pd-countdown-modern,
3473  .pd-share-modal-overlay,
3474  [id*="promo-"],
3475  .bottom-nav,
3476  .pd-buy-form,
3477  .pd-buy-card,
3478  .pd-extra-meta,
3479  .pd-reviews-section,
3480  #reviews,
3481  hr,
3482  .divider,
3483  .separator {
3484    display: none !important;
3485    visibility: hidden !important;
3486    opacity: 0 !important;
3487    height: 0 !important;
3488    max-height: 0 !important;
3489    overflow: hidden !important;
3490    margin: 0 !important;
3491    padding: 0 !important;
3492    border: none !important;
3493  }
3494
3495  /* Extra aggressive reset for any remaining borders */
3496  .pd-info-card *,
3497  .pd-meta-inline *,
3498  .pd-variants * {
3499    border: none !important;
3500    border-top: none !important;
3501    border-bottom: none !important;
3502  }
3503
3504  /* Layout restructure */
3505  .product-single {
3506    max-width: 100% !important;
3507    margin: 0 !important;
3508    padding: 0 !important;
3509    box-shadow: none !important;
3510  }
3511
3512  /* Reset all shadows and borders for print */
3513  * {
3514    box-shadow: none !important;
3515  }
3516
3517  .pd-top-layout {
3518    display: grid !important;
3519    grid-template-columns: 280px 1fr !important;
3520    gap: 24px !important;
3521    margin: 0 0 24px 0 !important;
3522    padding: 0 !important;
3523    page-break-inside: avoid;
3524    align-items: start !important;
3525  }
3526
3527  /* Print header with logo - centered and larger */
3528  .product-single::before {
3529    content: "" !important;
3530    display: block !important;
3531    width: 150px !important;
3532    height: 50px !important;
3533    background: url('/assets/https://ik.imagekit.io/boytoto/logo.webp') no-repeat center !important;
3534    background-size: contain !important;
3535    margin: 0 auto 20px !important;
3536    visibility: visible !important;
3537    opacity: 1 !important;
3538  }
3539
3540  /* Media card - left column */
3541  .pd-media-card {
3542    border: none !important;
3543    padding: 0 !important;
3544    margin: 0 !important;
3545    text-align: center;
3546    page-break-inside: avoid;
3547  }
3548
3549  /* Hide desktop image, use mobile slider for both desktop and mobile */
3550  .pd-main-image {
3551    display: none !important;
3552  }
3553
3554  /* Mobile slider - show first image only in print */
3555  .pd-mobile-slider {
3556    display: block !important;
3557    overflow: visible !important;
3558  }
3559
3560  .pd-mslide {
3561    display: none !important;
3562  }
3563
3564  .pd-mslide:first-child {
3565    display: block !important;
3566    border: 1px solid #e2e8f0 !important;
3567    max-width: 280px !important;
3568    margin: 0 auto !important;
3569    border-radius: 8px !important;
3570  }
3571
3572  .pd-mslide:first-child img {
3573    max-height: 280px !important;
3574    width: 100% !important;
3575    height: auto !important;
3576    object-fit: contain !important;
3577  }
3578
3579  /* Info card - right column */
3580  .pd-info-card {
3581    border: none !important;
3582    border-top: none !important;
3583    border-bottom: none !important;
3584    padding: 0 !important;
3585    margin: 0 !important;
3586    background: white !important;
3587    box-shadow: none !important;
3588  }
3589
3590  /* Title */
3591  .pd-title-row {
3592    display: block !important;
3593    margin-bottom: 10px !important;
3594    border: none !important;
3595    border-top: none !important;
3596    border-bottom: none !important;
3597    padding: 0 !important;
3598    box-shadow: none !important;
3599    text-decoration: none !important;
3600  }
3601
3602  /* Hide share button in print */
3603  .pd-title-row .pd-share-btn,
3604  .pd-title-row #pd-share-btn {
3605    display: none !important;
3606  }
3607
3608  .pd-title {
3609    font-size: 13pt !important;
3610    color: #000 !important;
3611    margin: 0 0 10px 0 !important;
3612    page-break-after: avoid;
3613    font-weight: 700 !important;
3614    line-height: 1.3 !important;
3615    border: none !important;
3616    border-top: none !important;
3617    border-bottom: none !important;
3618    padding-bottom: 0 !important;
3619    box-shadow: none !important;
3620    text-decoration: none !important;
3621  }
3622
3623  /* Price and QR container - side by side */
3624  .pd-price-qr-container {
3625    display: flex !important;
3626    gap: 16px !important;
3627    align-items: flex-start !important;
3628    margin-bottom: 14px !important;
3629    box-shadow: none !important;
3630    border: none !important;
3631    text-decoration: none !important;
3632  }
3633
3634  /* Price block - left side */
3635  .pd-price-block {
3636    flex: 1 !important;
3637    margin: 0 !important;
3638    padding-bottom: 0 !important;
3639    border-bottom: none !important;
3640    box-shadow: none !important;
3641  }
3642
3643  .pd-price-row {
3644    margin-bottom: 8px !important;
3645    border: none !important;
3646    border-top: none !important;
3647    border-bottom: none !important;
3648    padding: 0 !important;
3649  }
3650
3651  /* Hide all empty or unnecessary elements in price row */
3652  .pd-price-row > span:empty,
3653  .pd-price-row > .sold-inline,
3654  #pd-sold-inline {
3655    display: none !important;
3656  }
3657
3658  .pd-price-current {
3659    font-size: 18pt !important;
3660    color: #0d6efd !important;
3661  }
3662
3663  /* Show price range for variant products in print */
3664  .pd-price-current[data-price-range]::after {
3665    content: attr(data-price-range) !important;
3666    display: block !important;
3667    font-size: 11pt !important;
3668    color: #64748b !important;
3669    margin-top: 4px !important;
3670    font-weight: normal !important;
3671  }
3672
3673  /* Hide 'Range:' prefix text */
3674  .pd-price-current[data-price-range]::after {
3675    content: attr(data-price-range) !important;
3676  }
3677
3678  .pd-price-original {
3679    font-size: 12pt !important;
3680    color: #64748b !important;
3681  }
3682
3683  .pd-discount-badge {
3684    font-size: 10pt !important;
3685    padding: 2px 6px !important;
3686  }
3687
3688  /* QR Code section - right side beside price */
3689  .pd-qr-print {
3690    display: block !important;
3691    flex: 0 0 100px !important;
3692    margin: 0 !important;
3693    padding: 8px !important;
3694    background: #f8fafc !important;
3695    border-radius: 8px !important;
3696    text-align: center !important;
3697    page-break-inside: avoid !important;
3698    border: 1px solid #cbd5e1 !important;
3699  }
3700
3701  .pd-qr-label {
3702    font-size: 6.5pt !important;
3703    color: #64748b !important;
3704    margin-bottom: 5px !important;
3705    line-height: 1.2 !important;
3706    font-weight: 600 !important;
3707  }
3708
3709  .pd-qr-image {
3710    width: 80px !important;
3711    height: 80px !important;
3712    display: block !important;
3713    margin: 0 auto !important;
3714  }
3715
3716  /* Meta info */
3717  .pd-meta-inline {
3718    display: flex !important;
3719    gap: 12px !important;
3720    margin-bottom: 0 !important;
3721    padding-bottom: 14px !important;
3722    font-size: 10pt !important;
3723    border: none !important;
3724    border-top: none !important;
3725    border-bottom: none !important;
3726    padding-top: 0 !important;
3727    box-shadow: none !important;
3728    text-decoration: none !important;
3729    background: transparent !important;
3730  }
3731
3732  /* Hide stock badge in print */
3733  .stock-badge,
3734  .stock-info-btn {
3735    display: none !important;
3736  }
3737
3738  .sku {
3739    color: #64748b !important;
3740  }
3741
3742  /* Hide sold badge if empty/zero */
3743  .sold-inline {
3744    display: none !important;
3745  }
3746
3747  /* Hide variant summary section in print */
3748  .pd-variant-combined,
3749  #pd-variant-combined,
3750  .pd-variant-summary,
3751  [data-role="sold-chip"] {
3752    display: none !important;
3753  }
3754
3755  /* Hide compare section completely in print */
3756  .pd-compare-section,
3757  .pd-compare-btn,
3758  #pd-compare-btn,
3759  .pd-compare-info,
3760  .compare-note,
3761  .compare-text,
3762  .compare-remove {
3763    display: none !important;
3764    visibility: hidden !important;
3765    height: 0 !important;
3766    margin: 0 !important;
3767    padding: 0 !important;
3768    overflow: hidden !important;
3769  }
3770
3771  /* Variants - show all options with selected highlighted */
3772  .pd-variants {
3773    margin: 0 !important;
3774    padding: 0 !important;
3775    page-break-inside: avoid !important;
3776    border: none !important;
3777    border-top: none !important;
3778    border-bottom: none !important;
3779    box-shadow: none !important;
3780    background: transparent !important;
3781  }
3782
3783  /* Add 'Variant:' label before variant groups */
3784  .pd-variants::before {
3785    content: "Variant:" !important;
3786    display: block !important;
3787    font-size: 11pt !important;
3788    font-weight: 700 !important;
3789    color: #0f172a !important;
3790    margin-bottom: 10px !important;
3791    border: none !important;
3792    padding: 0 !important;
3793    border-top: none !important;
3794    border-bottom: none !important;
3795  }
3796
3797  .v-group {
3798    display: flex !important;
3799    align-items: center !important;
3800    gap: 8px !important;
3801    margin-bottom: 8px !important;
3802    line-height: 1 !important;
3803    page-break-inside: avoid !important;
3804    border: none !important;
3805    padding: 0 !important;
3806  }
3807
3808  .v-group:last-child {
3809    margin-bottom: 0 !important;
3810  }
3811
3812  .v-label {
3813    font-weight: 700 !important;
3814    margin: 0 !important;
3815    font-size: 9pt !important;
3816    color: #475569 !important;
3817    min-width: 60px !important;
3818    flex-shrink: 0 !important;
3819    border: none !important;
3820    padding: 0 !important;
3821  }
3822
3823  .v-options {
3824    display: flex !important;
3825    gap: 6px !important;
3826    flex-wrap: wrap !important;
3827    border: none !important;
3828    padding: 0 !important;
3829    margin: 0 !important;
3830  }
3831
3832  .v-chip {
3833    border: 1px solid #cbd5e1 !important;
3834    padding: 3px 10px !important;
3835    font-size: 9pt !important;
3836    background: #f8fafc !important;
3837    border-radius: 6px !important;
3838    display: inline-flex !important;
3839    align-items: center !important;
3840    page-break-inside: avoid !important;
3841  }
3842
3843  /* Highlight selected variant with blue background */
3844  .v-chip.selected {
3845    background: #eff6ff !important;
3846    border-color: #2563eb !important;
3847    font-weight: 600 !important;
3848  }
3849
3850  /* Hide radio inputs */
3851  .v-chip input[type="radio"] {
3852    display: none !important;
3853  }
3854
3855  /* Hide "Habis" badge in print */
3856  .v-chip .chip-badge {
3857    display: none !important;
3858  }
3859
3860  /* Short description - HIDDEN in print */
3861  .pd-short-desc {
3862    display: none !important;
3863  }
3864
3865  /* QR Code section - right side beside price */
3866  .pd-qr-print {
3867    display: block !important;
3868    flex: 0 0 100px !important;
3869    margin: 0 !important;
3870    padding: 8px !important;
3871    background: #f8fafc !important;
3872    border-radius: 8px !important;
3873    text-align: center !important;
3874    page-break-inside: avoid !important;
3875    border: 1px solid #cbd5e1 !important;
3876  }
3877
3878  .pd-qr-label {
3879    font-size: 6.5pt !important;
3880    color: #64748b !important;
3881    margin-bottom: 5px !important;
3882    line-height: 1.2 !important;
3883    font-weight: 600 !important;
3884  }
3885
3886  .pd-qr-image {
3887    width: 80px !important;
3888    height: 80px !important;
3889    display: block !important;
3890    margin: 0 auto !important;
3891  }
3892
3893  /* Full description - below 2 column layout */
3894  .pd-full-description {
3895    border: none !important;
3896    padding: 0 !important;
3897    margin: 24px 0 0 0 !important;
3898    page-break-before: avoid;
3899    grid-column: 1 / -1 !important;
3900  }
3901
3902  .pd-full-desc-title {
3903    font-size: 13pt !important;
3904    font-weight: 700 !important;
3905    margin: 0 0 12px 0 !important;
3906    color: #000 !important;
3907    border-top: none !important;
3908    border-bottom: none !important;
3909    padding-top: 16px !important;
3910  }
3911
3912  .pd-full-desc-body {
3913    font-size: 10pt !important;
3914    line-height: 1.6 !important;
3915    color: #334155 !important;
3916  }
3917
3918  .pd-full-desc-body img {
3919    max-height: 200px !important;
3920    width: auto !important;
3921    page-break-inside: avoid;
3922    display: none !important; /* Hide images in print to save space */
3923  }
3924
3925  /* Also target #deskripsi img if exists */
3926  #deskripsi img {
3927    max-width: 350px !important;
3928    height: auto !important;
3929  }
3930
3931  .pd-full-desc-body table {
3932    border-collapse: collapse !important;
3933    width: 100% !important;
3934    margin: 12px 0 !important;
3935    font-size: 9pt !important;
3936  }
3937
3938  .pd-full-desc-body th,
3939  .pd-full-desc-body td {
3940    border: 1px solid #cbd5e1 !important;
3941    padding: 6px 8px !important;
3942  }
3943
3944  /* Print footer */
3945  .product-single::after {
3946    content: "Plaza IT - plazait.co.id | Printed: " attr(data-print-date);
3947    display: block;
3948    margin-top: 30px;
3949    padding-top: 16px;
3950    border-top: 1px solid #e2e8f0;
3951    font-size: 9pt;
3952    color: #64748b;
3953    text-align: center;
3954  }
3955
3956  /* Page breaks */
3957  .pd-media-card,
3958  .pd-price-block,
3959  .pd-full-desc-title {
3960    page-break-after: avoid;
3961  }
3962
3963  .pd-full-description {
3964    page-break-before: auto;
3965  }
3966}
3967
3968/* === Breadcrumb (truncate last item) === */
3969.pd-breadcrumb{
3970  display:flex;
3971  flex-wrap:nowrap;
3972  align-items:center;
3973  gap:.5rem;
3974  font-size:.72rem;
3975  margin:.7rem 0 1.2rem .5rem;
3976  color:var(--pd-muted);
3977  overflow:hidden;
3978}
3979.pd-breadcrumb a{color:var(--pd-accent);text-decoration:none}
3980.pd-breadcrumb a:hover{text-decoration:underline}
3981.pd-breadcrumb a,
3982.pd-breadcrumb span{flex:0 0 auto}
3983.pd-breadcrumb .current{
3984  flex:1 1 auto;
3985  min-width:0;
3986  white-space:nowrap;
3987  overflow:hidden;
3988  text-overflow:ellipsis;
3989  font-weight:600;
3990  color:var(--pd-dark);
3991  padding-right:2px;
3992}
3993
3994/* === Top layout === */
3995.pd-top-layout{
3996  display:grid;
3997  gap:var(--pd-gap);
3998  grid-template-columns:minmax(260px,340px) minmax(460px,1fr) 300px;
3999  align-items:start;
4000  margin-bottom:2.4rem;
4001}
4002
4003/* === Media column === */
4004.pd-media-card{
4005  background:#fff;
4006  border:1px solid var(--pd-border);
4007  border-radius:var(--pd-radius);
4008  padding:1rem 1rem 1.2rem;
4009}
4010.pd-main-image{margin:0 0 .8rem;border:1px solid var(--pd-soft-border);border-radius:10px;overflow:hidden;background:#fff;position:relative}
4011.pd-main-image img{width:100%;display:block;object-fit:contain;aspect-ratio:1/1}
4012
4013/* Gallery Thumbnails */
4014.pd-thumb-gallery{margin-top:.6rem;display:flex;flex-wrap:wrap;gap:.55rem}
4015.pd-thumb-btn{
4016  border:1px solid var(--pd-soft-border);
4017  background:#fff;
4018  cursor:pointer;
4019  width:64px;height:64px;padding:2px;
4020  display:flex;align-items:center;justify-content:center;
4021  overflow:hidden;border-radius:8px;
4022  transition:border-color .18s ease, box-shadow .18s ease, transform .18s ease;
4023  position:relative;
4024}
4025.pd-thumb-btn img{max-width:100%;max-height:100%;object-fit:contain;display:block}
4026.pd-thumb-btn:hover{border-color:var(--pd-accent)}
4027.pd-thumb-btn.active{border-color:var(--pd-accent);box-shadow:0 0 0 2px rgba(13,110,253,.25)}
4028.pd-thumb-btn:focus-visible{outline:2px solid var(--pd-accent);outline-offset:2px}
4029.pd-thumb-btn:active{transform:scale(.96)}
4030
4031/* === Info column === */
4032.pd-info-card{
4033  background:#fff;border:1px solid var(--pd-border);border-radius:var(--pd-radius);
4034  padding:1.4rem 1.6rem 2rem;
4035}
4036.pd-title{margin:0 0 1rem;font-size:1.2rem;line-height:1.25;font-weight:600;color:var(--pd-dark)}
4037.pd-price-block{margin:0 0 1rem}
4038.pd-price-row{display:flex;flex-wrap:wrap;align-items:center;gap:.75rem;margin:0 0 .35rem}
4039.pd-price-current{font-size:2rem;font-weight:700;color:var(--pd-accent)}
4040.pd-price-original{font-size:.9rem;text-decoration:line-through;color:#94a3b8}
4041.pd-discount-badge{background:var(--pd-danger);color:#fff;font-size:.68rem;padding:4px 8px 3px;font-weight:600;border-radius:6px;letter-spacing:.5px}
4042.pd-meta-inline{display:flex;flex-wrap:wrap;gap:.9rem;align-items:center;font-size:.63rem;letter-spacing:.6px;text-transform:uppercase;font-weight:600}
4043.stock-badge{padding:4px 10px;border-radius:24px;background:var(--pd-soft);color:var(--pd-muted);display:inline-block;line-height:1;vertical-align:middle}
4044.stock-badge.in{background:var(--pd-success-bg);color:var(--pd-success-text)}
4045.stock-badge.out{background:var(--pd-danger-bg);color:var(--pd-danger-text)}
4046.digital-badge{padding:4px 10px;border-radius:24px;background:linear-gradient(135deg,#FF5722 0%,#764ba2 100%);color:#fff;font-size:.63rem;font-weight:600;letter-spacing:.6px;text-transform:uppercase;display:inline-block;line-height:1;vertical-align:middle;border:none}
4047.sku{color:var(--pd-muted)}
4048.pd-short-desc{font-size:.85rem;line-height:1.5;margin:0 0 1.2rem;color:#334155;overflow-wrap:anywhere}
4049.pd-short-desc p{margin:0 0 .75rem}
4050.pd-short-desc ul,.pd-short-desc ol{margin:.5rem 0 .9rem 1.1rem;font-size:.85rem;line-height:1.45}
4051
4052/* Meta row for sold and rating badges */
4053.pd-meta-row {
4054  display: flex;
4055  align-items: center;
4056  flex-wrap: wrap;
4057  gap: 5px;
4058  margin-top: 8px;
4059  margin-bottom: 4px;
4060}
4061
4062/* Sales (Terjual) badge inline */
4063.sold-inline{
4064  display:inline-block;font-size:.65rem;font-weight:600;color:#475569;
4065  background:#f1f5f9;padding:3px 8px 4px;border-radius:6px;letter-spacing:.4px;margin-left:0;
4066}
4067
4068/* Price row divider */
4069.price-divider {
4070  display: inline-block;
4071  margin: 0 .4rem;
4072  color: #cbd5e1;
4073  font-weight: 400;
4074}
4075
4076/* Rating badge inline */
4077.rating-inline {
4078  display: inline-flex;
4079  align-items: center;
4080  gap: 4px;
4081  padding: 3px 8px 4px;
4082  background: #fff7ed;
4083  border-radius: 6px;
4084  font-size: .65rem;
4085  font-weight: 600;
4086  color: #ea580c;
4087  text-decoration: none;
4088  transition: all .2s ease;
4089  cursor: pointer;
4090  margin-left: .5rem;
4091  white-space: nowrap;
4092}
4093
4094.rating-inline:hover {
4095  background: #ffedd5;
4096  transform: translateY(-1px);
4097  box-shadow: 0 2px 4px rgba(234, 88, 12, 0.1);
4098}
4099
4100.rating-inline .star-icon {
4101  font-size: .75rem;
4102  color: #f59e0b;
4103  line-height: 1;
4104}
4105
4106.rating-inline .rating-value {
4107  color: #ea580c;
4108}
4109
4110.rating-inline .rating-badge-count {
4111  color: #9a3412;
4112  opacity: 0.85;
4113}
4114
4115@media (max-width: 640px) {
4116  .rating-inline {
4117    font-size: .65rem;
4118    padding: 3px 8px 4px;
4119  }
4120  .rating-inline .star-icon {
4121    font-size: .7rem;
4122  }
4123}
4124
4125/* Highlight animation for review section */
4126@keyframes highlightFlash {
4127  0%, 100% { background-color: transparent; }
4128  50% { background-color: rgba(251, 146, 60, 0.1); }
4129}
4130
4131.highlight-flash {
4132  animation: highlightFlash 1.5s ease;
4133}
4134
4135/* === Variant chips === */
4136.pd-variants{margin-top:14px}
4137.v-group{margin-bottom:12px}
4138.v-label{font-weight:600;margin-bottom:6px}
4139.v-options{display:flex;flex-wrap:wrap;gap:8px}
4140.v-chip{
4141  display:inline-flex;align-items:center;gap:8px;
4142  padding:6px 10px;border:1px solid #d6d9de;border-radius:999px;
4143  cursor:pointer;background:#fff;transition:.15s ease;user-select:none
4144}
4145.v-chip:hover{border-color:#9aa3af;box-shadow:0 1px 0 rgba(0,0,0,.04)}
4146.v-chip.selected{border-color:#2563eb;background:#eff6ff}
4147.v-chip.disabled{opacity:.45;cursor:not-allowed}
4148.v-chip.out{border-style:dashed}
4149.v-chip input{display:none}
4150.v-chip .dot{width:14px;
4150height:14px;border-radius:50%;background:var(--dot,#999);box-shadow:inset 0 0 0 1px rgba(0,0,0,.2)}
4151.v-chip .dot--neutral{background:#9aa3af}
4152.v-chip .txt{font-size:.95rem;white-space:nowrap}
4153.v-chip .chip-badge{margin-left:2px;font-size:.72rem;line-height:1;padding:3px 6px;border-radius:6px;background:#fee2e2;color:#991b1b;border:1px solid #fecaca}
4154.pd-variant-summary{margin-top:8px;color:#475569;font-size:.92rem}
4155.pd-variant-summary .vs-item{display:inline-flex;gap:4px;margin-right:10px}
4156
4157/* === Buy form (di sidebar kanan untuk desktop) === */
4158.pd-buy-form{display:flex;flex-wrap:wrap;align-items:flex-end;gap:1rem 1.4rem;margin:0}
4159.qty-label{display:block;font-size:.6rem;font-weight:600;letter-spacing:.7px;text-transform:uppercase;color:var(--pd-muted);margin-bottom:.35rem}
4160.pd-buy-form input[type=number]{width:90px;padding:.55rem .6rem;border:1px solid var(--pd-soft-border);border-radius:6px;font-size:.85rem}
4161.pd-actions{display:flex;gap:.8rem;flex-wrap:wrap}
4162
4163/* Tombol senada tema */
4164.btn-add-cart,.btn-buy,.btn-chat-icon{
4165  cursor:pointer;border:0;font-weight:700;border-radius:10px;font-size:.9rem;
4166  display:inline-flex;align-items:center;justify-content:center;gap:.4rem;
4167  min-height:44px;padding:0 1rem;transition:var(--pd-transition)
4168}
4169.btn-add-cart{background:var(--brand-500);color:#fff;box-shadow:0 2px 8px -2px rgba(13,110,253,.35)}
4170.btn-add-cart:hover{filter:brightness(.95)}
4171.btn-buy{background:#fff;color:var(--brand-500);border:2px solid var(--brand-500)}
4172.btn-buy:hover{background:rgba(13,110,253,.06)}
4173.btn-chat-icon{background:#fff;color:#fb923c;border:2px solid #fb923c;padding:0;width:44px;min-width:44px}
4174.btn-chat-icon:hover{background:rgba(251, 146, 60, 0.06);border-color:#f97316}
4175.btn-chat-icon svg{width:20px;height:20px;flex-shrink:0}
4176.btn-add-cart:disabled,.btn-buy:disabled,.btn-chat-icon:disabled{opacity:.5;cursor:not-allowed}
4177
4178/* Buy with Voucher Button - Cleaner, more compact design */
4179.btn-buy-voucher{
4180  cursor:pointer;border:0;font-weight:600;border-radius:10px;
4181  display:inline-flex;align-items:center;justify-content:space-between;gap:10px;
4182  min-height:44px;padding:0 14px 0 12px;transition:all 0.3s ease;
4183  background:linear-gradient(135deg, #7c3aed 0%, #9333ea 100%);
4184  color:#fff;
4185  box-shadow:0 3px 10px rgba(124,58,237,0.3);
4186  position:relative;
4187  overflow:hidden;
4188  flex:1 1 auto
4189}
4190.btn-buy-voucher:hover{
4191  transform:translateY(-2px);
4192  box-shadow:0 5px 16px rgba(124,58,237,0.4)
4193}
4194.btn-buy-voucher:active{
4195  transform:translateY(0);
4196  box-shadow:0 2px 6px rgba(124,58,237,0.3)
4197}
4198.btn-buy-voucher .bv-icon{
4199  font-size:20px;
4200  line-height:1;
4201  flex-shrink:0
4202}
4203.btn-buy-voucher .bv-text{
4204  display:flex;
4205  flex-direction:column;
4206  align-items:flex-start;
4207  gap:2px;
4208  flex:1;
4209  min-width:0;
4210  font-size:14px;
4211  line-height:1.2
4212}
4213.btn-buy-voucher .bv-text small{
4214  font-size:10px;
4215  opacity:0.85;
4216  font-weight:500;
4217  letter-spacing:0.3px;
4218  font-family:monospace
4219}
4220.btn-buy-voucher .bv-save{
4221  font-size:12px;
4222  font-weight:700;
4223  background:rgba(255,255,255,0.25);
4224  padding:4px 8px;
4225  border-radius:6px;
4226  white-space:nowrap;
4227  flex-shrink:0
4228}
4229
4230/* Extra meta (opsional) */
4231.pd-extra-meta{background:var(--pd-soft);border:1px solid var(--pd-border);border-radius:10px;padding:.85rem 1rem;display:grid;gap:.45rem;font-size:.7rem;line-height:1.3}
4232.pd-extra-meta span{font-weight:600;color:var(--pd-muted);margin-right:.3rem}
4233.pd-extra-meta a{color:var(--pd-accent);text-decoration:none}
4234.pd-extra-meta a:hover{text-decoration:underline}
4235
4236/* === Sidebar === */
4237.pd-sidebar{display:flex;flex-direction:column;gap:1.2rem}
4238.pd-side-card{background:#fff;border:1px solid var(--pd-border);border-radius:var(--pd-radius);padding:1rem 1.1rem 1.2rem}
4239.pd-side-card h3{margin:0 0 .7rem;font-size:.8rem;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:var(--pd-dark)}
4240.pd-side-card ul{list-style:none;margin:0;padding:0;display:grid;gap:.45rem;font-size:.7rem;color:#334155}
4241.pd-side-card li{position:relative;padding-left:13px;line-height:1.25}
4242.pd-side-card li:before{content:'';width:6px;height:6px;background:var(--pd-accent);border-radius:50%;position:absolute;
4242left:0;top:.45rem}
4243.pay-logos{display:flex;gap:.4rem;flex-wrap:wrap;margin:.1rem 0 .4rem}
4244.pay-logos span{font-size:.55rem;background:var(--pd-soft);border:1px solid var(--pd-soft-border);padding:.35rem .55rem;border-radius:6px;font-weight:600;letter-spacing:.5px}
4245
4246/* Buy card hanya desktop (mobile pakai FAB) */
4247.pd-buy-card{display:block}
4248@media (max-width:880px){ .pd-buy-card{display:none} .pd-title-row {padding-right: 0 !important;}}
4249
4250/* === Full Description === */
4251.pd-full-description{background:#fff;border:1px solid var(--pd-border);border-radius:var(--pd-radius);padding:1.6rem 1.9rem .6rem;margin:0 0 .8rem}
4252.pd-full-desc-title{margin:0 0 1.1rem;font-size:1.15rem;font-weight:600;color:var(--pd-dark)}
4253.pd-full-desc-body{font-size:.9rem;line-height:1.6;color:#334155;overflow-wrap:anywhere}
4254.pd-full-desc-body p{margin:0 0 1rem}
4255.pd-full-desc-body ul,.pd-full-desc-body ol{margin:.5rem 0 1rem 1.2rem}
4256.pd-full-desc-body table{width:100%;border-collapse:collapse;margin:1rem 0 1.2rem}
4257.pd-full-desc-body table,.pd-full-desc-body th,.pd-full-desc-body td{border-bottom:1px dotted #cbd5e1;vertical-align: top}
4258.pd-full-desc-body th,.pd-full-desc-body td{padding:.45rem .55rem;text-align:left}
4259.pd-full-desc-body img{max-width:100%;height:auto;display:block;}
4260
4261/* === Related (container) === */
4262.pd-related-full{
4263  background:#fff;border:1px solid var(--pd-border);
4264  border-radius:var(--pd-radius);padding:1.4rem 1.6rem 1.9rem;
4265  margin:0 0 2rem;
4266}
4267.pd-related-title{margin:0 0 1.1rem;font-size:1.05rem;font-weight:600;color:var(--pd-dark)}
4268
4269/* Related Grid (dirapikan: gabung selector agar tidak duplikat) */
4270.pd-related-grid,
4271.pd-related-grid--full{
4272  display:grid !important;
4273  gap:16px !important;
4274  grid-template-columns:repeat(auto-fill,minmax(170px,1fr)) !important;
4275  align-items:stretch;
4276}
4277
4278/* Fallback jika grid tidak didukung */
4279@supports not (display:grid){
4280  .pd-related-grid,
4281  .pd-related-grid--full{display:flex !important;flex-wrap:wrap}
4282  .pd-related-grid .pd-related-card,
4283  .pd-related-grid--full .pd-related-card{flex:0 0 calc(20% - 16px);max-width:calc(20% - 16px)}
4284  @media (max-width:1100px){
4285    .pd-related-grid .pd-related-card,
4286    .pd-related-grid--full .pd-related-card{flex:0 0 calc(25% - 16px);max-width:calc(25% - 16px)}
4287  }
4288  @media (max-width:820px){
4289    .pd-related-grid .pd-related-card,
4290    .pd-related-grid--full .pd-related-card{flex:0 0 calc(33.333% - 16px);max-width:calc(33.333% - 16px)}
4291  }
4292  @media (max-width:580px){
4293    .pd-related-grid .pd-related-card,
4294    .pd-related-grid--full .pd-related-card{flex:0 0 calc(50% - 16px);max-width:calc(50% - 16px)}
4295  }
4296}
4297
4298/* Related Card */
4299.pd-related-card{
4300  display:flex;flex-direction:column;border:1px solid var(--pd-soft-border);
4301  background:#fff;border-radius:10px;text-decoration:none;color:inherit;
4302  overflow:hidden;transition:var(--pd-transition);position:relative;min-width:0;
4303}
4304.pd-related-card:hover{transform:translateY(-4px);box-shadow:var(--pd-shadow)}
4305.pd-related-card .thumb{aspect-ratio:1/1;background:#f1f5f9;overflow:hidden;position:relative}
4306.pd-related-card img{width:100%;height:100%;object-fit:cover;display:block}
4307.mini-disc{position:absolute;top:6px;left:6px;background:#dc2626;color:#fff;font-size:.55rem;padding:3px 6px 2px;border-radius:4px;font-weight:600}
4308.pd-related-card .info{padding:.55rem .65rem .75rem;display:flex;flex-direction:column;gap:.25rem}
4309.pd-related-card h3{font-size:.68rem;line-height:1.25;margin:0;height:2.5em;overflow:hidden;font-weight:600;color:#334155;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
4310.pd-related-card .price{font-size:.7rem;font-weight:600;color:var(--pd-accent)}
4311.pd-related-card .sold{display:block;font-size:.6rem;font-weight:600;color:#475569;margin-top:2px;letter-spacing:.3px;background:#f1f5f9;padding:2px 6px 3px;border-radius:5px; width: max-content;}
4312.pd-related-card .price .final {font-size: .82rem; font-weight: 700; color: #0d6efd;}
4313.pd-related-card .price .ori {font-size: .6rem; text-decoration: line-through; color: #94a3b8;font-weight: 500;}
4314
4315/* === Mobile slider (scroll-snap) — gambar utama tetap tampil === */
4316.pd-mobile-slider{display:none}
4317.pd-mobile-slider img{width:100%;display:block;object-fit:contain;aspect-ratio:1/1}
4318
4319/* Gallery counter badge */
4320.pd-gallery-counter{
4321  position:absolute;
4322  bottom:12px;
4323  left:12px;
4324  background:rgba(255,255,255,0.25);
4325  color:#9E9E9E;
4326  padding:4px 10px;
4327  border-radius:10px;
4328  font-size:12px;
4329  font-weight:600;
4330  z-index:100;
4331  backdrop-filter:blur(6px);
4332  -webkit-backdrop-filter:blur(6px);
4333  font-variant-numeric:tabular-nums;
4334  pointer-events:none;
4335  display:none;
4336  box-shadow:0 1px 4px rgba(0,0,0,.15);
4337  line-height:1;
4338  border:1px solid rgba(0,0,0,0.08);
4339}
4340
4341@media (max-width:640px){
4342  .pd-media-card{border: 0;padding: 0 1rem 0;}
4343  .pd-info-card {border: 0; padding: 0 1.3rem 0; }
4344  .pd-side-card {flex: 1 1 !important;}
4345  .pd-top-layout {gap: 0; margin-bottom: 0;}
4346  .pd-variants {margin-bottom: 1.5rem;}
4347  .pd-main-image{display:none}
4348  .pd-gallery-counter{display:block}
4349  .pd-mobile-slider-wrapper{
4350    position:relative;
4351    margin-bottom:.6rem;
4352  }
4353  .pd-mobile-slider{
4354    display:flex;gap:8px;overflow-x:auto;
4355    scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;
4356  }
4357  .pd-mslide{
4358    min-width:100%;flex:0 0 100%;scroll-snap-align:center;
4359    border:1px solid var(--pd-soft-border);border-radius:10px;overflow:hidden;background:#fff
4360  }
4361  .pd-thumb-gallery{display:none} /* hide thumbnails on mobile */
4362}
4363
4364/* === Mobile Floating Action Bar === */
4365.pd-mobile-fab{
4366  position:fixed;
4367  left:0;
4368  right:0;
4369  bottom:42px; /* Position above bottom-nav (60px min-height + 6px padding) */
4370  display:none;
4371  align-items:center;
4372  gap:.75rem;
4373  padding:12px 16px;
4374  background:rgba(255,255,255,0.95);
4375  backdrop-filter:blur(20px);
4376  -webkit-backdrop-filter:blur(20px);
4377  border-top:1px solid rgba(228,231,235,0.6);
4378  box-shadow:0 -4px 16px -4px rgba(0,0,0,0.12);
4379  z-index:999; /* Below bottom-nav (1000) */
4380  transition:bottom .3s cubic-bezier(0.4, 0.0, 0.2, 1);
4381}
4382
4383/* When bottom-nav is hidden, move FAB to bottom */
4384body:has(.bottom-nav.hidden) .pd-mobile-fab {
4385  bottom: 0;
4386}
4387.pd-mobile-fab .mf-actions{display:flex;gap:.5rem;width:100%}
4388.mf-btn{
4389  border-radius:10px;
4390  padding:.6rem 1rem;
4391  font-weight:600;
4392  font-size:14px;
4393  cursor:pointer;
4394  transition:all .2s;
4395  -webkit-tap-highlight-color:transparent;
4396  display:inline-flex;
4397  align-items:center;
4398  justify-content:center;
4399  gap:4px;
4400}
4401.mf-buy{
4402  background:#fb923c;
4403  color:#fff;
4404  border:0;
4405  box-shadow:0 2px 8px rgba(251, 146, 60, 0.3);
4406  flex:1;
4407  flex-direction:column;
4408  padding:.55rem 1rem;
4409  line-height:1;
4410  min-height:44px;
4411  font-family:inherit;
4412}
4413.mf-buy .mf-buy-text{
4414  font-size:11px;
4415  font-weight:600;
4416  opacity:0.95;
4417  font-family:inherit;
4418}
4419.mf-buy .mf-buy-price{
4420  font-size:13px;
4421  font-weight:800;
4422  letter-spacing:-0.3px;
4423  font-family:inherit;
4424  line-height:1;
4425}
4426.mf-buy:active{transform:scale(0.97)}
4427.mf-cart{
4428  background:#fff;
4429  color:#fb923c;
4430  border:2px solid #fb923c;
4431  width:44px;
4432  height:44px;
4433  min-height:44px;
4434  padding:0;
4435  flex-shrink:0;
4436  font-family:inherit;
4437}
4438.mf-cart:active{transform:scale(0.95)}
4439.mf-chat{background:#fff;color:#fb923c;border:2px solid #fb923c;width:44px;height:44px;min-height:44px;padding:0;flex-shrink:0;font-family:inherit}
4440.mf-chat:active{transform:scale(0.95)}
4441.mf-chat svg{width:20px;height:20px;flex-shrink:0}
4442.mf-share{
4443  background:#fff;
4444  color:#fb923c;
4445  border:2px solid #fb923c;
4446  width:44px;
4447  height:44px;
4448  padding:0;
4449  flex-shrink:0;
4450}
4451.mf-share:active{transform:scale(0.95)}
4452.mf-btn:disabled{opacity:.5;cursor:not-allowed}
4453@media (max-width:640px){
4454  .pd-mobile-fab{display:flex}
4455  .product-single{padding-bottom:0;} /* Space for FAB + bottom-nav */
4456}
4457
4458/* === Installment cards === */
4459.pd-installment-card{border:1px solid var(--pd-border);background:#fff;border-radius:var(--pd-radius);padding:1rem 1.1rem 1.25rem;display:flex;flex-direction:column;gap:.85rem}
4460.pd-installment-card h3{margin:0 0 .2rem;font-size:.78rem;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:var(--pd-dark)}
4461
4462.inst-tabs{display:flex;gap:.4rem}
4463.inst-tab{flex:1 1 0;background:var(--pd-soft);border:1px solid var(--pd-soft-border);padding:.45rem .5rem;font-size:.63rem;font-weight:600;border-radius:6px;cursor:pointer;letter-spacing:.4px;transition:var(--pd-transition)}
4464.inst-tab.active,.inst-tab:hover{background:var(--pd-accent);color:#fff;border-color:var(--pd-accent)}
4465.inst-tab:focus{outline:2px solid var(--pd-accent);outline-offset:2px}
4466
4467.inst-tenors{display:flex;gap:.4rem;flex-wrap:wrap}
4468.tenor-btn{flex:1 0 auto;background:#fff;border:1px solid var(--pd-soft-border);padding:.42rem .55rem;font-size:.6rem;font-weight:600;border-radius:20px;cursor:pointer;transition:.18s;min-width:56px}
4469.tenor-btn.active,.tenor-btn:hover{background:#1d7df0;color:#fff;border-color:#1d7df0}
4470
4471.inst-result{display:flex;flex-direction:column;gap:.45rem;font-size:.63rem}
4472.inst-line{display:flex;justify-content:space-between;align-items:center;padding:.35rem .55rem;background:var(--pd-soft);border:1px solid var(--pd-soft-border);border-radius:6px}
4473.inst-line.highlight{background:#1d7df0;color:#fff;border-color:#1d7df0;font-weight:600}
4474.inst-line .lbl{opacity:.8}
4475.inst-line .val{font-weight:600;letter-spacing:.3px}
4476.inst-footnote{font-size:.53rem;line-height:1.25;color:#64748b;margin-top:.2rem}
4477@media (max-width: 860px){
4478  .pd-installment-card{order:3}
4479  .inst-tabs .inst-tab,.inst-tenors .tenor-btn{font-size:.65rem}
4480
4481  /* Voucher button responsive - make it simpler */
4482  .btn-buy-voucher{
4483    padding:0 12px;
4484    gap:8px
4485  }
4486  .btn-buy-voucher .bv-icon{
4487    font-size:18px
4488  }
4489  .btn-buy-voucher .bv-text{
4490    font-size:13px
4491  }
4492  .btn-buy-voucher .bv-text small{
4493    font-size:9px
4494  }
4495  .btn-buy-voucher .bv-save{
4496    font-size:11px;
4497    padding:3px 6px
4498  }
4499}
4500
4501/* Installment List v2 (eye-catching) */
4502.pd-installment-card2{
4503  background:linear-gradient(135deg,#ffffff 0%,#f1f7ff 65%,#e8f2ff 100%);
4504  border:1px solid var(--pd-border);border-radius:var(--pd-radius);
4505  padding:1rem 1.05rem 1.2rem;display:flex;flex-direction:column;gap:.5rem;
4506  position:relative;overflow:hidden
4507}
4508.pd-installment-card2:before{
4509  content:'';position:absolute;width:140px;height:140px;
4510  background:radial-gradient(circle at center,rgba(29,125,240,.18),rgba(29,125,240,0));
4511  top:-40px;right:-40px;pointer-events:none
4512}
4513.pd-installment-card2 h3{margin:0;font-size:.8rem;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:#0f2f54}
4514.pd-installment-card2 .inst-head{display:flex;align-items:center;justify-content:space-between;gap:.5rem}
4515.inst-head-badge{background:#1d7df0;color:#fff;font-size:.55rem;font-weight:600;padding:.25rem .5rem;border-radius:20px;letter-spacing:.4px;box-shadow:0 2px 6px -2px rgba(29,125,240,.6)}
4516
4517.inst-bank-tabs{display:flex;gap:.45rem}
4518.bank-tab{flex:1 1 0;background:#fff;border:1px solid var(--pd-soft-border);padding:.5rem .55rem;font-size:.63rem;font-weight:600;border-radius:7px;cursor:pointer;transition:.18s;letter-spacing:.3px}
4519.bank-tab.active,.bank-tab:hover{background:#1d7df0;color:#fff;border-color:#1d7df0;box-shadow:0 3px 10px -4px rgba(29,125,240,.6)}
4520.bank-tab:focus{outline:2px solid #1d7df0;outline-offset:2px}
4521
4522.inst-month-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.55rem}
4523.inst-month-item{display:flex;align-items:center;gap:.65rem;background:#fff;border:1px solid var(--pd-soft-border);border-radius:14px;padding:.55rem .7rem;position:relative;overflow:hidden;transition:.25s}
4524.inst-month-item:before{content:'';position:absolute;inset:0;opacity:0;background:linear-gradient(90deg,rgba(29,125,240,.08),rgba(29,125,240,0));transition:.25s}
4525.inst-month-item:hover{transform:translateY(-2px);box-shadow:0 6px 14px -6px rgba(0,40,120,.25)}
4526.inst-month-item:hover:before{opacity:1}
4527.tenor-badge{background:#1d7df0;color:#fff;font-weight:700;min-width:42px;text-align:center;padding:.38rem .4rem;font-size:.8rem;border-radius:10px;box-shadow:0 3px 10px -4px rgba(29,125,240,.6)}
4528.amount{flex:1;font-size:.8rem;font-weight:600;color:#0f2f54;letter-spacing:.3px}
4529.tag{font-size:.55rem;font-weight:600;padding:.28rem .55rem;border-radius:30px;letter-spacing:.4px;text-transform:uppercase;white-space:nowrap}
4530.tag-zero{background:#16a34a;color:#fff;box-shadow:0 2px 8px -3px rgba(16,180,90,.55)}
4531
4532.inst-note{font-size:.6rem;font-weight:600;color:#0f2f54;background:#e0f7ed;border:1px solid #b2ebd2;padding:.4rem .6rem;border-radius:8px;letter-spacing:.3px}
4533.inst-disclaimer{font-size:.52rem;line-height:1.25;color:#64748b;margin-top:-.2rem}
4534
4535@media (max-width:860px){
4536  .pd-installment-card2{order:3}
4537  .tenor-badge{min-width:38px;font-size:.75rem}
4538  .amount{font-size:.78rem}
4539}
4540
4541/* Alt Fintech Banner (compact) */
4542.inst-alt-banner--compact{
4543  background:linear-gradient(142deg,#1d7df0 0%,#3c98ff 55%,#67bdff 100%);
4544  color:#fff;border:0;padding:.95rem .95rem 1.05rem;border-radius:16px;
4545  box-shadow:0 6px 18px -6px rgba(0,70,160,.45);position:relative;overflow:hidden;min-height:auto;
4546}
4547.inst-alt-banner--compact:before,
4548.inst-alt-banner--compact:after{
4549  content:'';position:absolute;border-radius:50%;background:rgba(255,255,255,.15);filter:blur(2px);pointer-events:none;
4550}
4551.inst-alt-banner--compact:before{width:130px;height:130px;top:-50px;right:-35px}
4552.inst-alt-banner--compact:after{width:90px;height:90px;bottom:-40px;left:-30px}
4553.inst-alt-banner--compact .iab-top{margin:0 0 .55rem;display:flex;flex-direction:column;gap:.25rem}
4554.inst-alt-banner--compact .iab-title{font-size:.74rem;font-weight:700;letter-spacing:.7px;line-height:1.15}
4555.inst-alt-banner--compact .iab-sub{font-size:.55rem;font-weight:500;opacity:.95;letter-spacing:.4px}
4556
4557.iab-logos-compact{display:flex;gap:.55rem;align-items:center;margin:0 0 .6rem;flex-wrap:nowrap;justify-content:space-between}
4558.iab-logos-compact .logo-fin{width:30%}
4559.iab-logos-compact .logo{
4560  width:48px;height:48px;border-radius:14px;display:flex;align-items:center;justify-content:center;
4561  font-size:.62rem;font-weight:700;letter-spacing:.3px;position:relative;color:#fff;
4562  box-shadow:0 4px 10px -4px rgba(0,0,0,.35);background:rgba(255,255,255,.15);
4563  backdrop-filter:blur(4px);transition:.25s;text-shadow:0 1px 2px rgba(0,0,0,.4);user-select:none;
4564}
4565.iab-logos-compact .logo:before{
4566  content:'';position:absolute;inset:0;border-radius:inherit;
4567  background:linear-gradient(140deg,rgba(255,255,255,.35),rgba(255,255,255,0));mix-blend-mode:overlay;opacity:.55;pointer-events:none;
4568}
4569.iab-logos-compact .logo:hover{transform:translateY(-3px) scale(1.05);box-shadow:0 8px 18px -6px rgba(0,0,0,.45)}
4570
4571.iab-cta--compact{
4572  display:inline-flex;padding:.45rem .85rem;font-size:.58rem;font-weight:700;background:#fff;color:#0f3c6d;border-radius:30px;text-decoration:none;letter-spacing:.55px;box-shadow:0 4px 12px -5px rgba(0,0,0,.35);transition:.25s;
4573}
4574.iab-cta--compact:hover{transform:translateY(-2px);box-shadow:0 8px 18px -7px rgba(0,0,0,.45)}
4575.iab-footnote--compact{margin-top:.45rem;font-size:.46rem;opacity:.85;letter-spacing:.25px}
4576@media (max-width:860px){
4577  .inst-alt-banner--compact{order:4;margin-top:.4rem;flex:1 1}
4578  .iab-logos-compact .logo{width:46px;height:46px}
4579}
4580
4581/* === Scroll To Top (baru) === */
4582.pd-scroll-top{
4583  position:fixed;
4584  right:20px;
4585  bottom:90px;
4586  width:48px;
4587  height:48px;
4588  border-radius:50%;
4589  background:linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
4590  color:#0f2f54;
4591  border:1px solid rgba(226, 232, 240, 0.8);
4592  box-shadow:0 8px 20px -8px rgba(0,0,0,.3), 0 2px 6px rgba(0,0,0,.1);
4593  display:flex;
4594  align-items:center;
4595  justify-content:center;
4596  cursor:pointer;
4597  z-index:1105;
4598  opacity:0;
4599  transform:translateY(12px) scale(.92);
4600  transition:all .3s cubic-bezier(0.4, 0, 0.2, 1);
4601  backdrop-filter:blur(8px);
4602  -webkit-backdrop-filter:blur(8px);
4603}
4604.pd-scroll-top:hover{
4605  box-shadow:0 12px 28px -10px rgba(0,0,0,.4), 0 4px 12px rgba(0,0,0,.15);
4606  transform:translateY(0) scale(1);
4607  background:linear-gradient(135deg, #ffffff 0%, #fefefe 100%);
4608  border-color:rgba(226, 232, 240, 1);
4609}
4610.pd-scroll-top:active{
4611  transform:translateY(0) scale(.96);
4612}
4613.pd-scroll-top:focus-visible{outline:2px solid var(--pd-accent);outline-offset:2px}
4614.pd-scroll-top.visible{opacity:1;transform:translateY(0) scale(1)}
4615@media (max-width:640px){
4616  /* Dynamic positioning via JavaScript */
4617  .pd-scroll-top{
4618    right:14px;
4619    bottom:var(--scroll-top-bottom, calc(68px + 18px + env(safe-area-inset-bottom)));
4620  }
4621}
4622
4623/* === Responsive layout tweaks === */
4624@media (max-width:1250px){
4625  .pd-top-layout{grid-template-columns:minmax(260px,330px) 1fr}
4626  .pd-sidebar{grid-column:1 / -1;flex-direction:row;flex-wrap:wrap;gap:1rem}
4627  /* Other cards 2-per-row; buy card always takes full row to avoid height-mismatch gaps */
4628  .pd-side-card{flex:1 1 calc(50% - .5rem)}
4629  .pd-buy-card{flex:0 0 100%}
4630}
4631@media (max-width:880px){
4632  .pd-top-layout{grid-template-columns:1fr}
4633  .pd-sidebar{flex-direction:column; padding: 0 1rem;}
4634  .pd-title{font-size:1.2rem}
4635  .pd-price-current{font-size:1.5rem}
4636}
4637@media (max-width:640px){
4638  /* Related: 2 kolom di mobile */
4639  .pd-related-full{padding:1.2rem;border:0;padding-top: .6rem;margin-bottom:.6rem;}
4640  .pd-related-grid,.pd-related-grid--full{grid-template-columns:repeat(2,1fr) !important;gap:12px !important}
4641}
4642@media (max-width:560px){
4643  .pd-actions{flex-direction:column}
4644  .btn-add-cart,.btn-buy,.btn-buy-voucher{width:100%}
4645  .pd-full-description{padding:1.2rem;border:0;padding-bottom:0;}
4646}
4647@media (max-width:380px){
4648  .pd-related-full{padding:.9rem .7rem 1.1rem}
4649  .pd-related-grid,.pd-related-grid--full{gap:8px !important}
4650  .pd-related-card .info{padding:.4rem .48rem .55rem}
4651}
4652
4653/* === Modern Countdown (Product Detail) === */
4654.pd-countdown-modern{
4655  --cd-bg1:#0ea5e9;          /* sky-500 */
4656  --cd-bg2:#6366f1;          /* indigo-500 */
4657  --cd-text:#0b1220;
4658  --cd-contrast:#ffffff;
4659  --cd-shadow: 0 8px 24px -10px rgba(0,0,0,.25);
4660  --cd-border: rgba(255,255,255,.25);
4661  --cd-box: rgba(255,255,255,.65);
4662  --cd-box-text:#0f172a;
4663  --cd-danger:#ef4444;
4664
4665  display:flex; gap:.75rem; align-items:center;
4666  background:linear-gradient(135deg,var(--cd-bg1),var(--cd-bg2));
4667  border:1px solid var(--cd-border);
4668  color:var(--cd-contrast);
4669  border-radius:14px;
4670  padding:.6rem .8rem;
4671  box-shadow:var(--cd-shadow);
4672  position:relative;
4673  overflow:hidden;
4674  margin-bottom: 1rem;
4675}
4676
4677.pd-countdown-modern .pd-cd-icon{
4678  width:28px;height:28px;flex:0 0 28px;
4679  display:grid;place-items:center;
4680  background:rgba(255,255,255,.22);
4681  border:1px solid var(--cd-border);
4682  border-radius:10px;
4683  box-shadow: inset 0 1px 0 rgba(255,255,255,.35);
4684}
4685.pd-countdown-modern .pd-cd-icon svg{width:18px;height:18px;fill:#fff;opacity:.95}
4686
4687.pd-countdown-modern .pd-cd-body{display:flex;flex-direction:column;gap:.35rem;min-width:0}
4688
4689.pd-countdown-modern .pd-cd-label{
4690  font-size:.72rem;font-weight:700;letter-spacing:.4px;opacity:.95;line-height:1;
4691  text-shadow:0 1px 0 rgba(0,0,0,.08);
4692}
4693
4694.pd-countdown-modern .pd-cd-time{
4695  display:flex; align-items:end; gap:.35rem; flex-wrap:wrap;
4696  line-height:1;
4697}
4698
4699.pd-countdown-modern .unit{
4700  display:flex; flex-direction:column; align-items:center; gap:.18rem;
4701  min-width:58px;
4702}
4703.pd-countdown-modern .num{
4704  display:inline-flex; align-items:center; justify-content:center;
4705  min-width:58px; height:40px; padding:0 .4rem;
4706  background:var(--cd-box);
4707  color:var(--cd-box-text);
4708  border:1px solid rgba(15,23,42,.08);
4709  border-radius:10px;
4710  font: 800 1.05rem ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
4711  letter-spacing:.5px;
4712  box-shadow: 0 4px 14px -6px rgba(0,0,0,.4), inset 0 1px 0 rgba(255,255,255,.55);
4713}
4714.pd-countdown-modern .unit-label{
4715  font-size:.62rem; font-weight:700; opacity:.9; text-shadow:0 1px 0 rgba(255,255,255,.15);
4716}
4717
4718.pd-countdown-modern .colon{
4719  font-weight:900; font-size:1.05rem; opacity:.95; transform: translateY(-2px);
4720  animation: cdBlink 1.2s infinite steps(2, jump-none);
4721}
4722@keyframes cdBlink { 0%{opacity:1} 50%{opacity:.25} 100%{opacity:1} }
4723
4724.pd-countdown-modern .pd-cd-progress{
4725  display:none;
4726  position:relative;
4727  height:6px; border-radius:999px; overflow:hidden;
4728  background:rgba(255,255,255,.25);
4729  border:1px solid rgba(255,255,255,.28);
4730}
4731.pd-countdown-modern .pd-cd-progress .bar{
4732  height:100%; width:0%; background:linear-gradient(90deg,#22c55e,#a3e635);
4733  box-shadow: 0 2px 8px rgba(0,0,0,.25) inset;
4734  transition:width .35s ease;
4735}
4736
4737/* State saat hampir habis */
4738.pd-countdown-modern.danger{
4739  --cd-bg1:#f97316; /* orange-500 */
4740  --cd-bg2:#ef4444; /* red-500   */
4741}
4742
4743/* Responsive */
4744@media (max-width:640px){
4745  .pd-countdown-modern{
4746    padding:.55rem .65rem; border-radius:12px; gap:.6rem;
4747  }
4748  .pd-countdown-modern .unit{min-width:52px}
4749  .pd-countdown-modern .num{min-width:52px;height:36px;font-size:.98rem}
4750}
4751
4752/* === Modern Countdown polish (shine + compact) === */
4753.pd-countdown-modern{
4754  position:relative;
4755}
4756.pd-countdown-modern::after{
4757  content:"";
4758  position:absolute; inset:-1px;
4759  background: linear-gradient(120deg, rgba(255,255,255,.12), rgba(255,255,255,0) 35%),
4760              radial-gradient(120% 120% at 0% 0%, rgba(255,255,255,.08), rgba(255,255,255,0) 60%);
4761  border-radius: inherit;
4762  pointer-events:none;
4763  mix-blend-mode: screen;
4764  animation: sheen 4.5s ease-in-out infinite;
4765}
4766@keyframes sheen {
4767  0%   { opacity:.35; transform: translateX(-10%) }
4768  50%  { opacity:.15; transform: translateX(4%) }
4769  100% { opacity:.35; transform: translateX(-10%) }
4770}
4771
4772/* Mode compact (aktifkan dengan data-mode="compact" di elemen) */
4773.pd-countdown-modern[data-mode="compact"]{
4774  gap:.55rem; padding:.5rem .6rem; border-radius:12px;
4775}
4776.pd-countdown-modern[data-mode="compact"] .unit{
4777  min-width:46px;
4778}
4779.pd-countdown-modern[data-mode="compact"] .num{
4780  min-width:46px; height:34px; font-size:.92rem;
4781}
4782.pd-countdown-modern[data-mode="compact"] .pd-cd-label{
4783  font-size:.66rem;
4784}
4785.pd-countdown-modern[data-mode="compact"] .pd-cd-icon{
4786  width:24px; height:24px; border-radius:8px;
4787}
4788.pd-countdown-modern[data-mode="compact"] .pd-cd-progress{
4789  height:5px;
4790}
4791
4792/* ============================================================
4793   Sidebar: shared refinements for medium + tablet (641px – 1250px)
4794   Buy card = form & paylater on the same row.
4795   Alt-fintech logos compact (no over-stretching).
4796   ============================================================ */
4797@media (min-width:641px) and (max-width:1250px) {
4798  /* Buy card: put qty/buy buttons and paylater promo inline */
4799  .pd-buy-card {
4800    display: flex !important;
4801    flex-direction: row;
4802    flex-wrap: wrap;
4803    align-items: center;
4804    gap: .6rem .75rem;
4805  }
4806  .pd-buy-card .pd-buy-form { flex: 1 1 0; min-width: 0; }
4807  .pd-buy-card .paylater-promo {
4808    flex: 0 0 auto;
4809    align-self: center;
4810    margin: 0;
4811    max-width: 44%;
4812  }
4813
4814  /* Alt-fintech banner: tighter logos */
4815  .iab-logos-compact {
4816    justify-content: center;
4817    gap: 1.25rem;
4818  }
4819  .iab-logos-compact .logo-fin {
4820    width: auto;
4821    max-height: 28px;
4822    object-fit: contain;
4823  }
4824}
4825
4826/* ============================================================
4827   Tablet layout (641px – 880px)
4828   Restores 2-column grid + horizontal sidebar for mid-size screens.
4829   Overrides the generic ≤880px mobile-collapse rules above.
4830   ============================================================ */
4831@media (min-width:641px) and (max-width:880px) {
4832  /* 2-column grid: image-card | info-card */
4833  .pd-top-layout {
4834    grid-template-columns: minmax(260px, 40%) 1fr;
4835    gap: 1rem;
4836  }
4837
4838  /* Sidebar: full-width horizontal flex row below the two main columns */
4839  .pd-sidebar {
4840    grid-column: 1 / -1;
4841    flex-direction: row;
4842    flex-wrap: wrap;
4843    gap: .75rem;
4844    padding: 0;
4845  }
4846  /* Banner cards fill 2-per-row */
4847  .pd-side-card { flex: 1 1 calc(50% - .375rem); }
4848  /* Buy card: full row (tallest card – avoids white-space gap if shared with shorter banners) */
4849  .pd-buy-card  { display: block !important; flex: 0 0 100%; }
4850
4851  /* Hide installment simulator + alt-fintech banner on tablet (too much vertical space) */
4852  .pd-installment-card2,
4853  .inst-alt-banner--compact { display: none !important; }
4854
4855  /* Restore sticky add-to-cart header (hidden at ≤880px by default) */
4856  .pd-sticky-header { display: flex !important; }
4857
4858  /* Typography – tighten slightly to fit the narrower info column */
4859  .pd-price-current { font-size: 1.65rem; }
4860  .pd-title         { font-size: 1.1rem;  }
4861  .pd-info-card     { padding: 1.1rem 1.2rem 1.5rem; }
4862}
4863</style>
4864  <style data-inlined="product-grid.css">
4865/* Product Grid (Firebird) */
4866
4867.product-archive {
4868  max-width: 1320px;
4869  margin-inline: auto;
4870  padding: 0 0 3rem;
4871  /*font-family: system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,sans-serif;*/
4872}
4873
4874.pa-toolbar {
4875  display: flex;
4876  flex-wrap: wrap;
4877  gap: 1rem;
4878  justify-content: space-between;
4879  align-items: flex-start;
4880}
4881
4882.pa-title {
4883  font-size: 1.4rem;
4884  margin: 0;
4885  font-weight: 600;
4886  line-height: 1.2;
4887  color: #0f172a;
4888}
4889
4890.pa-left {
4891  display: flex;
4892  flex-wrap: wrap;
4893  gap: .75rem;
4894  align-items: center;
4895}
4896
4897.pa-filter-badge {
4898  background: #f1f5f9;
4899  border: 1px solid #e2e8f0;
4900  padding: .45rem .6rem;
4901  border-radius: 8px;
4902  font-size: .7rem;
4903  display: flex;
4904  align-items: center;
4905  gap: .4rem;
4906  line-height: 1;
4907  color: #475569;
4908}
4909.pa-filter-badge strong { color: #0f172a; }
4910.pa-filter-badge .clear {
4911  display:inline-block;
4912  text-decoration:none;
4913  background:#e2e8f0;
4914  color:#475569;
4915  font-weight:600;
4916  padding:0 .35rem .05rem;
4917  border-radius:4px;
4918  transition:.2s;
4919  font-size:.75rem;
4920  line-height:1;
4921}
4922.pa-filter-badge .clear:hover { background:#cbd5e1; color:#1e293b; }
4923
4924.pa-right {
4925  display:flex;
4926  gap:1rem;
4927  flex-wrap:wrap;
4928  align-items:flex-start;
4929  margin-bottom: 0.5rem;
4930}
4931
4932.pa-search {
4933  display:flex;
4934  gap:.5rem;
4935  align-items:center;
4936  background:#fff;
4937  border:1px solid #e2e8f0;
4938  padding:.4rem .5rem;
4939  border-radius:8px;
4940}
4941.pa-search input[type=text] {
4942  border:0;
4943  outline:none;
4944  font-size:.8rem;
4945  padding:.35rem .4rem;
4946  min-width:200px;
4947}
4948.pa-search button {
4949  background:#ec4899;
4950  border:0;
4951  color:#fff;
4952  font-size:.7rem;
4953  padding:.45rem .75rem;
4954  border-radius:6px;
4955  cursor:pointer;
4956  font-weight:600;
4957  letter-spacing:.3px;
4958  transition:.25s;
4959}
4960.pa-search button:hover { background:#db2777; }
4961
4962.pa-sort {
4963  display:flex;
4964  gap:.4rem;
4965  align-items:center;
4966  font-size:.7rem;
4967  background:#fff;
4968  border:1px solid #e2e8f0;
4969  padding:.4rem .55rem;
4970  border-radius:8px;
4971}
4972.pa-sort select {
4973  font-size:.75rem;
4974  padding:.35rem .5rem;
4975  border:1px solid #cbd5e1;
4976  border-radius:6px;
4977  background:#fff;
4978  outline:none;
4979}
4980
4981.pa-empty {
4982  border:1px dashed #cbd5e1;
4983  padding:3rem 1rem;
4984  text-align:center;
4985  border-radius:12px;
4986  background:#fff;
4987}
4988.pa-empty p {
4989  margin:0 0 1rem;
4990  font-size:.9rem;
4991  color:#475569;
4992}
4993.btn-reset {
4994  display:inline-block;
4995  background:#ec4899;
4996  color:#fff;
4997  font-size:.7rem;
4998  padding:.55rem 1rem;
4999  border-radius:8px;
5000  text-decoration:none;
5001  font-weight:600;
5002  letter-spacing:.4px;
5003  transition:.25s;
5004}
5005.btn-reset:hover { background:#db2777; }
5006
5007/* Grid */
5008.pa-grid {
5009  display:grid;
5010  gap:1rem;
5011  grid-template-columns:repeat(auto-fill, minmax(180px,1fr));
5012  align-items:stretch;
5013}
5014
5015.pa-card {
5016  background:#fff;
5017  border:1px solid #e2e8f0;
5018  border-radius:12px;
5019  display:flex;
5020  flex-direction:column;
5021  text-decoration:none;
5022  color:inherit;
5023  position:relative;
5024  overflow:hidden;
5025  transition:.28s cubic-bezier(.4,0,.2,1);
5026  min-width:0;
5027  padding: 0;
5028}
5029.pa-card:hover {
5030  transform:translateY(-4px);
5031  box-shadow:0 8px 22px -8px rgba(236,72,153,.2);
5032  border-color:#fbcfe8;
5033}
5034
5035.pa-card .thumb {
5036  aspect-ratio:1/1;
5037  background:#f1f5f9;
5038  display:flex;
5039  align-items:center;
5040  justify-content:center;
5041  overflow:hidden;
5042  position:relative;
5043}
5044.pa-card img {
5045  width:100%;
5046  height:100%;
5047  object-fit:cover;
5048  display:block;
5049}
5050.badge-disc {
5051  position:absolute;
5052  top:8px;
5053  left:8px;
5054  background:#dc2626;
5055  color:#fff;
5056  font-size:.6rem;
5057  padding:4px 6px 3px;
5058  border-radius:6px;
5059  font-weight:700;
5060  letter-spacing:.4px;
5061}
5062
5063.pa-card .body {
5064  padding:.65rem .7rem .8rem;
5065  display:flex;
5066  flex-direction:column;
5067  gap:.45rem;
5068  min-height:90px;
5069}
5070
5071.pa-card .title {
5072  font-size:.75rem;
5073  line-height:1.25;
5074  margin:0;
5075  font-weight:600;
5076  color:#1e293b;
5077  height:2.4em;
5078  overflow:hidden;
5079}
5080
5081.pa-card .prices {
5082  display:flex;
5083  flex-wrap:wrap;
5084  align-items:baseline;
5085  gap:.4rem;
5086}
5087.pa-card .prices .final {
5088  font-size:.83rem;
5089  font-weight:700;
5090  color:#ec4899;
5091}
5092.pa-card .prices .ori {
5093  font-size:.63rem;
5094  text-decoration:line-through;
5095  color:#94a3b8;
5096  font-weight:500;
5097}
5098
5099.pa-card .cat {
5100  margin-top:auto;
5101  font-size:.55rem;
5102  letter-spacing:.6px;
5103  text-transform:uppercase;
5104  background:#fdf2f8;
5105  border:1px solid #fbcfe8;
5106  padding:.28rem .45rem .25rem;
5107  border-radius:20px;
5108  font-weight:600;
5109  color:#db2777;
5110  align-self:flex-start;
5111}
5112.pa-card .meta-line {
5113    margin-top:4px;
5114    font-size:.65rem;
5115    color:#475569;
5116}
5117.pa-card .meta-line .sold {
5118    display:inline-block;
5119    background:#f1f5f9;
5120    padding:2px 6px 3px;
5121    border-radius:4px;
5122    font-weight:600;
5123    letter-spacing:.3px;
5124    color:#334155;
5125}
5126
5127/* Pagination */
5128.pa-pagination {
5129  margin-top:2rem;
5130  display:flex;
5131  flex-wrap:wrap;
5132  gap:.4rem;
5133  justify-content:center;
5134  font-size:.7rem;
5135  align-items:center;
5136}
5137.pa-pagination a,
5138.pa-pagination span {
5139  min-width:34px;
5140  height:34px;
5141  display:flex;
5142  align-items:center;
5143  justify-content:center;
5144  border-radius:8px;
5145  text-decoration:none;
5146  font-weight:600;
5147  padding:0 .6rem;
5148  box-sizing:border-box;
5149  border:1px solid #e2e8f0;
5150  background:#fff;
5151  color:#334155;
5152  transition:.25s;
5153}
5154.pa-pagination a:hover {
5155  background:#fdf2f8;
5156  color:#db2777;
5157  border-color:#fbcfe8;
5158}
5159.pa-pagination .current {
5160  background:#ec4899;
5161  color:#fff;
5162  border-color:#ec4899;
5163}
5164.pa-pagination .gap {
5165  border:none;
5166  background:transparent;
5167  font-weight:400;
5168  color:#94a3b8;
5169}
5170
5171/* =============== Mobile Filter Bar (Tokopedia-like) =============== */
5172.pa-mobile-filterbar{display:none}
5173@media (max-width: 640px){
5174  /* sembunyikan tombol lama */
5175  .pa-btn-filter{display:none}
5176
5177  .pa-mobile-filterbar{
5178    display:block;
5179    position:sticky;
5180    top:0; /* sesuaikan jika ada header fixed di atas */
5181    z-index:5;
5182    background:#fff;
5183    border-bottom:1px solid #e2e8f0;
5184    padding:.4rem .5rem;
5185    margin:-.4rem -1rem .6rem; /* full-bleed di container mobile */
5186  }
5187  .pa-mobile-filterbar .mf-scroll{
5188    display:flex;align-items:center;gap:.5rem;
5189    overflow-x:auto;
5190    -webkit-overflow-scrolling:touch;
5191    scrollbar-width:none;
5192  }
5193  .pa-mobile-filterbar .mf-scroll::-webkit-scrollbar{display:none}
5194
5195  .mf-chip{
5196    flex:0 0 auto;
5197    background:#fff;
5198    color:#334155;
5199    text-decoration:none;
5200    border:1px solid #e2e8f0;
5201    border-radius:999px;
5202    padding:.44rem .7rem;
5203    font-size:.78rem;
5204    font-weight:600;
5205    letter-spacing:.2px;
5206    white-space:nowrap;
5207  }
5208  .mf-chip:active{transform:scale(.98)}
5209  .mf-chip.active{
5210    background:#fdf2f8;
5211    border-color:#ec4899;
5212    color:#ec4899;
5213  }
5214
5215  .mf-filter-btn{
5216    position:relative;
5217    flex:0 0 auto;
5218    width:38px;height:38px;
5219    border-radius:50%;
5220    border:1.5px solid #ec4899;
5221    background:#fff;
5222    display:inline-flex;align-items:center;justify-content:center;
5223    color:#ec4899;
5224    cursor:pointer;
5225  }
5226  .mf-filter-btn:active{transform:scale(.96)}
5227  .mf-filter-btn svg{width:18px;height:18px;display:block}
5228  .mf-filter-btn .mf-badge{
5229    position:absolute;top:0;right:-4px;
5230    min-width:18px;height:18px;border-radius:9px;
5231    background:#ef4444;color:#fff;
5232    font-size:.65rem;font-weight:700;
5233    display:flex;align-items:center;justify-content:center;
5234    padding:0 4px;line-height:1;
5235    box-shadow:0 2px 6px -2px rgba(239,68,68,.6);
5236  }
5237}
5238
5239/* Responsive */
5240@media (max-width: 640px) {
5241  .product-archive { padding: 0 1rem 3rem; }
5242  .pa-grid { grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); }
5243  .pa-search input[type=text]{ min-width:140px; }
5244  .pa-toolbar { flex-direction:column; align-items:stretch; margin-bottom: 0; display: none;}
5245  .pa-right { display: none; }
5246  .pa-sort { width: 100%; justify-content:center; }
5247  .pa-mobile-filterbar { display: flex !important; }
5248}
5249
5250.pa-mobile-filterbar {
5251  display: none;
5252  align-items: center;
5253  gap: 8px;
5254  padding: 8px 0 8px 12px;
5255  background: #fff;
5256  width: 100vw;
5257}
5258.pa-mobile-filterbar .mf-scroll {
5259  display: flex;
5260  gap: 8px;
5261  overflow-x: auto;
5262  -webkit-overflow-scrolling: touch;
5263  scrollbar-width: none; /* Firefox */
5264  flex: 1 1 auto; /* takes remaining width */
5265}
5266.pa-mobile-filterbar .mf-scroll::-webkit-scrollbar { display: none; } /* WebKit */
5267.pa-mobile-filterbar .mf-chip {
5268  flex: 0 0 auto; /* prevent shrinking, keep width based on content */
5269  white-space: nowrap;
5270}
5271.pa-mobile-filterbar .mf-filter-btn {
5272  flex: 0 0 auto; /* do not shrink */
5273  position: relative; /* keep in place, not inside scroller */
5274  display: inline-flex;
5275  align-items: center;
5276  gap: 6px;
5277}
5278
5279/* Optional: ensure on small screens the button stays visible with min width */
5280@media (max-width: 768px) {
5281  .pa-mobile-filterbar .mf-filter-btn {
5282    padding: 6px 10px;
5283  }
5284}
5285
5286/* Product Specification Chips */
5287.pa-card .spec-chips {
5288  font-size: .6rem;
5289  color: #64748b;
5290  font-weight: 500;
5291  letter-spacing: .2px;
5292  margin-top: 2px;
5293  margin-bottom: 4px;
5294  line-height: 1.2;
5295  opacity: 0.9;
5296  max-width: 100%;
5297  overflow: hidden;
5298  text-overflow: ellipsis;
5299  white-space: nowrap;
5300}
5301
5302.pa-card .spec-chips .spec-divider {
5303  color: #94a3b8;
5304  font-weight: 400;
5305  margin: 0 1px;
5306}
5307</style>
5308  <style data-inlined="site-notice.css">
5309/**
5310 * Site Notice Banner - Reusable Notification System
5311 * Dismissible top bar for announcements, holidays, maintenance, etc.
5312 */
5313
5314.holiday-notice {
5315    position: fixed;
5316    top: 0;
5317    left: 0;
5318    right: 0;
5319    z-index: 9999;
5320    background: linear-gradient(135deg, #db2777 0%, #f472b6 100%);
5321    border-bottom: 2px solid rgba(255, 255, 255, 0.2);
5322    box-shadow: 0 2px 8px rgba(219, 39, 119, 0.25);
5323    animation: slideDown 0.4s cubic-bezier(0.4, 0, 0.2, 1);
5324    transform-origin: top;
5325}
5326
5327.holiday-notice.hidden {
5328    animation: slideUp 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;
5329}
5330
5331@keyframes slideDown {
5332    from {
5333        transform: translateY(-100%);
5334        opacity: 0;
5335    }
5336    to {
5337        transform: translateY(0);
5338        opacity: 1;
5339    }
5340}
5341
5342@keyframes slideUp {
5343    from {
5344        transform: translateY(0);
5345        opacity: 1;
5346    }
5347    to {
5348        transform: translateY(-100%);
5349        opacity: 0;
5350    }
5351}
5352
5353.holiday-notice-container {
5354    max-width: 1400px;
5355    margin: 0 auto;
5356    padding: 12px 20px;
5357    display: flex;
5358    align-items: center;
5359    justify-content: space-between;
5360    gap: 16px;
5361}
5362
5363.holiday-notice-content {
5364    display: flex;
5365    align-items: center;
5366    gap: 12px;
5367    flex: 1;
5368}
5369
5370.holiday-notice-icon {
5371    font-size: 20px;
5372    line-height: 1;
5373    flex-shrink: 0;
5374}
5375
5376.holiday-notice-text {
5377    color: #ffffff;
5378    font-size: 14px;
5379    font-weight: 500;
5380    line-height: 1.5;
5381    margin: 0;
5382    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
5383}
5384
5385.holiday-notice-emoji {
5386    display: inline-block;
5387    animation: bounce 2s ease-in-out infinite;
5388}
5389
5390@keyframes bounce {
5391    0%, 100% {
5392        transform: translateY(0);
5393    }
5394    50% {
5395        transform: translateY(-4px);
5396    }
5397}
5398
5399.holiday-notice-close {
5400    background: rgba(255, 255, 255, 0.2);
5401    border: 1px solid rgba(255, 255, 255, 0.3);
5402    border-radius: 6px;
5403    padding: 6px;
5404    cursor: pointer;
5405    color: #ffffff;
5406    transition: all 0.2s ease;
5407    flex-shrink: 0;
5408    display: flex;
5409    align-items: center;
5410    justify-content: center;
5411    width: 32px;
5412    height: 32px;
5413}
5414
5415.holiday-notice-close:hover {
5416    background: rgba(255, 255, 255, 0.35);
5417    transform: scale(1.05);
5418}
5419
5420.holiday-notice-close:active {
5421    transform: scale(0.95);
5422}
5423
5424.holiday-notice-close:focus {
5425    outline: 2px solid rgba(255, 255, 255, 0.6);
5426    outline-offset: 2px;
5427}
5428
5429/* Body padding adjustment when banner is visible */
5430body.has-holiday-notice {
5431    padding-top: 52px;
5432}
5433
5434/* Tablet Styles */
5435@media (max-width: 1024px) {
5436    .holiday-notice-container {
5437        padding: 10px 16px;
5438    }
5439
5440    .holiday-notice-text {
5441        font-size: 13px;
5442    }
5443
5444    .holiday-notice-icon {
5445        font-size: 18px;
5446    }
5447
5448    body.has-holiday-notice {
5449        padding-top: 48px;
5450    }
5451}
5452
5453/* Mobile Styles */
5454@media (max-width: 640px) {
5455    .holiday-notice-container {
5456        padding: 10px 12px;
5457        gap: 8px;
5458    }
5459
5460    .holiday-notice-content {
5461        gap: 8px;
5462    }
5463
5464    .holiday-notice-text {
5465        font-size: 12px;
5466        line-height: 1.4;
5467    }
5468
5469    .holiday-notice-icon {
5470        font-size: 16px;
5471    }
5472
5473    .holiday-notice-close {
5474        width: 28px;
5475        height: 28px;
5476    }
5477
5478    .holiday-notice-close svg {
5479        width: 16px;
5480        height: 16px;
5481    }
5482
5483    body.has-holiday-notice {
5484        padding-top: 44px;
5485    }
5486}
5487
5488/* Ensure header doesn't get covered - removed z-index as it conflicts with dropdown stacking */
5489/* .header,
5490.navbar,
5491header {
5492    position: relative;
5493    z-index: 100;
5494} */
5495
5496</style>
5497        <style data-inlined="benchmark.css">
5498/**
5499 * Benchmark Section Styles
5500 * For product detail page benchmark display
5501 */
5502
5503.pd-benchmark-section {
5504    background: #fff;
5505    border-radius: 12px;
5506    padding: 24px;
5507    margin: 32px 0;
5508    box-shadow: 0 2px 8px rgba(0,0,0,0.06);
5509    border: 1px solid #e5e7eb;
5510}
5511
5512.benchmark-header {
5513    margin-bottom: 20px;
5514}
5515
5516.benchmark-title {
5517    font-size: 22px;
5518    font-weight: 700;
5519    color: #1a1a1a;
5520    margin: 0 0 12px 0;
5521    display: flex;
5522    align-items: center;
5523    gap: 8px;
5524}
5525
5526.benchmark-hint {
5527    font-size: 14px;
5528    color: #6b7280;
5529    margin: 0;
5530}
5531
5532/* Preview Badges */
5533.benchmark-preview-badges {
5534    display: flex;
5535    flex-wrap: wrap;
5536    gap: 10px;
5537    margin-top: 12px;
5538}
5539
5540.benchmark-badge {
5541    display: inline-flex;
5542    align-items: center;
5543    gap: 6px;
5544    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
5545    color: #fff;
5546    padding: 8px 16px;
5547    border-radius: 20px;
5548    font-size: 13px;
5549    font-weight: 500;
5550    box-shadow: 0 2px 6px rgba(102, 126, 234, 0.25);
5551    transition: all 0.2s ease;
5552}
5553
5554.benchmark-badge:hover {
5555    transform: translateY(-2px);
5556    box-shadow: 0 4px 12px rgba(102, 126, 234, 0.35);
5557}
5558
5559.benchmark-badge strong {
5560    font-weight: 600;
5561    opacity: 0.9;
5562}
5563
5564/* Load Button */
5565.btn-load-benchmark {
5566    display: flex;
5567    align-items: center;
5568    justify-content: center;
5569    gap: 10px;
5570    width: 100%;
5571    max-width: 420px;
5572    padding: 14px 24px;
5573    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
5574    color: #fff;
5575    border: none;
5576    border-radius: 8px;
5577    font-size: 15px;
5578    font-weight: 600;
5579    cursor: pointer;
5580    transition: all 0.3s ease;
5581    box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
5582    margin: 0 auto;
5583}
5584
5585.btn-load-benchmark:hover:not(:disabled) {
5586    transform: translateY(-2px);
5587    box-shadow: 0 6px 16px rgba(102, 126, 234, 0.4);
5588}
5589
5590.btn-load-benchmark:disabled {
5591    opacity: 0.6;
5592    cursor: not-allowed;
5593}
5594
5595.btn-load-benchmark .btn-icon {
5596    font-size: 20px;
5597}
5598
5599.btn-load-benchmark.loading .btn-icon {
5600    animation: spin 1s linear infinite;
5601}
5602
5603@keyframes spin {
5604    from { transform: rotate(0deg); }
5605    to { transform: rotate(360deg); }
5606}
5607
5608/* Benchmark Content */
5609.benchmark-content {
5610    margin-top: 24px;
5611    padding-top: 24px;
5612    border-top: 2px solid #f3f4f6;
5613}
5614
5615.benchmark-loading {
5616    text-align: center;
5617    padding: 40px 20px;
5618    color: #6b7280;
5619}
5620
5621.benchmark-loading-spinner {
5622    width: 48px;
5623    height: 48px;
5624    margin: 0 auto 16px;
5625    border: 4px solid #f3f4f6;
5626    border-top-color: #667eea;
5627    border-radius: 50%;
5628    animation: spin 1s linear infinite;
5629}
5630
5631.benchmark-error {
5632    text-align: center;
5633    padding: 32px 20px;
5634    color: #ef4444;
5635    background: #fee2e2;
5636    border-radius: 8px;
5637}
5638
5639/* Benchmark Data Display */
5640.benchmark-data-grid {
5641    display: grid;
5642    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
5643    gap: 16px;
5644    margin-bottom: 24px;
5645}
5646
5647.benchmark-card {
5648    background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%);
5649    border: 1px solid #e5e7eb;
5650    border-radius: 10px;
5651    padding: 18px;
5652    text-align: center;
5653    transition: all 0.2s ease;
5654}
5655
5656.benchmark-card:hover {
5657    transform: translateY(-3px);
5658    box-shadow: 0 6px 16px rgba(102, 126, 234, 0.15);
5659}
5660
5661.benchmark-card-label {
5662    font-size: 13px;
5663    color: #6b7280;
5664    font-weight: 500;
5665    margin-bottom: 8px;
5666    text-transform: uppercase;
5667    letter-spacing: 0.5px;
5668}
5669
5670.benchmark-card-value {
5671    font-size: 24px;
5672    font-weight: 700;
5673    color: #1a1a1a;
5674    line-height: 1.2;
5675}
5676
5677.benchmark-card-value.large {
5678    font-size: 28px;
5679}
5680
5681.benchmark-card-subtitle {
5682    font-size: 12px;
5683    color: #9ca3af;
5684    margin-top: 4px;
5685}
5686
5687/* Spec Details */
5688.benchmark-specs {
5689    background: #f9fafb;
5690    border-radius: 10px;
5691    padding: 20px;
5692    margin-top: 20px;
5693}
5694
5695.benchmark-specs-title {
5696    font-size: 16px;
5697    font-weight: 600;
5698    color: #1a1a1a;
5699    margin: 0 0 16px 0;
5700}
5701
5702.benchmark-spec-row {
5703    display: flex;
5704    justify-content: space-between;
5705    padding: 10px 0;
5706    border-bottom: 1px solid #e5e7eb;
5707}
5708
5709.benchmark-spec-row:last-child {
5710    border-bottom: none;
5711}
5712
5713.benchmark-spec-label {
5714    font-size: 14px;
5715    color: #6b7280;
5716    font-weight: 500;
5717}
5718
5719.benchmark-spec-value {
5720    font-size: 14px;
5721    color: #1a1a1a;
5722    font-weight: 600;
5723    text-align: right;
5724}
5725
5726/* Score Badges */
5727.benchmark-score-badge {
5728    display: inline-block;
5729    padding: 6px 14px;
5730    border-radius: 20px;
5731    font-size: 13px;
5732    font-weight: 600;
5733    margin-top: 8px;
5734}
5735
5736.benchmark-score-badge.excellent {
5737    background: #10b981;
5738    color: #fff;
5739}
5740
5741.benchmark-score-badge.good {
5742    background: #3b82f6;
5743    color: #fff;
5744}
5745
5746.benchmark-score-badge.average {
5747    background: #f59e0b;
5748    color: #fff;
5749}
5750
5751.benchmark-score-badge.poor {
5752    background: #ef4444;
5753    color: #fff;
5754}
5755
5756/* Attribution */
5757.benchmark-attribution {
5758    text-align: center;
5759    margin-top: 24px;
5760    padding-top: 20px;
5761    border-top: 1px solid #e5e7eb;
5762    font-size: 13px;
5763    color: #9ca3af;
5764}
5765
5766.benchmark-attribution a {
5767    color: #667eea;
5768    text-decoration: none;
5769    font-weight: 500;
5770}
5771
5772.benchmark-attribution a:hover {
5773    text-decoration: underline;
5774}
5775
5776/* Responsive */
5777@media (max-width: 768px) {
5778    .pd-benchmark-section {
5779        padding: 16px;
5780        margin: 20px 0;
5781    }
5782
5783    .benchmark-title {
5784        font-size: 18px;
5785    }
5786
5787    .benchmark-preview-badges {
5788        flex-direction: column;
5789        gap: 8px;
5790    }
5791
5792    .benchmark-badge {
5793        justify-content: center;
5794    }
5795
5796    .btn-load-benchmark {
5797        font-size: 14px;
5798        padding: 12px 20px;
5799    }
5800
5801    .benchmark-data-grid {
5802        grid-template-columns: 1fr 1fr;
5803        gap: 12px;
5804    }
5805
5806    .benchmark-card {
5807        padding: 14px;
5808    }
5809
5810    .benchmark-card-value {
5811        font-size: 20px;
5812    }
5813
5814    .benchmark-spec-row {
5815        flex-direction: column;
5816        gap: 4px;
5817    }
5818
5819    .benchmark-spec-value {
5820        text-align: left;
5821    }
5822}
5823
5824@media (max-width: 480px) {
5825    .benchmark-data-grid {
5826        grid-template-columns: 1fr;
5827    }
5828}
5829
5830</style>
5831    <style data-inlined="product-filters.css">
5832/* =========================================================
5833   Product Filters (Desktop + Mobile Drawer)
5834   Light Only + FIX:
5835   - Desktop: f-actions selalu tampil (static bottom)
5836   - Mobile: konten filter dapat discroll
5837   ========================================================= */
5838
5839/* ========== Layout wrapper (tidak mengubah grid produk) ========== */
5840.product-archive.with-filters .pa-layout {
5841  display:flex;
5842  gap:24px;
5843  align-items:flex-start;
5844}
5845.pa-main { flex:1; min-width:0; }
5846
5847/* =========================================================
5848   DESKTOP PANEL
5849   ========================================================= */
5850.pa-filters {
5851  width:300px;
5852  background:linear-gradient(145deg,#ffffff 0%,#f8fafc 55%,#f1f5f9 100%);
5853  border:1px solid #e2e8f0;
5854  padding:18px 18px 12px; /* bottom dikurangi karena f-actions punya padding sendiri */
5855  border-radius:18px;
5856  position:sticky;
5857  top:80px;
5858  height:calc(100vh - 100px); /* tinggi stabil untuk inner scroll */
5859  font-size:14px;
5860  box-shadow:0 1px 2px rgba(0,0,0,.04),0 4px 14px rgba(15,23,42,.08);
5861  backdrop-filter:blur(3px);
5862  -webkit-backdrop-filter:blur(3px);
5863  transition:box-shadow .3s, background .4s;
5864  display:flex;
5865  flex-direction:column;
5866  overflow:hidden; /* cegah double scroll, inner yang scroll */
5867}
5868.pa-filters::before {
5869  content:"";
5870  position:absolute;
5871  inset:0 auto 0 0;
5872  width:7px;
5873  border-radius:18px 0 0 18px;
5874  background:linear-gradient(180deg,#f59e0b 0%,#ffb53a 35%,#1d6ce0 100%);
5875}
5876.pa-filters.active { box-shadow:0 8px 32px -4px rgba(15,23,42,.35); }
5877
5878/* Scroll area (desktop/mode shared) */
5879.filters-scroll {
5880  flex:1;
5881  min-height:0;          /* wajib agar overflow terbaca dalam flex container */
5882  overflow:auto;
5883  padding-right:6px;
5884  margin-right:-6px;      /* kompensasi scrollbar */
5885  scrollbar-width:thin;
5886  overscroll-behavior:contain;
5887}
5888.filters-scroll::-webkit-scrollbar { width:8px; }
5889.filters-scroll::-webkit-scrollbar-thumb {
5890  background:#cbd5e1;
5891  border-radius:20px;
5892  border:2px solid #f1f5f9;
5893}
5894.filters-scroll::-webkit-scrollbar-thumb:hover { background:#94a3b8; }
5895
5896/* Heading */
5897.pa-filters h2 {
5898  margin:0 0 14px;
5899  font-size:20px;
5900  font-weight:700;
5901  letter-spacing:.4px;
5902  line-height:1.15;
5903  background:linear-gradient(90deg,#0f172a,#1d6ce0 60%,#0f172a);
5904  -webkit-background-clip:text;
5905  color:transparent;
5906  background-size:200% 100%;
5907  animation:titleSheen 8s linear infinite;
5908}
5909@keyframes titleSheen {
5910  0%{background-position:0 0}
5911  50%{background-position:100% 0}
5912  100%{background-position:0 0}
5913}
5914
5915/* Form groups */
5916.pa-filters .f-group { margin-bottom:18px; position:relative; }
5917.pa-filters label {
5918  display:block;
5919  font-weight:600;
5920  margin:0 0 6px;
5921  font-size:13px;
5922  color:#0f172a;
5923  letter-spacing:.3px;
5924}
5925
5926/* Inputs / selects */
5927.pa-filters input[type=text],
5928.pa-filters input[type=number],
5929.pa-filters select {
5930  width:100%;
5931  padding:7px 10px;
5932  border:1px solid #cbd5e1;
5933  border-radius:8px;
5934  font-size:13px;
5935  min-height:34px;
5936  background:#ffffff;
5937  box-sizing:border-box;
5938  color:#0f172a;
5939  transition:border-color .25s, box-shadow .25s, background .25s;
5940  -webkit-appearance:none;
5941  -moz-appearance:none;
5942  appearance:none;
5943}
5944.pa-filters select {
5945  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%230f172a' d='M6 9L1 4h10z'/%3E%3C/svg%3E");
5946  background-repeat:no-repeat;
5947  background-position:right 10px center;
5948  background-size:12px;
5949  padding-right:32px;
5950}
5951.pa-filters input[type=text]:hover,
5952.pa-filters input[type=number]:hover,
5953.pa-filters select:hover { border-color:#94b5ec; }
5954.pa-filters input[type=text]:focus,
5955.pa-filters input[type=number]:focus,
5956.pa-filters select:focus {
5957  outline:none;
5958  border-color:#1d6ce0;
5959  box-shadow:0 0 0 3px rgba(29,108,224,.25);
5960  background:#f8fbff;
5961}
5962.pa-filters .range { display:flex; gap:10px; }
5963.pa-filters .range input { flex:1; }
5964.pa-filters .hint { font-size:11px; color:#64748b; margin-top:6px; }
5965
5966/* Spec blocks */
5967.f-specs { margin-top:4px; }
5968.spec-attr {
5969  margin:0 0 14px;
5970  padding:12px 12px 10px;
5971  border:1px solid #e2e8f0;
5972  border-radius:14px;
5973  background:linear-gradient(130deg,#ffffff 0%,#f7faff 85%);
5974  position:relative;
5975  transition:border-color .3s, box-shadow .3s, background .4s;
5976}
5977.spec-attr.collapsed { padding-bottom:8px; }
5978.spec-attr.has-selected {
5979  border-color:#1d6ce0;
5980  box-shadow:0 0 0 2px rgba(29,108,224,.15),0 2px 8px -2px rgba(29,108,224,.35);
5981  background:linear-gradient(135deg,#ffffff 0%,#e9f2ff 100%);
5982}
5983.spec-attr-title {
5984  font-weight:600;
5985  margin:0 0 8px;
5986  font-size:13px;
5987  display:flex;
5988  justify-content:space-between;
5989  align-items:center;
5990  cursor:pointer;
5991  color:#0f172a;
5992  user-select:none;
5993}
5994.spec-attr-title .chevron {
5995  font-size:10px;
5996  transition:transform .25s, opacity .25s;
5997  opacity:.65;
5998}
5999.spec-attr.collapsed .spec-attr-title .chevron {
6000  transform:rotate(-90deg);
6001  opacity:.5;
6002}
6003
6004/* Spec options */
6005.spec-attr-options { display:flex; flex-wrap:wrap; gap:6px; }
6006.spec-attr-options label {
6007  display:inline-flex;
6008  align-items:center;
6009  gap:4px;
6010  font-weight:500;
6011  border:1px solid #d4dde7;
6012  padding:6px 9px;
6013  border-radius:30px;
6014  cursor:pointer;
6015  background:#f3f6fa;
6016  font-size:12px;
6017  line-height:1.1;
6018  transition:background .25s, border-color .25s, color .25s, box-shadow .25s;
6019  position:relative;
6020}
6021.spec-attr-options label:hover { background:#e8f1ff; border-color:#b1cff3; }
6022.spec-attr-options input {
6023  margin:0;
6024  position:absolute;
6025  inset:0;
6026  opacity:0;
6027  cursor:pointer;
6028}
6029.spec-attr-options label:has(input:checked) {
6030  background:linear-gradient(135deg,#1d6ce0 0%,#155ab8 100%);
6031  border-color:#1d6ce0;
6032  color:#ffffff;
6033  box-shadow:0 2px 6px -1px rgba(29,108,224,.55);
6034}
6035.spec-attr-options label:has(input:checked)::after {
6036  content:"";
6037  position:absolute;
6038  right:6px;
6039  top:6px;
6040  width:6px;
6041  height:6px;
6042  background:#fff;
6043  border-radius:50%;
6044  box-shadow:0 0 0 4px rgba(255,255,255,.25),0 0 0 7px rgba(29,108,224,.5);
6045  animation:pulseDot 1.9s ease-out infinite;
6046}
6047@keyframes pulseDot {
6048  0%{transform:scale(.8);opacity:.9}
6049  60%{transform:scale(1.4);opacity:.15}
6050  100%{transform:scale(.8);opacity:.9}
6051}
6052
6053/* Badges */
6054.pa-filter-badges { margin:0 0 18px; display:flex; flex-wrap:wrap; gap:6px; }
6055.pa-filter-badge {
6056  display:inline-flex;
6057  gap:6px;
6058  align-items:center;
6059  background:linear-gradient(135deg,#eef4ff 0%,#e0edff 100%);
6060  border:1px solid #b4cff3;
6061  padding:5px 10px 5px 12px;
6062  border-radius:22px;
6063  margin:2px 4px 0 0;
6064  font-size:11px;
6065  font-weight:500;
6066  line-height:1;
6067  color:#0f172a;
6068  position:relative;
6069  overflow:hidden;
6070}
6071.pa-filter-badge::before {
6072  content:"";
6073  position:absolute;
6074  inset:0;
6075  background:linear-gradient(90deg,rgba(255,255,255,0),rgba(255,255,255,.35),rgba(255,255,255,0));
6076  transform:translateX(-100%);
6077  animation:badgeSheen 6s linear infinite;
6078}
6079@keyframes badgeSheen {
6080  0%{transform:translateX(-100%)}
6081  55%{transform:translateX(120%)}
6082  100%{transform:translateX(120%)}
6083}
6084.pa-filter-badge a.clear {
6085  text-decoration:none;
6086  color:#c02828;
6087  font-weight:700;
6088  padding:0 3px;
6089  display:inline-flex;
6090  align-items:center;
6091  justify-content:center;
6092  border-radius:50%;
6093  line-height:1;
6094  transition:background .25s, color .25s;
6095}
6096.pa-filter-badge a.clear:hover { background:#ffeded; color:#a40000; }
6097.pa-btn-filter { width: 100%;}
6098
6099/* =========================================================
6100   ACTIONS (DESKTOP) - STATIC BOTTOM
6101   ========================================================= */
6102.f-actions {
6103  margin-top:10px;
6104  display:flex;
6105  gap:12px;
6106  flex-wrap:wrap;
6107  padding:16px 0 4px;
6108  border-top:1px solid #e2e8f0;
6109  background:linear-gradient(180deg,rgba(255,255,255,0) 0%,#f1f5f9 70%);
6110  flex-shrink:0;
6111}
6112.f-actions button {
6113  background:linear-gradient(135deg,#1d6ce0 0%,#155ab8 100%);
6114  color:#fff;
6115  border:none;
6116  padding:10px 20px;
6117  border-radius:12px;
6118  cursor:pointer;
6119  font-size:14px;
6120  font-weight:600;
6121  letter-spacing:.3px;
6122  display:inline-flex;
6123  align-items:center;
6124  gap:6px;
6125  box-shadow:0 4px 14px -2px rgba(29,108,224,.45);
6126  transition:transform .25s, box-shadow .3s;
6127}
6128.f-actions button:hover { transform:translateY(-3px); box-shadow:0 8px 26px -4px rgba(29,108,224,.6); }
6129.f-actions button:active { transform:translateY(-1px) scale(.98); box-shadow:0 4px 14px -4px rgba(29,108,224,.55); }
6130.f-actions .reset {
6131  font-size:12px;
6132  text-decoration:none;
6133  color:#d92d2d;
6134  align-self:center;
6135  font-weight:600;
6136  padding:4px 6px;
6137  border-radius:8px;
6138  transition:background .25s, color .25s;
6139}
6140.f-actions .reset:hover { background:#ffe6e6; color:#b80000; text-decoration:underline; }
6141.pa-filters .close-drawer { display: none; }
6142
6143/* Form wrapper: flex container agar inner .filters-scroll bisa scroll di desktop */
6144.filter-form {
6145  flex: 1;
6146  min-height: 0;
6147  display: flex;
6148  flex-direction: column;
6149  overflow: hidden;
6150}
6151
6152/* =========================================================
6153   FILTER TOGGLE (MOBILE TRIGGER)
6154   ========================================================= */
6155.filter-toggle {
6156  display:none;
6157  margin:0 0 14px;
6158  background:linear-gradient(135deg,#1d6ce0 0%,#155ab8 100%);
6159  color:#fff;
6160  border:none;
6161  padding:11px 18px;
6162  font-size:14px;
6163  font-weight:600;
6164  border-radius:14px;
6165  cursor:pointer;
6166  letter-spacing:.4px;
6167  box-shadow:0 4px 14px -2px rgba(29,108,224,.55);
6168  position:relative;
6169  z-index:100;
6170  transition:box-shadow .3s, transform .25s;
6171}
6172.filter-toggle:hover { box-shadow:0 8px 26px -4px rgba(29,108,224,.6); transform:translateY(-2px); }
6173.filter-toggle:active { transform:translateY(0); box-shadow:0 4px 14px -4px rgba(29,108,224,.55); }
6174.filter-toggle .count-badge {
6175  display:inline-block;
6176  background:linear-gradient(135deg,#f59e0b 0%,#ffb43d 55%,#f59e0b 100%);
6177  color:#1d2b44;
6178  font-size:11px;
6179  font-weight:700;
6180  padding:3px 7px 4px;
6181  border-radius:30px;
6182  margin-left:8px;
6183  box-shadow:0 0 0 2px #ffffff,0 0 0 4px #1d6ce0;
6184}
6185
6186/* Overlay (mobile) */
6187.pa-filters-overlay { display:none; }
6188
6189/* =========================================================
6190   MOBILE DRAWER
6191   ========================================================= */
6192@media (max-width:768px){
6193  .product-archive.with-filters .pa-layout { flex-direction:column; gap:12px; }
6194  .filter-toggle { display:inline-flex; align-items:center; gap:6px; }
6195
6196  .pa-filters-overlay {
6197    position:fixed;
6198    inset:0;
6199    background:rgba(0,20,45,.55);
6200    z-index:998;
6201    opacity:0;
6202    transition:opacity .35s;
6203    display:block;
6204    pointer-events:none;
6205    backdrop-filter:blur(2px);
6206  }
6207  .pa-filters-overlay.active { opacity:1; pointer-events:auto; }
6208
6209  .pa-filters {
6210    position:fixed;
6211    top:auto;
6212    left:0; right:0; bottom:0;
6213    max-height:92vh;
6214    height:auto;
6215    width:100%;
6216    border-radius:30px 30px 0 0;
6217    border:1px solid #dbe4ef;
6218    padding:26px 22px 0;
6219    z-index:10000; /* di atas bottom-nav (9998) */
6220    transform:translateY(102%);
6221    transition:transform .55s cubic-bezier(.68,-0.35,.25,1);
6222    box-shadow:0 -4px 40px -6px rgba(15,23,42,.45);
6223    overflow:hidden; /* outer hidden, inner scroll */
6224  }
6225  .pa-filters.active { transform:translateY(0); }
6226
6227  /* Drag handle indicator */
6228  .pa-filters::after {
6229    content:"";
6230    display:block;
6231    position:absolute;
6232    top:10px;
6233    left:50%;
6234    transform:translateX(-50%);
6235    width:36px;
6236    height:4px;
6237    background:#cbd5e1;
6238    border-radius:4px;
6239  }
6240  body.filter-open { overflow:hidden; touch-action:none; }
6241
6242  /* Restore mobile-friendly input sizes (prevent iOS zoom on focus) */
6243  .pa-filters input[type=text],
6244  .pa-filters input[type=number],
6245  .pa-filters select {
6246    font-size:16px;
6247    min-height:44px;
6248    padding:10px 12px;
6249    border-radius:10px;
6250  }
6251  .pa-filters select { padding-right:32px; }
6252
6253  .filters-scroll {
6254    flex:1;
6255    overflow:auto;
6256    padding:0 0 120px; /* ruang di atas bar actions */
6257    margin:0;
6258    -webkit-overflow-scrolling:touch;
6259  }
6260
6261  .pa-filters h2 { font-size:19px; margin-bottom:10px; }
6262
6263  .pa-filters .close-drawer {
6264    display: block;
6265    position:absolute;
6266    right:14px; top:12px;
6267    background:linear-gradient(135deg,#dc2626 0%,#b91c1c 100%);
6268    color:#fff;
6269    padding:7px 14px;
6270    border:none;
6271    border-radius:10px;
6272    font-size:12px;
6273    font-weight:600;
6274    cursor:pointer;
6275    box-shadow:0 3px 10px -2px rgba(220,38,38,.55);
6276    transition:transform .25s, box-shadow .3s;
6277    z-index: 99;
6278  }
6279  .pa-filters .close-drawer:hover { transform:translateY(-2px); box-shadow:0 6px 18px -4px rgba(220,38,38,.6); }
6280
6281  .spec-attr { border-radius:18px; }
6282  .spec-attr-options label { font-size:11px; padding:5px 9px; }
6283
6284  /* Actions fixed di viewport bawah */
6285  .f-actions {
6286    position:fixed;
6287    left:0; right:0; bottom:0;
6288    margin:0;
6289    border-top:1px solid #d6dee8;
6290    padding:14px 16px calc(16px + env(safe-area-inset-bottom));
6291    background:#ffffffea;
6292    backdrop-filter:blur(6px);
6293    box-shadow:0 -4px 22px -6px rgba(15,23,42,.25);
6294    justify-content:space-between;
6295    z-index:10001; /* di atas pa-filters (10000) dan bottom-nav (9998) */
6296  }
6297
6298  /* Sembunyikan bottom-nav saat filter drawer terbuka */
6299  body.filter-open .bottom-nav {
6300    display: none !important;
6301  }
6302  .f-actions button {
6303    flex:1 1 auto;
6304    justify-content:center;
6305    padding:14px 14px;
6306    font-size:15px;
6307    border-radius:14px;
6308  }
6309  .f-actions .reset {
6310    order:-1;
6311    font-size:12px;
6312    padding:2px 8px;
6313  }
6314}
6315
6316/* Scrollbar (outer panel desktop only kept for compatibility) */
6317.pa-filters::-webkit-scrollbar { width:0; } /* disembunyikan karena inner yang scroll */
6318
6319/* Utilities */
6320.hidden { display:none !important; }
6321.gradient-text {
6322  background:linear-gradient(90deg,#1d6ce0,#155ab8,#1d6ce0);
6323  -webkit-background-clip:text;
6324  color:transparent;
6325  animation:titleSheen 10s linear infinite;
6326}
6327.glow-focus:focus {
6328  outline:none !important;
6329  box-shadow:0 0 0 3px rgba(29,108,224,.4) !important;
6330  border-color:#1d6ce0 !important;
6331}
6332
6333/* Simple fade (opsional) */
6334@keyframes fadeIn {
6335  from { opacity:0; transform:translateY(8px); }
6336  to { opacity:1; transform:translateY(0); }
6337}
6338</style>
6339  <style data-inlined="mega-menu-tiles.css">
6340/* Tile Mega Menu — Modern E-commerce Enhanced (2025) */
6341
6342/* Design Tokens */
6343:root {
6344  --mm2-bg: #ffffff;
6345  --mm2-surface: #ffffff;
6346  --mm2-surface-elevated: rgba(255,255,255,0.98);
6347  --mm2-muted: #f8fafc;
6348  --mm2-border: #e2e8f0;
6349  --mm2-border-strong: #cbd5e1;
6350  --mm2-accent: #6366f1;
6351  --mm2-accent-hover: #4f46e5;
6352  --mm2-accent-10: rgba(99,102,241,.10);
6353  --mm2-accent-20: rgba(99,102,241,.20);
6354  --mm2-fg: #0f172a;
6355  --mm2-fg-muted: #64748b;
6356  --mm2-fg-light: #94a3b8;
6357
6358  /* Enhanced Shadows - Multi-layer elevation */
6359  --mm2-shadow-sm: 0 2px 8px rgba(15,23,42,.06), 0 1px 2px rgba(15,23,42,.04);
6360  --mm2-shadow-md: 0 8px 24px rgba(15,23,42,.10), 0 2px 8px rgba(15,23,42,.06);
6361  --mm2-shadow-lg: 0 20px 60px rgba(15,23,42,.14), 0 8px 24px rgba(15,23,42,.08);
6362  --mm2-shadow-xl: 0 32px 80px rgba(15,23,42,.18), 0 12px 32px rgba(15,23,42,.10);
6363  --mm2-shadow-hover: 0 16px 40px rgba(15,23,42,.16), 0 6px 16px rgba(15,23,42,.08);
6364
6365  /* Border Radius */
6366  --mm2-radius-xl: 20px;
6367  --mm2-radius-lg: 16px;
6368  --mm2-radius-md: 12px;
6369  --mm2-radius-sm: 10px;
6370
6371  --mm2-z: 60;
6372
6373  /* Animation Timing */
6374  --mm2-transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
6375  --mm2-transition-base: 250ms cubic-bezier(0.4, 0, 0.2, 1);
6376  --mm2-transition-slow: 350ms cubic-bezier(0.4, 0, 0.2, 1);
6377}
6378
6379.navbar { position: relative; z-index: var(--mm2-z); }
6380
6381/* Trigger Button - Enhanced */
6382.mm2-trigger {
6383  display: inline-flex; align-items: center; gap: 8px;
6384  padding: 10px 14px; border-radius: var(--mm2-radius-md);
6385  border: 1.5px solid var(--mm2-border);
6386  background: var(--mm2-surface); color: var(--mm2-fg);
6387  cursor: pointer; user-select: none;
6388  transition: all var(--mm2-transition-fast);
6389  text-decoration: none;
6390  font-weight: 600;
6391  box-shadow: var(--mm2-shadow-sm);
6392}
6393.mm2-trigger:hover { 
6394  border-color: var(--mm2-accent); 
6395  background: var(--mm2-muted);
6396  box-shadow: var(--mm2-shadow-md);
6397  transform: translateY(-1px);
6398}
6399.mm2-trigger:active { transform: translateY(0); }
6400.mm2-trigger svg {max-width: unset !important; transition: transform var(--mm2-transition-fast); }
6401.mm2-trigger:hover svg { transform: scale(1.05); }
6402
6403/* Backdrop - Enhanced Glassmorphism */
6404.mm2-backdrop {
6405  position: fixed; 
6406  inset: 0;
6407  top: 0;
6408  left: 0;
6409  right: 0;
6410  bottom: 0;
6411  background: rgba(15,23,42,.40);
6412  backdrop-filter: blur(8px) saturate(1.2);
6413  -webkit-backdrop-filter: blur(8px) saturate(1.2);
6414  opacity: 0; 
6415  pointer-events: none;
6416  z-index: 499;
6417  transition: opacity var(--mm2-transition-base);
6418}
6419.mm2-open .mm2-backdrop { opacity: 1; pointer-events: auto; }
6420
6421/* Panel - Glassmorphism Enhanced */
6422.mm2-panel {
6423  position: fixed;
6424  left: 50%;
6425  top: 120px;
6426  transform: translateX(-50%) translateY(-16px);
6427  width: min(1200px, 95vw);
6428  background: var(--mm2-surface-elevated);
6429  backdrop-filter: blur(20px) saturate(1.8);
6430  -webkit-backdrop-filter: blur(20px) saturate(1.8);
6431  border: 1.5px solid var(--mm2-border-strong);
6432  border-radius: var(--mm2-radius-xl);
6433  box-shadow: var(--mm2-shadow-xl);
6434  display: none;
6435  overflow: hidden;
6436  opacity: 0;
6437  z-index: 500;
6438  transition: transform var(--mm2-transition-slow), opacity var(--mm2-transition-base);
6439}
6440.mm2-open .mm2-panel { 
6441  display: block; 
6442  opacity: 1; 
6443  transform: translateX(-50%) translateY(0);
6444}
6445
6446/* View switcher */
6447.mm2-view { display: none; }
6448.mm2-open .mm2-view.is-active { display: block; }
6449
6450/* Top grid (level-1 tiles) - Enhanced */
6451.mm2-top {
6452  padding: 24px;
6453  max-height: 75vh; overflow: auto;
6454}
6455.mm2-grid {
6456  display: grid;
6457  grid-template-columns: repeat(4, minmax(0,1fr));
6458  gap: 14px;
6459}
6460@media (max-width: 1200px) { .mm2-grid { grid-template-columns: repeat(3, minmax(0,1fr)); } }
6461@media (max-width: 960px)  { .mm2-grid { grid-template-columns: repeat(2, minmax(0,1fr)); gap: 12px; } }
6462
6463/* Stagger animation for cards */
6464.mm2-open .mm2-card {
6465  animation: slideInUp 0.3s cubic-bezier(0.4, 0, 0.2, 1) backwards;
6466}
6467.mm2-open .mm2-card:nth-child(1)  { animation-delay: 0.02s; }
6468.mm2-open .mm2-card:nth-child(2)  { animation-delay: 0.04s; }
6469.mm2-open .mm2-card:nth-child(3)  { animation-delay: 0.06s; }
6470.mm2-open .mm2-card:nth-child(4)  { animation-delay: 0.08s; }
6471.mm2-open .mm2-card:nth-child(5)  { animation-delay: 0.10s; }
6472.mm2-open .mm2-card:nth-child(6)  { animation-delay: 0.12s; }
6473.mm2-open .mm2-card:nth-child(7)  { animation-delay: 0.14s; }
6474.mm2-open .mm2-card:nth-child(8)  { animation-delay: 0.16s; }
6475.mm2-open .mm2-card:nth-child(9)  { animation-delay: 0.18s; }
6476.mm2-open .mm2-card:nth-child(10) { animation-delay: 0.20s; }
6477.mm2-open .mm2-card:nth-child(11) { animation-delay: 0.22s; }
6478.mm2-open .mm2-card:nth-child(12) { animation-delay: 0.24s; }
6479.mm2-open .mm2-card:nth-child(n+13) { animation-delay: 0.26s; }
6480
6481@keyframes slideInUp {
6482  from {
6483    opacity: 0;
6484    transform: translateY(12px);
6485  }
6486  to {
6487    opacity: 1;
6488    transform: translateY(0);
6489  }
6490}
6491
6492.mm2-card {
6493  position: relative;
6494  display: flex; gap: 14px; align-items: center;
6495  padding: 16px; border-radius: var(--mm2-radius-md);
6496  background: var(--mm2-surface);
6497  border: 1.5px solid var(--mm2-border);
6498  text-decoration: none; color: var(--mm2-fg);
6499  transition: all var(--mm2-transition-fast);
6500  min-height: 72px;
6501  box-shadow: var(--mm2-shadow-sm);
6502}
6503.mm2-card:hover { 
6504  border-color: var(--mm2-accent);
6505  background: linear-gradient(135deg, rgba(255,255,255,1), rgba(248,250,252,1));
6506  box-shadow: var(--mm2-shadow-hover);
6507  transform: translateY(-3px);
6508}
6509.mm2-card:active { transform: translateY(-1px); }
6510
6511/* Icon with category-specific gradients */
6512.mm2-card .mm2-ico {
6513  width: 48px; height: 48px; border-radius: 12px;
6514  display: inline-flex; align-items: center; justify-content: center;
6515  background: linear-gradient(135deg, #e0e7ff, #fff);
6516  border: 1.5px solid #c7d2fe;
6517  color: #4f46e5;
6518  flex-shrink: 0;
6519  transition: all var(--mm2-transition-fast);
6520  box-shadow: 0 2px 8px rgba(99,102,241,0.1);
6521}
6522.mm2-card:hover .mm2-ico {
6523  transform: scale(1.08) rotate(2deg);
6524  box-shadow: 0 4px 16px rgba(99,102,241,0.2);
6525}
6526.mm2-card .mm2-ico svg { transition: transform var(--mm2-transition-fast); }
6527.mm2-card:hover .mm2-ico svg { transform: scale(1.1); }
6528
6529.mm2-card .mm2-title { 
6530  font-weight: 700; 
6531  font-size: 15px;
6532  letter-spacing: -0.01em;
6533  line-height: 1.3;
6534}
6535.mm2-card .mm2-sub { 
6536  color: var(--mm2-fg-muted); 
6537  font-size: 12.5px; 
6538  margin-top: 3px;
6539  display: flex;
6540  align-items: center;
6541  gap: 4px;
6542  white-space: nowrap;
6543  overflow: hidden;
6544  text-overflow: ellipsis;
6545}
6546.mm2-card .mm2-count {
6547  display: inline-flex;
6548  padding: 2px 8px;
6549  border-radius: 6px;
6550  background: var(--mm2-accent-10);
6551  color: var(--mm2-accent);
6552  font-weight: 600;
6553  font-size: 11px;
6554  margin-left: 4px;
6555}
6556
6557/* Detail view (level-2 + level-3) - Enhanced */
6558.mm2-detail {
6559  position: relative;
6560  padding: 16px 24px 24px;
6561  max-height: 70vh; overflow: auto;
6562}
6563.mm2-detail-header {
6564  display: flex; align-items: center; gap: 14px;
6565  padding: 10px 0 16px; border-bottom: 2px solid var(--mm2-muted); margin-bottom: 16px;
6566}
6567.mm2-back {
6568  display: inline-flex; align-items: center; justify-content: center;
6569  width: 40px; height: 40px; border-radius: 12px;
6570  border: 1.5px solid var(--mm2-border); background: #fff; color: var(--mm2-fg);
6571  cursor: pointer;
6572  transition: all var(--mm2-transition-fast);
6573  box-shadow: var(--mm2-shadow-sm);
6574}
6575.mm2-back:hover {
6576  border-color: var(--mm2-accent);
6577  background: var(--mm2-muted);
6578  transform: translateX(-3px);
6579  box-shadow: var(--mm2-shadow-md);
6580}
6581.mm2-detail-title { 
6582  font-weight: 800; 
6583  font-size: 18px;
6584  letter-spacing: -0.02em;
6585}
6586
6587/* Two-pane layout (desktop) - Enhanced */
6588.mm2-detail-layout {
6589  display: grid;
6590  grid-template-columns: 280px 1fr;
6591  gap: 20px;
6592}
6593
6594/* Left: subnav (level-2 list) - Enhanced */
6595.mm2-subnav {
6596  position: sticky; top: 8px;
6597  align-self: start;
6598  max-height: calc(70vh - 56px);
6599  overflow: auto;
6600  padding-right: 6px;
6601}
6602.mm2-subnav::-webkit-scrollbar { width: 8px; height: 8px; }
6603.mm2-subnav::-webkit-scrollbar-track { background: transparent; }
6604.mm2-subnav::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 8px; }
6605.mm2-subnav::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
6606
6607.mm2-subitem {
6608  display: flex; align-items: center; justify-content: flex-start; gap: 10px;
6609  width: 100%;
6610  padding: 12px 14px;
6611  border-radius: 12px;
6612  border: 1.5px solid transparent;
6613  background: transparent;
6614  color: var(--mm2-fg);
6615  cursor: pointer;
6616  text-align: left;
6617  transition: all var(--mm2-transition-fast);
6618  margin-bottom: 6px;
6619  font-weight: 600;
6620  font-size: 14px;
6621  position: relative;
6622}
6623.mm2-subitem::before {
6624  content: '';
6625  position: absolute;
6626  left: 0;
6627  top: 50%;
6628  transform: translateY(-50%);
6629  width: 3px;
6630  height: 0;
6631  background: var(--mm2-accent);
6632  border-radius: 0 2px 2px 0;
6633  transition: height var(--mm2-transition-fast);
6634}
6635.mm2-subitem:hover { 
6636  background: var(--mm2-muted);
6637  color: var(--mm2-accent-hover);
6638}
6639.mm2-subitem.active {
6640  background: linear-gradient(135deg, rgba(99,102,241,0.08), rgba(99,102,241,0.04));
6641  border-color: var(--mm2-accent-20);
6642  color: var(--mm2-accent-hover);
6643  box-shadow: inset 0 0 0 1px rgba(99,102,241,0.1);
6644}
6645.mm2-subitem.active::before {
6646  height: 24px;
6647}
6648
6649/* Right: subcontent (level-3 grid) - Enhanced */
6650.mm2-subcontent { min-height: 240px; }
6651.mm2-links {
6652  display: grid; gap: 12px 14px;
6653  grid-template-columns: repeat(3, minmax(0,1fr));
6654}
6655@media (max-width: 1200px) { .mm2-links { grid-template-columns: repeat(2, minmax(0,1fr)); } }
6656
6657.mm2-link {
6658  display: flex; align-items: center; gap: 12px;
6659  padding: 14px; border: 1.5px solid var(--mm2-border); border-radius: var(--mm2-radius-md);
6660  background: var(--mm2-surface); color: var(--mm2-fg); text-decoration: none;
6661  min-height: 52px;
6662  font-weight: 500;
6663  font-size: 14px;
6664  transition: all var(--mm2-transition-fast);
6665  box-shadow: var(--mm2-shadow-sm);
6666  position: relative;
6667  justify-content: space-between;
6668}
6669.mm2-link:hover { 
6670  background: var(--mm2-muted); 
6671  border-color: var(--mm2-accent);
6672  transform: translateX(4px);
6673  box-shadow: var(--mm2-shadow-md);
6674}
6675.mm2-link-text {
6676  flex: 1;
6677}
6678
6679/* Product Thumbnails */
6680.mm2-thumbs {
6681  display: flex;
6682  gap: 6px;
6683  align-items: center;
6684  margin-left: auto;
6685  flex-shrink: 0;
6686}
6687.mm2-thumb {
6688  width: 36px;
6689  height: 36px;
6690  border-radius: 8px;
6691  object-fit: cover;
6692  border: 1.5px solid var(--mm2-border);
6693  box-shadow: 0 2px 6px rgba(0,0,0,0.08);
6694  transition: all var(--mm2-transition-fast);
6695  background: var(--mm2-muted);
6696}
6697.mm2-link:hover .mm2-thumb {
6698  border-color: var(--mm2-accent);
6699  box-shadow: 0 4px 10px rgba(99,102,241,0.15);
6700  transform: scale(1.08);
6701}
6702
6703/* Thumbnail Skeleton Loader */
6704.mm2-thumb-skeleton {
6705  width: 36px;
6706  height: 36px;
6707  border-radius: 8px;
6708  background: linear-gradient(90deg, #f1f5f9 25%, #e2e8f0 50%, #f1f5f9 75%);
6709  background-size: 200% 100%;
6710  animation: shimmer 1.5s infinite;
6711  border: 1.5px solid var(--mm2-border);
6712}
6713
6714.mm2-badge {
6715  font-size: 11px; padding: 3px 10px; margin-left: auto;
6716  border-radius: 12px; background: var(--mm2-accent-10); color: var(--mm2-accent);
6717  font-weight: 700;
6718  border: 1px solid var(--mm2-accent-20);
6719}
6720
6721/* "See all" highlight card - Enhanced */
6722.mm2-seeall {
6723  border: 2px dashed var(--mm2-accent);
6724  background: linear-gradient(135deg, rgba(99,102,241,.08), rgba(99,102,241,.02));
6725  font-weight: 700;
6726}
6727.mm2-seeall:hover {
6728  background: linear-gradient(135deg, rgba(99,102,241,.14), rgba(99,102,241,.06));
6729  border-style: solid;
6730  transform: translateX(6px) scale(1.02);
6731}
6732
6733/* Mobile panel styles - Enhanced with drag handle & better touch targets */
6734@media (max-width: 960px) {
6735  .mm2-panel {
6736    position: fixed;
6737    left: 0;
6738    right: 0;
6739    bottom: 0;
6740    top: auto;
6741    width: 100%;
6742    max-height: 88vh;
6743    transform: translateX(0) translateY(100%);
6744    border-radius: var(--mm2-radius-xl) var(--mm2-radius-xl) 0 0;
6745    border-bottom: none;
6746  }
6747  .mm2-open .mm2-panel {
6748    transform: translateX(0) translateY(0); z-index: 9999;
6749  }
6750
6751  /* Enhanced backdrop for mobile */
6752  .mm2-backdrop {
6753    backdrop-filter: blur(6px) saturate(1.2);
6754    -webkit-backdrop-filter: blur(6px) saturate(1.2);
6755  }
6756
6757  /* Mobile close button - Soft glassmorphism */
6758  .mm2-mobile-close {
6759    position: absolute;
6760    top: 4px;
6761    right: 8px;
6762    width: 28px;
6763    height: 28px;
6764    border-radius: 50%;
6765    border: 1px solid rgba(226, 232, 240, 0.6);
6766    background: rgba(255, 255, 255, 0.85);
6767    backdrop-filter: blur(12px) saturate(1.8);
6768    -webkit-backdrop-filter: blur(12px) saturate(1.8);
6769    color: var(--mm2-fg-muted);
6770    display: inline-flex;
6771    align-items: center;
6772    justify-content: center;
6773    cursor: pointer;
6774    transition: all var(--mm2-transition-fast);
6775    box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08);
6776    z-index: 20;
6777  }
6778  .mm2-mobile-close:hover {
6779    background: rgba(254, 242, 242, 0.95);
6780    border-color: rgba(252, 165, 165, 0.8);
6781    color: #dc2626;
6782    box-shadow: 0 4px 12px rgba(220, 38, 38, 0.15);
6783  }
6784  .mm2-mobile-close:active {
6785    transform: scale(0.92);
6786  }
6787
6788  /* Drag handle indicator */
6789  .mm2-top::before,
6790  .mm2-detail::before {
6791    content: '';
6792    position: sticky;
6793    top: 0;
6794    left: 50%;
6795    transform: translateX(-50%);
6796    width: 40px;
6797    height: 4px;
6798    background: var(--mm2-border-strong);
6799    border-radius: 999px;
6800    margin: 12px auto 20px;
6801    display: block;
6802    z-index: 10;
6803    transition: background var(--mm2-transition-fast);
6804  }
6805  .mm2-panel:active .mm2-top::before,
6806  .mm2-panel:active .mm2-detail::before {
6807    background: #94a3b8;
6808  }
6809
6810  .mm2-top {
6811    padding: 10px;  
6812  }
6813
6814  .mm2-top, .mm2-detail { 
6815    max-height: min(82vh, 720px);
6816    padding-top: 8px;
6817  }
6818
6819  /* Two-pane collapses: subnav becomes GRID (no horizontal scroll) */
6820  .mm2-detail-layout { display: block; }
6821  .mm2-subnav {
6822    position: static; max-height: none; overflow: visible;
6823    display: grid; gap: 10px; padding: 10px 4px 16px; margin-bottom: 16px;
6824    grid-template-columns: repeat(2, minmax(0,1fr));
6825    border-bottom: 2px solid var(--mm2-muted);
6826  }
6827  .mm2-subitem {
6828    margin: 0; width: 100%; text-align: center;
6829    border-radius: 14px;
6830    background: #fff; border: 1.5px solid var(--mm2-border);
6831    white-space: normal; line-height: 1.3; min-height: 48px;
6832    justify-content: center;
6833    padding: 12px 10px;
6834    font-size: 13.5px;
6835    box-shadow: var(--mm2-shadow-sm);
6836  }
6837  .mm2-subitem::before { display: none; }
6838  .mm2-subitem:hover {
6839    transform: scale(1.02);
6840  }
6841  .mm2-subitem.active {
6842    background: linear-gradient(135deg, var(--mm2-accent), var(--mm2-accent-hover));
6843    border-color: var(--mm2-accent-hover);
6844    color: white;
6845    box-shadow: 0 4px 12px rgba(99,102,241,0.3);
6846    font-weight: 700;
6847  }
6848
6849  /* Level-3 grid tetap 2 kolom dengan spacing lebih baik */
6850  .mm2-links { 
6851    grid-template-columns: repeat(2, minmax(0,1fr));
6852    gap: 10px;
6853  }
6854
6855  /* Mobile: hide thumbnails untuk save space */
6856  .mm2-thumbs {
6857    display: none;
6858  }
6859
6860  /* Larger touch targets for mobile */
6861  .mm2-card {
6862    min-height: 80px;
6863    padding: 18px 14px;
6864  }
6865  .mm2-card .mm2-ico {
6866    width: 50px;
6867    height: 50px;
6868  }
6869  .mm2-card .mm2-title {
6870    font-size: 14px;
6871  }
6872  .mm2-card .mm2-sub {
6873    font-size: 11.5px;
6874  }
6875  .mm2-link {
6876    min-height: 56px;
6877    padding: 16px;
6878  }
6879  .mm2-back {
6880    width: 44px;
6881    height: 44px;
6882  }
6883
6884  /* Grid spacing */
6885  .mm2-grid {
6886    gap: 10px;
6887  }
6888}
6889</style>
6890  <style data-inlined="product-compare.css">
6891/* Product Compare Feature Styles */
6892
6893/* Compare Button in Product Detail - Modern Chip Style */
6894.pd-compare-section {
6895  margin: 0.5rem 0;
6896  display: flex;
6897  align-items: center;
6898  gap: 0.5rem;
6899}
6900
6901.pd-compare-btn {
6902  display: inline-flex;
6903  align-items: center;
6904  gap: 0.4rem;
6905  padding: 0.5rem 0.85rem;
6906  border: 1px solid #cbd5e1;
6907  border-radius: 20px;
6908  background: #fff;
6909  color: #64748b;
6910  font-weight: 600;
6911  font-size: 0.8rem;
6912  cursor: pointer;
6913  transition: all 0.2s ease;
6914  position: relative;
6915  white-space: nowrap;
6916}
6917
6918.pd-compare-btn:hover {
6919  border-color: #3b82f6;
6920  color: #3b82f6;
6921  background: #f0f9ff;
6922  transform: translateY(-1px);
6923  box-shadow: 0 2px 6px rgba(59, 130, 246, 0.15);
6924}
6925
6926.pd-compare-btn.added {
6927  border-color: #16a34a;
6928  color: #16a34a;
6929  background: #f0fdf4;
6930}
6931
6932.pd-compare-btn .compare-remove {
6933  display: none;
6934}
6935
6936.pd-compare-btn.added .compare-text {
6937  display: none;
6938}
6939
6940.pd-compare-btn.added .compare-remove {
6941  display: flex !important;
6942  align-items: center;
6943  gap: 0.25rem;
6944  font-weight: 600;
6945  font-size: 0.9rem;
6946  color: #dc2626 !important;
6947  opacity: 1 !important;
6948  visibility: visible !important;
6949  z-index: 1;
6950  position: relative;
6951}
6952
6953.pd-compare-btn.added .compare-remove[hidden] {
6954  display: flex !important;
6955}
6956
6957/* Force visibility for added state */
6958.pd-compare-btn.added .compare-remove {
6959  display: flex !important;
6960}
6961
6962[hidden].compare-remove {
6963  display: none !important;
6964}
6965
6966.pd-compare-btn.added [hidden].compare-remove {
6967  display: flex !important;
6968}
6969
6970/* Ensure the added state text is always visible */
6971.pd-compare-btn.added {
6972  border-color: #dc2626 !important;
6973  color: #dc2626 !important;
6974  background: #fef2f2 !important;
6975}
6976
6977.pd-compare-btn.added .compare-remove svg {
6978  color: #dc2626 !important;
6979}
6980
6981.pd-compare-btn.added:hover {
6982  border-color: #15803d;
6983  color: #15803d;
6984  background: #dcfce7;
6985  transform: translateY(-1px);
6986  box-shadow: 0 2px 6px rgba(22, 163, 74, 0.2);
6987}
6988
6989.pd-compare-btn .compare-icon {
6990  width: 16px;
6991  height: 16px;
6992  flex-shrink: 0;
6993}
6994
6995.pd-compare-info {
6996  display: inline-flex;
6997  align-items: center;
6998}
6999
7000.compare-note {
7001  color: #94a3b8;
7002  font-size: 0.7rem;
7003  font-weight: 500;
7004  transition: color 0.2s ease;
7005}
7006
7007.pd-compare-btn.added + .pd-compare-info .compare-note {
7008  color: #16a34a;
7009  font-weight: 600;
7010}
7011
7012/* Compare Badge in Navigation */
7013.compare-indicator {
7014  position: relative;
7015}
7016
7017.compare-indicator .badge {
7018  position: absolute;
7019  top: -8px;
7020  right: -8px;
7021  min-width: 18px;
7022  height: 18px;
7023  padding: 0 5px;
7024  background: #ef4444;
7025  color: white;
7026  border-radius: 9px;
7027  font-size: 11px;
7028
7029
7030
7031/* Floating Compare Badge (Sticky) */
7032.compare-floating-badge {
7033  position: fixed;
7034  right: 16px;
7035  bottom: calc(20px + 60px); /* Above scroll-to-top button */
7036  width: auto;
7037  min-width: 56px;
7038  height: 56px;
7039  padding: 0 16px;
7040  border-radius: 28px;
7041  background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
7042  color: #fff;
7043  box-shadow: 0 8px 24px -8px rgba(59, 130, 246, 0.5);
7044  cursor: pointer;
7045  z-index: 1104;
7046  opacity: 0;
7047  transform: translateY(8px) scale(0.9);
7048  transition: opacity 0.3s ease, transform 0.3s ease, box-shadow 0.2s ease;
7049  display: flex;
7050  align-items: center;
7051  justify-content: center;
7052  gap: 8px;
7053  font-weight: 600;
7054  font-size: 0.85rem;
7055  pointer-events: none;
7056}
7057
7058.compare-floating-badge.visible {
7059  opacity: 1;
7060  transform: translateY(0) scale(1);
7061  pointer-events: auto;
7062}
7063
7064.compare-floating-badge:hover {
7065  box-shadow: 0 12px 32px -10px rgba(59, 130, 246, 0.6);
7066  transform: translateY(-2px) scale(1.02);
7067}
7068
7069.compare-floating-badge:active {
7070  transform: translateY(0) scale(0.98);
7071}
7072
7073.compare-floating-badge .cfb-icon {
7074  width: 20px;
7075  height: 20px;
7076  flex-shrink: 0;
7077}
7078
7079.compare-floating-badge .cfb-count {
7080  font-size: 0.9rem;
7081  font-weight: 700;
7082  min-width: 20px;
7083  text-align: center;
7084  background: rgba(255, 255, 255, 0.25);
7085  padding: 2px 8px;
7086  border-radius: 12px;
7087  line-height: 1;
7088  display: flex;
7089  align-items: center;
7090  justify-content: center;
7091  height: 24px;
7092}
7093
7094.compare-floating-badge .cfb-text {
7095  display: none;
7096}
7097
7098@media (min-width: 641px) {
7099  .compare-floating-badge .cfb-text {
7100    display: block;
7101    white-space: nowrap;
7102  }
7103}
7104
7105@media (max-width: 640px) {
7106  .compare-floating-badge {
7107    right: 14px;
7108    bottom: calc(var(--scroll-top-bottom, calc(68px + 18px + env(safe-area-inset-bottom))) + 60px);
7109    min-width: 48px;
7110    height: 48px;
7111    padding: 0 12px;
7112    border-radius: 24px;
7113  }
7114
7115  .compare-floating-badge .cfb-icon {
7116    width: 18px;
7117    height: 18px;
7118  }
7119
7120  .compare-floating-badge .cfb-count {
7121    font-size: 0.8rem;
7122    padding: 2px 6px;
7123    height: 20px;
7124  }
7125} font-weight: 600;
7126  display: flex;
7127  align-items: center;
7128  justify-content: center;
7129  transform: scale(0);
7130  transition: transform 0.2s ease;
7131}
7132
7133.compare-indicator .badge.active {
7134  transform: scale(1);
7135}
7136
7137/* Compare Sidebar */
7138.compare-sidebar {
7139  position: fixed;
7140  top: 0;
7141  right: -400px;
7142  width: 400px;
7143  height: 100vh;
7144  background: #ffffff;
7145  box-shadow: -4px 0 20px rgba(0, 0, 0, 0.1);
7146  z-index: 9999;
7147  display: flex;
7148  flex-direction: column;
7149  transition: right 0.3s ease;
7150}
7151
7152.compare-sidebar.open {
7153  right: 0;
7154}
7155
7156.compare-overlay {
7157  position: fixed;
7158  top: 0;
7159  left: 0;
7160  width: 100%;
7161  height: 100%;
7162  background: rgba(0, 0, 0, 0.5);
7163  z-index: 9998;
7164  opacity: 0;
7165  visibility: hidden;
7166  transition: all 0.3s ease;
7167}
7168
7169.compare-overlay.active {
7170  opacity: 1;
7171  visibility: visible;
7172}
7173
7174.compare-header {
7175  display: flex;
7176  align-items: center;
7177  justify-content: space-between;
7178  padding: 1.25rem;
7179  border-bottom: 1px solid #e2e8f0;
7180  background: #f8fafc;
7181}
7182
7183.compare-title {
7184  display: flex;
7185  align-items: center;
7186  gap: 0.5rem;
7187  font-size: 1rem;
7188  font-weight: 600;
7189  color: #1e293b;
7190  margin: 0;
7191}
7192
7193.compare-count {
7194  color: #64748b;
7195  font-weight: 400;
7196}
7197
7198.compare-close {
7199  display: flex;
7200  align-items: center;
7201  justify-content: center;
7202  width: 32px;
7203  height: 32px;
7204  border: none;
7205  border-radius: 6px;
7206  background: transparent;
7207  color: #64748b;
7208  cursor: pointer;
7209  transition: all 0.2s ease;
7210}
7211
7212.compare-close:hover {
7213  background: #e2e8f0;
7214  color: #374151;
7215}
7216
7217.compare-content {
7218  flex: 1;
7219  overflow-y: auto;
7220  padding: 1rem;
7221}
7222
7223.compare-empty {
7224  display: flex;
7225  flex-direction: column;
7226  align-items: center;
7227  justify-content: center;
7228  height: 100%;
7229  text-align: center;
7230  padding: 2rem 1rem;
7231}
7232
7233.compare-empty h5 {
7234  margin: 1rem 0 0.5rem;
7235  color: #374151;
7236}
7237
7238.compare-empty p {
7239  color: #64748b;
7240  font-size: 0.875rem;
7241  line-height: 1.5;
7242}
7243
7244.compare-list {
7245  display: flex;
7246  flex-direction: column;
7247  gap: 0.75rem;
7248}
7249
7250.compare-item {
7251  display: flex;
7252  align-items: center;
7253  gap: 0.75rem;
7254  padding: 0.75rem;
7255  border: 1px solid #e2e8f0;
7256  border-radius: 8px;
7257  background: #ffffff;
7258  transition: all 0.2s ease;
7259}
7260
7261.compare-item:hover {
7262  border-color: #cbd5e1;
7263  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
7264}
7265
7266.compare-item-image {
7267  flex-shrink: 0;
7268  width: 48px;
7269  height: 48px;
7270  border-radius: 6px;
7271  overflow: hidden;
7272  border: 1px solid #e2e8f0;
7273}
7274
7275.compare-item-image img {
7276  width: 100%;
7277  height: 100%;
7278  object-fit: cover;
7279}
7280
7281.compare-item-info {
7282  flex: 1;
7283  min-width: 0;
7284}
7285
7286.compare-item-name {
7287  font-weight: 500;
7288  font-size: 0.875rem;
7289  color: #1e293b;
7290  margin-bottom: 0.25rem;
7291  line-height: 1.3;
7292  display: -webkit-box;
7293  -webkit-line-clamp: 2;
7294  -webkit-box-orient: vertical;
7295  overflow: hidden;
7296}
7297
7298.compare-item-price {
7299  font-weight: 600;
7300  font-size: 0.8rem;
7301  color: #059669;
7302}
7303
7304.compare-item-remove {
7305  flex-shrink: 0;
7306  display: flex;
7307  align-items: center;
7308  justify-content: center;
7309  width: 28px;
7310  height: 28px;
7311  border: none;
7312  border-radius: 4px;
7313  background: transparent;
7314  color: #dc2626;
7315  cursor: pointer;
7316  transition: all 0.2s ease;
7317}
7318
7319.compare-item-remove:hover {
7320  background: #fef2f2;
7321  color: #991b1b;
7322}
7323
7324.compare-actions {
7325  padding: 1rem;
7326  border-top: 1px solid #e2e8f0;
7327  display: flex;
7328  gap: 0.75rem;
7329}
7330
7331.compare-actions .btn {
7332  flex: 1;
7333  padding: 0.75rem 1rem;
7334  border-radius: 6px;
7335  font-weight: 600;
7336  font-size: 0.875rem;
7337  display: flex;
7338  align-items: center;
7339  justify-content: center;
7340  gap: 0.5rem;
7341  cursor: pointer;
7342  transition: all 0.2s ease;
7343}
7344
7345.btn-outline-danger {
7346  border: 1px solid #dc2626;
7347  background: transparent;
7348  color: #dc2626;
7349}
7350
7351.btn-outline-danger:hover {
7352  background: #dc2626;
7353  color: white;
7354}
7355
7356.btn-primary {
7357  border: 1px solid #3b82f6;
7358  background: #3b82f6;
7359  color: white;
7360}
7361
7362.btn-primary:hover {
7363  background: #2563eb;
7364  border-color: #2563eb;
7365}
7366
7367.btn-primary:disabled {
7368  opacity: 0.5;
7369  cursor: not-allowed;
7370}
7371
7372.btn-primary:disabled:hover {
7373  background: #3b82f6;
7374  border-color: #3b82f6;
7375}
7376
7377/* AI Comparison Modal */
7378.ai-compare-modal {
7379  position: fixed;
7380  top: 0;
7381  left: 0;
7382  width: 100%;
7383  height: 100%;
7384  z-index: 10000;
7385  display: none;
7386  align-items: center;
7387  justify-content: center;
7388}
7389
7390.ai-compare-modal.open {
7391  display: flex;
7392}
7393
7394.ai-compare-modal .modal-backdrop {
7395  position: absolute;
7396  top: 0;
7397  left: 0;
7398  width: 100%;
7399  height: 100%;
7400  background: rgba(0, 0, 0, 0.6);
7401  backdrop-filter: blur(4px);
7402}
7403
7404.modal-container {
7405  position: relative;
7406  width: 90%;
7407  max-width: 1000px;
7408  max-height: 90vh;
7409  background: #ffffff;
7410  border-radius: 12px;
7411  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
7412  display: flex;
7413  flex-direction: column;
7414  overflow: hidden;
7415}
7416
7417.modal-header {
7418  display: flex;
7419  align-items: center;
7420  justify-content: space-between;
7421  padding: 1.5rem 2rem;
7422  border-bottom: 1px solid #e2e8f0;
7423  background: #f8fafc;
7424}
7425
7426.modal-title {
7427  display: flex;
7428  align-items: center;
7429  gap: 0.75rem;
7430  font-size: 1.25rem;
7431  font-weight: 600;
7432  color: #1e293b;
7433  margin: 0;
7434}
7435
7436.modal-close {
7437  display: flex;
7438  align-items: center;
7439  justify-content: center;
7440  width: 36px;
7441  height: 36px;
7442  border: none;
7443  border-radius: 8px;
7444  background: transparent;
7445  color: #64748b;
7446  cursor: pointer;
7447  transition: all 0.2s ease;
7448}
7449
7450.modal-close:hover {
7451  background: #e2e8f0;
7452  color: #374151;
7453}
7454
7455.modal-content {
7456  flex: 1;
7457  overflow-y: auto;
7458  padding: 2rem;
7459}
7460
7461/* AI Loading State */
7462.ai-loading {
7463  display: flex;
7464  flex-direction: column;
7465  align-items: center;
7466  justify-content: center;
7467  padding: 3rem 2rem;
7468  text-align: center;
7469}
7470
7471.loading-animation {
7472  margin-bottom: 1rem;
7473}
7474
7475.typing-indicator {
7476  display: flex;
7477  align-items: center;
7478  justify-content: center;
7479  gap: 4px;
7480  margin-bottom: 1rem;
7481}
7482
7483.typing-indicator span {
7484  width: 8px;
7485  height: 8px;
7486  background: #3b82f6;
7487  border-radius: 50%;
7488  animation: typing 1.4s infinite ease-in-out;
7489}
7490
7491.typing-indicator span:nth-child(2) {
7492  animation-delay: 0.2s;
7493}
7494
7495.typing-indicator span:nth-child(3) {
7496  animation-delay: 0.4s;
7497}
7498
7499@keyframes typing {
7500  0%, 80%, 100% {
7501    opacity: 0.3;
7502    transform: scale(0.8);
7503  }
7504  40% {
7505    opacity: 1;
7506    transform: scale(1);
7507  }
7508}
7509
7510.ai-loading p {
7511  font-weight: 500;
7512  color: #1e293b;
7513  margin-bottom: 0.5rem;
7514}
7515
7516.ai-loading small {
7517  color: #64748b;
7518}
7519
7520/* AI Error State */
7521.ai-error {
7522  display: flex;
7523  flex-direction: column;
7524  align-items: center;
7525  justify-content: center;
7526  padding: 3rem 2rem;
7527  text-align: center;
7528}
7529
7530.ai-error h4 {
7531  margin: 1rem 0 0.5rem;
7532  color: #dc2626;
7533}
7534
7535.ai-error p {
7536  color: #64748b;
7537  margin-bottom: 1.5rem;
7538}
7539
7540/* AI Results */
7541.ai-results > div {
7542  margin-bottom: 2rem;
7543}
7544
7545.ai-summary-card,
7546.ai-comparison-table,
7547.ai-detailed-analysis,
7548.ai-recommendations,
7549.ai-tips {
7550  background: #f8fafc;
7551  border: 1px solid #e2e8f0;
7552  border-radius: 12px;
7553  padding: 1.5rem;
7554}
7555
7556.ai-results h4 {
7557  display: flex;
7558  align-items: center;
7559  gap: 0.5rem;
7560  font-size: 1.1rem;
7561  font-weight: 600;
7562  color: #1e293b;
7563  margin-bottom: 1rem;
7564}
7565
7566.summary-text {
7567  font-size: 1rem;
7568  line-height: 1.6;
7569  color: #374151;
7570  margin-bottom: 1rem;
7571}
7572
7573.winner-badge {
7574  padding: 1rem;
7575  background: linear-gradient(135deg, #10b981 0%, #059669 100%);
7576  color: white;
7577  border-radius: 8px;
7578}
7579
7580.winner-badge strong {
7581  display: block;
7582  margin-bottom: 0.25rem;
7583}
7584
7585.winner-badge small {
7586  opacity: 0.9;
7587  font-size: 0.875rem;
7588}
7589
7590/* Comparison Table */
7591.comparison-grid {
7592  display: grid;
7593  gap: 1rem;
7594  margin-top: 1rem;
7595}
7596
7597.comparison-category {
7598  background: white;
7599  border: 1px solid #e2e8f0;
7600  border-radius: 8px;
7601  overflow: hidden;
7602}
7603
7604.category-header {
7605  background: #f1f5f9;
7606  padding: 0.75rem 1rem;
7607  font-weight: 600;
7608  color: #1e293b;
7609  border-bottom: 1px solid #e2e8f0;
7610}
7611
7612.category-products {
7613  display: grid;
7614  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
7615  gap: 1px;
7616  background: #e2e8f0;
7617}
7618
7619.product-score {
7620  background: white;
7621  padding: 1rem;
7622  text-align: center;
7623}
7624
7625.score-value {
7626  display: inline-flex;
7627  align-items: center;
7628  gap: 0.25rem;
7629  margin-bottom: 0.5rem;
7630}
7631
7632.score-stars {
7633  color: #fbbf24;
7634}
7635
7636.score-note {
7637  font-size: 0.8rem;
7638  color: #64748b;
7639  line-height: 1.3;
7640}
7641
7642/* Product name header in comparison table */
7643.product-name-header {
7644  margin-bottom: 0.5rem;
7645}
7646
7647.product-name-header strong {
7648  font-size: .8rem; /* h5 size (18px) */
7649  font-weight: 600;
7650  color: #1e293b;
7651  line-height: 1.3;
7652  display: block;
7653  word-break: break-word;
7654}
7655
7656.product-price {
7657  font-size: 0.8rem;
7658  font-weight: 600;
7659  color: #059669;
7660}
7661
7662/* Detailed Analysis */
7663.products-analysis {
7664  display: grid;
7665  gap: 1rem;
7666  margin-top: 1rem;
7667}
7668
7669.product-analysis {
7670  background: white;
7671  border: 1px solid #e2e8f0;
7672  border-radius: 8px;
7673  padding: 1rem;
7674}
7675
7676.product-analysis h5 {
7677  color: #1e293b;
7678  margin-bottom: 0.75rem;
7679  font-weight: 600;
7680}
7681
7682.analysis-section {
7683  margin-bottom: 0.75rem;
7684}
7685
7686.analysis-section:last-child {
7687  margin-bottom: 0;
7688}
7689
7690.analysis-section h6 {
7691  font-size: 0.875rem;
7692  font-weight: 600;
7693  margin-bottom: 0.5rem;
7694}
7695
7696.analysis-section h6.strengths {
7697  color: #059669;
7698}
7699
7700.analysis-section h6.weaknesses {
7701  color: #dc2626;
7702}
7703
7704.analysis-section h6.best-for {
7705  color: #7c3aed;
7706}
7707
7708.analysis-section ul {
7709  list-style: none;
7710  padding: 0;
7711  margin: 0;
7712}
7713
7714.analysis-section li {
7715  display: flex;
7716  align-items: flex-start;
7717  gap: 0.5rem;
7718  margin-bottom: 0.25rem;
7719  font-size: 0.875rem;
7720  line-height: 1.4;
7721}
7722
7723.analysis-section li:before {
7724  content: "•";
7725  color: #64748b;
7726  font-weight: bold;
7727  margin-top: 0.1rem;
7728}
7729
7730.best-for-text {
7731  font-size: 0.875rem;
7732  color: #374151;
7733  font-style: italic;
7734}
7735
7736/* Recommendations */
7737.recommendations-grid {
7738  display: grid;
7739  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
7740  gap: 1rem;
7741  margin-top: 1rem;
7742}
7743
7744.recommendation-card {
7745  background: white;
7746  border: 1px solid #e2e8f0;
7747  border-radius: 8px;
7748  padding: 1rem;
7749  text-align: center;
7750}
7751
7752.recommendation-card h5 {
7753  font-size: 1rem;
7754  margin-bottom: 0.75rem;
7755  color: #1e293b;
7756}
7757
7758.recommendation-card p {
7759  font-size: 0.875rem;
7760  color: #374151;
7761  line-height: 1.5;
7762}
7763
7764/* Tips */
7765.tips-list {
7766  list-style: none;
7767  padding: 0;
7768  margin: 1rem 0 0 0;
7769}
7770
7771.tips-list li {
7772  display: flex;
7773  align-items: flex-start;
7774  gap: 0.75rem;
7775  margin-bottom: 0.75rem;
7776  font-size: 0.9rem;
7777  line-height: 1.5;
7778  color: #374151;
7779}
7780
7781.tips-list li:before {
7782  content: "💡";
7783  font-size: 1rem;
7784  margin-top: 0.1rem;
7785}
7786
7787/* Modal Footer */
7788.modal-footer {
7789  display: flex;
7790  justify-content: space-between;
7791  align-items: center;
7792  padding: 1.5rem 2rem;
7793  border-top: 1px solid #e2e8f0;
7794  background: #f8fafc;
7795}
7796
7797.btn-outline-secondary {
7798  border: 1px solid #64748b;
7799  background: transparent;
7800  color: #64748b;
7801  padding: 0.75rem 1.5rem;
7802  border-radius: 6px;
7803  font-weight: 600;
7804  display: flex;
7805  align-items: center;
7806  gap: 0.5rem;
7807  cursor: pointer;
7808  transition: all 0.2s ease;
7809}
7810
7811.btn-outline-secondary:hover {
7812  background: #64748b;
7813  color: white;
7814}
7815
7816/* Responsive for chip button */
7817@media (max-width: 880px) {
7818  .pd-compare-section {
7819    margin: 1rem 0;
7820    display: block;
7821  }
7822
7823  .pd-compare-btn {
7824    width: 100%;
7825    font-size: 0.85rem;
7826    padding: 0.65rem 1rem;
7827    border-radius: 10px;
7828    justify-content: center;
7829    min-height: 44px;
7830  }
7831
7832  .pd-compare-btn .compare-icon {
7833    width: 16px;
7834    height: 16px;
7835  }
7836
7837  .pd-compare-info {
7838    margin-top: 0.5rem;
7839    display: block;
7840    text-align: center;
7841  }
7842
7843  .compare-note {
7844    font-size: 0.7rem;
7845    display: block;
7846  }
7847}
7848
7849/* Responsive Design */
7850@media (max-width: 768px) {
7851  .compare-sidebar {
7852    width: 100%;
7853    right: -100%;
7854    /* Fix for mobile viewport height changes */
7855    height: 100vh;
7856    height: 100dvh; /* Dynamic viewport height for modern browsers */
7857  }
7858
7859  .compare-actions {
7860    /* Ensure actions are always visible */
7861    position: sticky;
7862    bottom: 0;
7863    background: white;
7864    border-top: 2px solid #e2e8f0;
7865    box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
7866    z-index: 10;
7867  }
7868
7869  .compare-actions .btn {
7870    /* Larger touch targets for mobile */
7871    min-height: 48px;
7872    font-size: 0.9rem;
7873    font-weight: 700;
7874  }
7875
7876  .modal-container {
7877    width: 95%;
7878    max-height: 95vh;
7879    max-height: 95dvh; /* Dynamic viewport height */
7880  }
7881
7882  .modal-header,
7883  .modal-content,
7884  .modal-footer {
7885    padding: 1rem;
7886  }
7887
7888  .recommendations-grid {
7889    grid-template-columns: 1fr;
7890  }
7891
7892  .category-products {
7893    grid-template-columns: 1fr;
7894  }
7895
7896  .modal-footer {
7897    flex-direction: column;
7898    gap: 0.75rem;
7899  }
7900
7901  .modal-footer .btn {
7902    width: 100%;
7903  }
7904
7905  /* Ensure compare button is visible on mobile */
7906  .pd-compare-section {
7907    display: block !important;
7908    visibility: visible !important;
7909    opacity: 1 !important;
7910    margin: 1rem 0;
7911    padding: 0;
7912    position: relative !important;
7913    z-index: 1 !important;
7914    max-width: 100%;
7915    overflow: hidden;
7916  }
7917
7918  .pd-compare-btn {
7919    display: flex !important;
7920    visibility: visible !important;
7921    opacity: 1 !important;
7922    width: 100%;
7923    padding: 0.75rem 1rem;
7924    font-size: 0.9rem;
7925    min-height: 48px; /* Minimum touch target for mobile */
7926    border: 2px solid #e2e8f0 !important;
7927    background: #fff !important;
7928    position: relative !important;
7929    z-index: 2 !important;
7930    box-sizing: border-box;
7931    white-space: normal !important; /* Allow text wrapping on mobile */
7932    flex-wrap: wrap;
7933    gap: 0.5rem;
7934  }
7935
7936  .pd-compare-btn .compare-text {
7937    display: inline !important;
7938    visibility: visible !important;
7939    font-weight: 600;
7940    color: #475569 !important;
7941  }
7942
7943  .pd-compare-btn .compare-remove {
7944    display: none !important; /* Hidden by default */
7945  }
7946
7947  .pd-compare-btn.added .compare-remove {
7948    display: flex !important; /* Only show when added */
7949    flex-wrap: wrap;
7950    align-items: center;
7951    gap: 0.4rem;
7952    white-space: normal !important;
7953    word-break: break-word;
7954  }
7955
7956  .pd-compare-btn .compare-icon {
7957    display: inline-block !important;
7958    width: 18px;
7959    height: 18px;
7960    flex-shrink: 0;
7961  }
7962
7963  .pd-compare-info {
7964    margin-top: 0.5rem;
7965  }
7966
7967  .compare-note {
7968    font-size: 0.85rem !important;
7969    display: block !important;
7970    visibility: visible !important;
7971    color: #64748b !important;
7972  }
7973}
7974
7975/* iOS Safari specific fixes */
7976@media (max-width: 480px) {
7977  #compareBtn { 
7978    width:46px;
7979    height:46px;
7980  }
7981
7982  .pd-compare-section {
7983    -webkit-transform: translateZ(0);
7984    transform: translateZ(0);
7985  }
7986
7987  .pd-compare-btn {
7988    -webkit-appearance: none !important;
7989    appearance: none !important;
7990    -webkit-tap-highlight-color: transparent;
7991    touch-action: manipulation;
7992  }
7993
7994  /* Fix sidebar height for iOS address bar */
7995  .compare-sidebar {
7996    /* Use fixed positioning that accounts for iOS address bar */
7997    height: 100vh;
7998    height: -webkit-fill-available; /* iOS Safari compatibility */
7999    min-height: 100vh;
8000    min-height: -webkit-fill-available;
8001  }
8002
8003  /* Ensure compare actions stay at bottom */
8004  .compare-actions {
8005    position: sticky;
8006    bottom: 0;
8007    background: white;
8008    border-top: 2px solid #e2e8f0;
8009    box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.15);
8010    padding: 1.25rem 1rem;
8011    z-index: 20;
8012  }
8013
8014  .compare-actions .btn {
8015    min-height: 52px; /* Larger for easier tapping */
8016    font-size: 1rem;
8017    font-weight: 700;
8018    border-radius: 8px;
8019  }
8020
8021  /* Adjust content padding to account for sticky actions */
8022  .compare-content {
8023    padding-bottom: 6rem; /* Extra space for sticky actions */
8024  }
8025}
8026
8027/* High-resolution mobile devices (iPhone 12 Pro, etc.) */
8028@media (-webkit-min-device-pixel-ratio: 2) and (max-width: 430px) {
8029  .pd-compare-section {
8030    display: block !important;
8031  }
8032
8033  .pd-compare-btn {
8034    display: flex !important;
8035    min-height: 44px;
8036    font-size: 16px; /* Prevent zoom on iOS */
8037  }
8038
8039  /* Enhanced fixes for iPhone 12 Pro and similar devices */
8040  .compare-sidebar {
8041    /* Ensure full height coverage */
8042    height: 100vh;
8043    height: 100dvh; /* Dynamic viewport height */
8044    height: -webkit-fill-available; /* iOS fallback */
8045    max-height: 100vh;
8046    max-height: 100dvh;
8047    max-height: -webkit-fill-available;
8048    overflow: hidden; /* Prevent scroll issues */
8049  }
8050
8051  .compare-content {
8052    /* Flexible content area */
8053    flex: 1;
8054    overflow-y: auto;
8055    -webkit-overflow-scrolling: touch;
8056    padding: 1rem 1rem 8rem 1rem; /* Extra bottom padding */
8057  }
8058
8059  .compare-actions {
8060    /* Force actions to stay visible */
8061    position: fixed;
8062    bottom: 0;
8063    left: 0;
8064    right: 0;
8065    width: 100%;
8066    background: white;
8067    border-top: 2px solid #e2e8f0;
8068    box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.2);
8069    padding: 1rem;
8070    z-index: 9999;
8071    display: flex !important;
8072    gap: 0.75rem;
8073  }
8074
8075  .compare-actions .btn {
8076    flex: 1;
8077    min-height: 30px; /* Extra large for high-DPI screens */
8078    font-size: .8rem;
8079    font-weight: 600;
8080    border-radius: 12px;
8081    display: flex !important;
8082    align-items: center;
8083    justify-content: center;
8084    gap: 0.5rem;
8085  }
8086
8087  /* Ensure AI analyze button is always visible and prominent */
8088  #compareAnalyze {
8089    background: #3b82f6 !important;
8090    border-color: #3b82f6 !important;
8091    color: white !important;
8092    box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
8093  }
8094
8095  #compareAnalyze:not(:disabled) {
8096    background: #2563eb !important;
8097    border-color: #2563eb !important;
8098    transform: scale(1.02);
8099  }
8100
8101  #compareAnalyze:disabled {
8102    opacity: 0.6 !important;
8103    background: #9ca3af !important;
8104    border-color: #9ca3af !important;
8105  }
8106}
8107
8108/* AI Disclaimer Styles */
8109.ai-disclaimer {
8110  display: flex;
8111  align-items: flex-start;
8112  gap: 0.75rem;
8113  padding: 1rem;
8114  background: #fef3c7;
8115  border: 1px solid #fbbf24;
8116  border-radius: 8px;
8117  margin: 0;
8118}
8119
8120.disclaimer-icon {
8121  font-size: 1.25rem;
8122  flex-shrink: 0;
8123  margin-top: 0.125rem;
8124}
8125
8126.disclaimer-text {
8127  font-size: 0.85rem;
8128  line-height: 1.4;
8129  color: #92400e;
8130}
8131
8132.disclaimer-text strong {
8133  color: #78350f;
8134  font-weight: 600;
8135}
8136
8137/* Compare Disclaimer in Sidebar */
8138.compare-disclaimer {
8139  margin-top: 1rem;
8140  padding: 1rem;
8141  background: #f0f9ff;
8142  border: 1px solid #0ea5e9;
8143  border-radius: 8px;
8144}
8145
8146.compare-disclaimer h6 {
8147  display: flex;
8148  align-items: center;
8149  gap: 0.5rem;
8150  font-size: 0.875rem;
8151  font-weight: 600;
8152  color: #0c4a6e;
8153  margin-bottom: 0.5rem;
8154}
8155
8156.compare-disclaimer p {
8157  font-size: 0.8rem;
8158  line-height: 1.4;
8159  color: #075985;
8160  margin-bottom: 0.5rem;
8161}
8162
8163.compare-disclaimer p:last-child {
8164  margin-bottom: 0;
8165}
8166
8167.compare-disclaimer ul {
8168  list-style: none;
8169  padding: 0;
8170  margin: 0.5rem 0 0 0;
8171}
8172
8173.compare-disclaimer li {
8174  display: flex;
8175  align-items: flex-start;
8176  gap: 0.5rem;
8177  margin-bottom: 0.25rem;
8178  font-size: 0.75rem;
8179  line-height: 1.3;
8180  color: #075985;
8181}
8182
8183.compare-disclaimer li:before {
8184  content: "•";
8185  color: #0ea5e9;
8186  font-weight: bold;
8187  margin-top: 0.1rem;
8188  flex-shrink: 0;
8189}
8190
8191/* Mobile Browser Address Bar Fixes */
8192@supports (height: 100dvh) {
8193  /* Modern browsers with dynamic viewport support */
8194  @media (max-width: 768px) {
8195    .compare-sidebar {
8196      height: 100dvh !important;
8197      max-height: 100dvh !important;
8198    }
8199
8200    .modal-container {
8201      max-height: 95dvh !important;
8202    }
8203  }
8204}
8205
8206@supports (height: -webkit-fill-available) {
8207  /* iOS Safari specific support */
8208  @media (max-width: 768px) {
8209    .compare-sidebar {
8210      height: -webkit-fill-available !important;
8211      max-height: -webkit-fill-available !important;
8212    }
8213  }
8214}
8215
8216/* Fallback for older browsers */
8217@media (max-width: 768px) and (orientation: portrait) {
8218  .compare-sidebar {
8219    /* Fixed height that works across different mobile browsers */
8220    min-height: 600px;
8221    height: calc(100vh - env(keyboard-inset-height, 0px));
8222    height: calc(100vh - env(safe-area-inset-bottom, 0px));
8223  }
8224
8225  /* Ensure actions are always accessible */
8226  .compare-actions {
8227    position: sticky !important;
8228    bottom: env(safe-area-inset-bottom, 0px) !important;
8229    margin-bottom: 0 !important;
8230    padding-bottom: calc(1rem + env(safe-area-inset-bottom, 0px)) !important;
8231  }
8232}
8233
8234/* PWA and fullscreen app support */
8235@media (display-mode: standalone) {
8236  .compare-sidebar {
8237    height: 100vh !important;
8238    height: 100dvh !important;
8239  }
8240}
8241
8242/* Utilities */
8243.btn-sm {
8244  padding: 0.5rem 0.75rem;
8245  font-size: 0.8rem;
8246}
8247
8248[hidden] {
8249  display: none !important;
8250}
8251
8252/* ===== UX ENHANCEMENTS ===== */
8253
8254/* Compare Button Highlight Animation */
8255.pulse-highlight {
8256  animation: pulse-glow 2s ease-in-out 3;
8257  box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7);
8258}
8259
8260@keyframes pulse-glow {
8261  0% {
8262    box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7);
8263  }
8264  50% {
8265    box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
8266  }
8267  100% {
8268    box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
8269  }
8270}
8271
8272/* Flying Product Animation */
8273.flying-product {
8274  position: fixed;
8275  z-index: 9999;
8276  pointer-events: none;
8277}
8278
8279.flying-product img {
8280  border-radius: 8px;
8281  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
8282}
8283
8284/* Sticky Compare Bar - REMOVED (using only fly-to-navbar animation) */
8285
8286/* Inline Compare Preview - REMOVED (keeping it simple and clean) */
8287</style>
8288  <style data-inlined="product-card-actions.css">
8289/**
8290 * Product Card Quick Actions - 3 Dot Menu + Bottom Sheet
8291 * Tokopedia-inspired design
8292 */
8293
8294/* ===== 3 Dot Button on Product Card ===== */
8295.prod-card {
8296    position: relative;
8297}
8298
8299.prod-card-actions-btn {
8300    position: absolute;
8301    top: 8px;
8302    right: 8px;
8303    width: 32px;
8304    height: 32px;
8305    border-radius: 50%;
8306    background: rgba(255, 255, 255, 0.95);
8307    border: 1px solid rgba(0, 0, 0, 0.08);
8308    display: inline-flex;
8309    align-items: center;
8310    justify-content: center;
8311    cursor: pointer;
8312    z-index: 3;
8313    transition: all 0.2s ease;
8314    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
8315    -webkit-tap-highlight-color: transparent;
8316}
8317
8318.prod-card-actions-btn:hover {
8319    background: #fff;
8320    border-color: rgba(0, 0, 0, 0.15);
8321    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
8322    transform: scale(1.05);
8323}
8324
8325.prod-card-actions-btn:active {
8326    transform: scale(0.95);
8327}
8328
8329.prod-card-actions-btn svg {
8330    width: 20px;
8331    height: 20px;
8332    color: #64748b;
8333    display: block;
8334}
8335
8336/* Desktop: Show on hover */
8337@media (min-width: 641px) {
8338    .prod-card-actions-btn {
8339        opacity: 0;
8340        pointer-events: none;
8341    }
8342
8343    .prod-card:hover .prod-card-actions-btn {
8344        opacity: 1;
8345        pointer-events: auto;
8346    }
8347}
8348
8349/* Mobile: Always visible but smaller */
8350@media (max-width: 640px) {
8351    .prod-card-actions-btn {
8352        width: 28px;
8353        height: 28px;
8354        top: 6px;
8355        right: 6px;
8356        opacity: 95%;
8357    }
8358
8359    .prod-card-actions-btn svg {
8360        width: 18px;
8361        height: 18px;
8362    }
8363}
8364
8365/* ===== Bottom Sheet Overlay ===== */
8366.pca-overlay {
8367    position: fixed;
8368    inset: 0;
8369    background: rgba(15, 23, 42, 0.6);
8370    backdrop-filter: blur(4px);
8371    z-index: 99998;
8372    opacity: 0;
8373    visibility: hidden;
8374    transition: all 0.3s ease;
8375}
8376
8377.pca-overlay.active {
8378    opacity: 1;
8379    visibility: visible;
8380}
8381
8382/* ===== Bottom Sheet Container ===== */
8383.pca-bottom-sheet {
8384    position: fixed;
8385    bottom: 0;
8386    left: 0;
8387    right: 0;
8388    background: #fff;
8389    border-radius: 20px 20px 0 0;
8390    box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.15);
8391    z-index: 99999;
8392    max-width: 600px;
8393    margin: 0 auto;
8394    transform: translateY(100%);
8395    transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
8396}
8397
8398.pca-bottom-sheet.active {
8399    transform: translateY(0);
8400}
8401
8402/* Drag Handle */
8403.pca-sheet-handle {
8404    width: 40px;
8405    height: 4px;
8406    background: #cbd5e1;
8407    border-radius: 999px;
8408    margin: 12px auto 8px;
8409}
8410
8411/* Product Preview */
8412.pca-sheet-product {
8413    display: flex;
8414    gap: 12px;
8415    padding: 12px 20px;
8416    border-bottom: 1px solid #f1f5f9;
8417}
8418
8419.pca-product-image {
8420    width: 60px;
8421    height: 60px;
8422    border-radius: 8px;
8423    object-fit: cover;
8424    flex-shrink: 0;
8425    border: 1px solid #e2e8f0;
8426}
8427
8428.pca-product-info {
8429    flex: 1;
8430    min-width: 0;
8431}
8432
8433.pca-product-name {
8434    font-size: 14px;
8435    font-weight: 600;
8436    color: #0f172a;
8437    margin: 0 0 4px 0;
8438    display: -webkit-box;
8439    -webkit-line-clamp: 2;
8440    -webkit-box-orient: vertical;
8441    overflow: hidden;
8442    line-height: 1.3;
8443}
8444
8445.pca-product-price {
8446    font-size: 15px;
8447    font-weight: 700;
8448    color: #0ea5e9;
8449    margin: 0;
8450}
8451
8452.pca-product-stock {
8453    font-size: 12px;
8454    color: #64748b;
8455    margin: 2px 0 0 0;
8456}
8457
8458.pca-product-stock.in {
8459    color: #16a34a;
8460}
8461
8462.pca-product-stock.low {
8463    color: #ea580c;
8464}
8465
8466.pca-product-stock.out {
8467    color: #dc2626;
8468}
8469
8470/* Action Menu */
8471.pca-sheet-actions {
8472    padding: 8px 0 20px;
8473}
8474
8475.pca-action-item {
8476    display: flex;
8477    align-items: center;
8478    gap: 16px;
8479    padding: 16px 20px;
8480    background: transparent;
8481    border: 0;
8482    width: 100%;
8483    cursor: pointer;
8484    transition: background-color 0.15s ease;
8485    -webkit-tap-highlight-color: transparent;
8486}
8487
8488.pca-action-item:hover {
8489    background: #f8fafc;
8490}
8491
8492.pca-action-item:active {
8493    background: #f1f5f9;
8494}
8495
8496.pca-action-item:disabled {
8497    opacity: 0.4;
8498    cursor: not-allowed;
8499}
8500
8501.pca-action-item:disabled:hover {
8502    background: transparent;
8503}
8504
8505.pca-action-icon {
8506    width: 24px;
8507    height: 24px;
8508    color: #475569;
8509    flex-shrink: 0;
8510}
8511
8512.pca-action-content {
8513    flex: 1;
8514    text-align: left;
8515}
8516
8517.pca-action-label {
8518    font-size: 15px;
8519    font-weight: 600;
8520    color: #0f172a;
8521    margin: 0 0 2px 0;
8522}
8523
8524.pca-action-desc {
8525    font-size: 12px;
8526    color: #64748b;
8527    margin: 0;
8528}
8529
8530.pca-action-badge {
8531    display: inline-flex;
8532    align-items: center;
8533    justify-content: center;
8534    min-width: 20px;
8535    height: 20px;
8536    padding: 0 6px;
8537    background: #0ea5e9;
8538    color: #fff;
8539    font-size: 11px;
8540    font-weight: 700;
8541    border-radius: 999px;
8542    margin-left: auto;
8543}
8544
8545.pca-action-badge.compare {
8546    background: #8b5cf6;
8547}
8548
8549/* Action Item Colors */
8550.pca-action-item.cart .pca-action-icon {
8551    color: #0ea5e9;
8552}
8553
8554.pca-action-item.compare .pca-action-icon {
8555    color: #8b5cf6;
8556}
8557
8558.pca-action-item.compare.active {
8559    background: rgba(139, 92, 246, 0.05);
8560}
8561
8562.pca-action-item.compare.active .pca-action-label {
8563    color: #8b5cf6;
8564}
8565
8566.pca-action-item.compare.active .pca-action-icon {
8567    color: #8b5cf6;
8568}
8569
8570.pca-action-item.share .pca-action-icon {
8571    color: #64748b;
8572}
8573
8574/* Loading State */
8575.pca-action-item.loading {
8576    pointer-events: none;
8577}
8578
8579.pca-action-item.loading .pca-action-icon {
8580    animation: spin 1s linear infinite;
8581}
8582
8583@keyframes spin {
8584    from { transform: rotate(0deg); }
8585    to { transform: rotate(360deg); }
8586}
8587
8588/* Success State Animation */
8589.pca-action-item.success .pca-action-icon {
8590    animation: checkmark 0.5s ease;
8591}
8592
8593@keyframes checkmark {
8594    0% { transform: scale(0); }
8595    50% { transform: scale(1.2); }
8596    100% { transform: scale(1); }
8597}
8598
8599/* ===== Safe Area for Mobile Devices ===== */
8600@supports (padding-bottom: env(safe-area-inset-bottom)) {
8601    .pca-sheet-actions {
8602        padding-bottom: calc(20px + env(safe-area-inset-bottom));
8603    }
8604}
8605
8606/* ===== Responsive Tweaks ===== */
8607@media (max-width: 640px) {
8608    .pca-bottom-sheet {
8609        border-radius: 16px 16px 0 0;
8610    }
8611
8612    .pca-sheet-product {
8613        padding: 10px 16px;
8614    }
8615
8616    .pca-product-image {
8617        width: 50px;
8618        height: 50px;
8619    }
8620
8621    .pca-product-name {
8622        font-size: 13px;
8623    }
8624
8625    .pca-product-price {
8626        font-size: 14px;
8627    }
8628
8629    .pca-action-item {
8630        padding: 14px 16px;
8631        gap: 12px;
8632    }
8633
8634    .pca-action-icon {
8635        width: 22px;
8636        height: 22px;
8637    }
8638
8639    .pca-action-label {
8640        font-size: 14px;
8641    }
8642}
8643
8644/* ===== Animation for Flying Product ===== */
8645.flying-product {
8646    position: fixed;
8647    pointer-events: none;
8648    z-index: 99999;
8649    animation: fadeOut 0.8s ease forwards;
8650}
8651
8652@keyframes fadeOut {
8653    to {
8654        opacity: 0;
8655        transform: scale(0.5);
8656    }
8657}
8658
8659/* ===== Desktop Dropdown (Alternative) ===== */
8660@media (min-width: 1024px) {
8661    /* Optional: Could add desktop dropdown menu here */
8662    /* For now, using bottom sheet for consistency */
8663}
8664
8665/* ===== Print Styles ===== */
8666@media print {
8667    .prod-card-actions-btn,
8668    .pca-overlay,
8669    .pca-bottom-sheet {
8670        display: none !important;
8671    }
8672}
8673
8674</style>
8675  <style data-inlined="modal.css">
8676/* Modern Modal Dialog for Cart Operations */
8677.modal-overlay {
8678  position: fixed;
8679  top: 0;
8680  left: 0;
8681  right: 0;
8682  bottom: 0;
8683  background: rgba(0, 0, 0, 0.5);
8684  backdrop-filter: blur(4px);
8685  display: flex;
8686  align-items: center;
8687  justify-content: center;
8688  z-index: 9999;
8689  opacity: 0;
8690  visibility: hidden;
8691  transition: all 0.2s ease;
8692}
8693
8694.modal-overlay.show {
8695  opacity: 1;
8696  visibility: visible;
8697}
8698
8699.modal-dialog {
8700  background: white;
8701  border-radius: 16px;
8702  box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15);
8703  max-width: 400px;
8704  width: 90%;
8705  max-height: 90vh;
8706  overflow-y: auto;
8707  transform: scale(0.95);
8708  transition: transform 0.2s ease;
8709}
8710
8711.modal-overlay.show .modal-dialog {
8712  transform: scale(1);
8713}
8714
8715.modal-header {
8716  padding: 24px 24px 16px;
8717  border-bottom: 1px solid #f1f5f9;
8718}
8719
8720.modal-title {
8721  font-size: 20px;
8722  font-weight: 700;
8723  color: #0f172a;
8724  margin: 0;
8725  display: flex;
8726  align-items: center;
8727  gap: 12px;
8728}
8729
8730.modal-body {
8731  padding: 16px 24px 24px;
8732}
8733
8734.modal-message {
8735  font-size: 16px;
8736  line-height: 1.5;
8737  color: #475569;
8738  margin: 0;
8739}
8740
8741.modal-actions {
8742  display: flex;
8743  gap: 12px;
8744  margin-top: 24px;
8745}
8746
8747.modal-btn {
8748  flex: 1;
8749  padding: 12px 16px;
8750  border: none;
8751  border-radius: 8px;
8752  font-size: 14px;
8753  font-weight: 600;
8754  cursor: pointer;
8755  transition: all 0.2s ease;
8756  text-align: center;
8757}
8758
8759.modal-btn-secondary {
8760  background: #f8fafc;
8761  color: #64748b;
8762  border: 1px solid #e2e8f0;
8763}
8764
8765.modal-btn-secondary:hover {
8766  background: #f1f5f9;
8767  border-color: #cbd5e1;
8768}
8769
8770.modal-btn-danger {
8771  background: #ef4444;
8772  color: white;
8773}
8774
8775.modal-btn-danger:hover {
8776  background: #dc2626;
8777}
8778
8779.modal-btn-primary {
8780  background: #3b82f6;
8781  color: white;
8782}
8783
8784.modal-btn-primary:hover {
8785  background: #2563eb;
8786}
8787
8788.modal-icon {
8789  font-size: 24px;
8790  width: 32px;
8791  height: 32px;
8792  display: flex;
8793  align-items: center;
8794  justify-content: center;
8795  border-radius: 8px;
8796}
8797
8798.modal-icon.danger {
8799  background: #fef2f2;
8800  color: #ef4444;
8801}
8802
8803.modal-icon.warning {
8804  background: #fffbeb;
8805  color: #f59e0b;
8806}
8807
8808.modal-icon.success {
8809  background: #f0fdf4;
8810  color: #22c55e;
8811}
8812
8813.modal-icon.info {
8814  background: #eff6ff;
8815  color: #3b82f6;
8816}
8817
8818/* Mobile responsive */
8819@media (max-width: 640px) {
8820  .modal-dialog {
8821    margin: 16px;
8822    max-width: none;
8823    width: auto;
8824  }
8825
8826  .modal-header,
8827  .modal-body {
8828    padding-left: 20px;
8829    padding-right: 20px;
8830  }
8831
8832  .modal-actions {
8833    flex-direction: column;
8834  }
8835
8836  .modal-btn {
8837    flex: none;
8838  }
8839}
8840</style>
8841
8842        <style data-inlined="reviews-points.css">
8843/**
8844 * Reviews & Points System - Styles
8845 * Product reviews, rating display, and point redemption UI
8846 */
8847
8848/* ===================================
8849   PRODUCT REVIEWS
8850   =================================== */
8851
8852.product-tabs {
8853  display: flex;
8854  border-bottom: 2px solid #e5e7eb;
8855  margin: 24px 0 16px;
8856  gap: 8px;
8857}
8858
8859.tab-btn {
8860  padding: 12px 20px;
8861  border: none;
8862  background: none;
8863  cursor: pointer;
8864  font-size: 15px;
8865  font-weight: 500;
8866  color: #6b7280;
8867  border-bottom: 3px solid transparent;
8868  transition: all 0.2s ease;
8869  position: relative;
8870  top: 2px;
8871}
8872
8873.tab-btn:hover {
8874  color: #111827;
8875}
8876
8877.tab-btn.active {
8878  color: #2563eb;
8879  border-bottom-color: #2563eb;
8880}
8881
8882.tab-content {
8883  padding: 20px 0;
8884}
8885
8886/* Review Statistics */
8887.review-stats {
8888  display: grid;
8889  grid-template-columns: 200px 1fr;
8890  gap: 32px;
8891  padding: 24px;
8892  background: #f9fafb;
8893  border-radius: 12px;
8894  margin-bottom: 24px;
8895}
8896
8897.rating-summary {
8898  text-align: center;
8899}
8900
8901.avg-rating {
8902  display: block;
8903  font-size: 48px;
8904  font-weight: 700;
8905  color: #111827;
8906  line-height: 1;
8907}
8908
8909.rating-summary .stars {
8910  font-size: 24px;
8911  color: #fbbf24;
8912  margin: 8px 0;
8913}
8914
8915.total-reviews {
8916  display: block;
8917  font-size: 14px;
8918  color: #6b7280;
8919  margin-top: 4px;
8920}
8921
8922.rating-breakdown {
8923  display: flex;
8924  flex-direction: column;
8925  gap: 8px;
8926}
8927
8928.rating-bar {
8929  display: flex;
8930  align-items: center;
8931  gap: 12px;
8932}
8933
8934.rating-label {
8935  font-size: 14px;
8936  color: #374151;
8937  width: 32px;
8938  text-align: right;
8939}
8940
8941.bar-container {
8942  flex: 1;
8943  height: 8px;
8944  background: #e5e7eb;
8945  border-radius: 4px;
8946  overflow: hidden;
8947}
8948
8949.bar-fill {
8950  height: 100%;
8951  background: linear-gradient(90deg, #fbbf24 0%, #f59e0b 100%);
8952  transition: width 0.3s ease;
8953}
8954
8955.rating-count {
8956  font-size: 14px;
8957  color: #6b7280;
8958  width: 40px;
8959  text-align: right;
8960}
8961
8962/* Review Filters */
8963.review-filters {
8964  display: flex;
8965  justify-content: space-between;
8966  align-items: center;
8967  margin-bottom: 20px;
8968}
8969
8970.review-sort {
8971  padding: 8px 12px;
8972  border: 1px solid #d1d5db;
8973  border-radius: 8px;
8974  font-size: 14px;
8975  background: white;
8976}
8977
8978.write-review-btn {
8979  padding: 10px 20px;
8980  background: #2563eb;
8981  color: white;
8982  border: none;
8983  border-radius: 8px;
8984  font-weight: 500;
8985  cursor: pointer;
8986  transition: background 0.2s;
8987}
8988
8989.write-review-btn:hover {
8990  background: #1d4ed8;
8991}
8992
8993/* Review List */
8994.reviews-list {
8995  display: flex;
8996  flex-direction: column;
8997  gap: 16px;
8998}
8999
9000.review-item {
9001  padding: 20px;
9002  background: white;
9003  border: 1px solid #e5e7eb;
9004  border-radius: 12px;
9005  transition: box-shadow 0.2s;
9006}
9007
9008.review-item:hover {
9009  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
9010}
9011
9012.review-header {
9013  display: flex;
9014  justify-content: space-between;
9015  align-items: flex-start;
9016  margin-bottom: 8px;
9017}
9018
9019.review-author {
9020  display: flex;
9021  align-items: center;
9022  gap: 8px;
9023}
9024
9025.review-author strong {
9026  font-size: 15px;
9027  color: #111827;
9028}
9029
9030.verified-badge {
9031  display: inline-flex;
9032  align-items: center;
9033  gap: 4px;
9034  font-size: 12px;
9035  color: #059669;
9036  background: #d1fae5;
9037  padding: 2px 8px;
9038  border-radius: 4px;
9039  font-weight: 500;
9040}
9041
9042.anonymous-badge {
9043  display: inline-flex;
9044  align-items: center;
9045  font-size: 12px;
9046  color: #6b7280;
9047  background: #f3f4f6;
9048  padding: 2px 6px;
9049  border-radius: 4px;
9050  margin-left: 4px;
9051  cursor: help;
9052}
9053
9054.review-rating .stars {
9055  font-size: 16px;
9056  color: #fbbf24;
9057}
9058
9059.review-date {
9060  font-size: 13px;
9061  color: #9ca3af;
9062  margin-bottom: 12px;
9063}
9064
9065.review-text {
9066  font-size: 15px;
9067  line-height: 1.6;
9068  color: #374151;
9069  margin-bottom: 12px;
9070}
9071
9072.review-images {
9073  display: flex;
9074  gap: 8px;
9075  margin-bottom: 12px;
9076  flex-wrap: wrap;
9077}
9078
9079.review-img {
9080  width: 80px;
9081  height: 80px;
9082  object-fit: cover;
9083  border-radius: 8px;
9084  cursor: pointer;
9085  transition: transform 0.2s;
9086}
9087
9088.review-img:hover {
9089  transform: scale(1.05);
9090}
9091
9092.review-actions {
9093  display: flex;
9094  gap: 12px;
9095  padding-top: 12px;
9096  border-top: 1px solid #f3f4f6;
9097}
9098
9099.btn-helpful {
9100  display: flex;
9101  align-items: center;
9102  gap: 6px;
9103  padding: 6px 12px;
9104  background: #f9fafb;
9105  border: 1px solid #e5e7eb;
9106  border-radius: 6px;
9107  font-size: 13px;
9108  color: #6b7280;
9109  cursor: pointer;
9110  transition: all 0.2s;
9111}
9112
9113.btn-helpful:hover {
9114  background: #f3f4f6;
9115  color: #111827;
9116  border-color: #d1d5db;
9117}
9118
9119.btn-helpful.voted {
9120  background: #dbeafe;
9121  color: #1d4ed8;
9122  border-color: #93c5fd;
9123}
9124
9125.helpful-icon {
9126  font-size: 14px;
9127}
9128
9129.no-reviews {
9130  text-align: center;
9131  padding: 60px 20px;
9132  color: #9ca3af;
9133  font-size: 15px;
9134}
9135
9136.load-more-btn {
9137  display: block;
9138  margin: 24px auto 0;
9139  padding: 12px 32px;
9140  background: white;
9141  border: 1px solid #d1d5db;
9142  border-radius: 8px;
9143  font-size: 14px;
9144  font-weight: 500;
9145  color: #374151;
9146  cursor: pointer;
9147  transition: all 0.2s;
9148}
9149
9150.load-more-btn:hover {
9151  background: #f9fafb;
9152  border-color: #9ca3af;
9153}
9154
9155/* ===================================
9156   POINT BALANCE WIDGET (LEGACY - kept for backward compatibility)
9157   =================================== */
9158
9159.point-balance-card {
9160  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
9161  color: white;
9162  padding: 24px;
9163  border-radius: 16px;
9164  margin-bottom: 24px;
9165  box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3);
9166}
9167
9168.point-header {
9169  display: flex;
9170  justify-content: space-between;
9171  align-items: center;
9172  margin-bottom: 12px;
9173}
9174
9175.point-header h3 {
9176  font-size: 18px;
9177  font-weight: 600;
9178  margin: 0;
9179  display: flex;
9180  align-items: center;
9181  gap: 8px;
9182}
9183
9184.point-header .balance {
9185  font-size: 32px;
9186  font-weight: 700;
9187}
9188
9189.point-value {
9190  font-size: 14px;
9191  opacity: 0.9;
9192  margin: 8px 0 16px;
9193}
9194
9195.point-stats {
9196  display: flex;
9197  gap: 20px;
9198  font-size: 13px;
9199  opacity: 0.85;
9200  margin-bottom: 16px;
9201}
9202
9203.btn-view-history {
9204  display: inline-block;
9205  padding: 10px 20px;
9206  background: rgba(255, 255, 255, 0.2);
9207  color: white;
9208  text-decoration: none;
9209  border-radius: 8px;
9210  font-size: 14px;
9211  font-weight: 500;
9212  transition: background 0.2s;
9213}
9214
9215.btn-view-history:hover {
9216  background: rgba(255, 255, 255, 0.3);
9217}
9218
9219/* ===================================
9220   CHECKOUT POINT REDEMPTION
9221   =================================== */
9222
9223.checkout-points {
9224  padding: 16px;
9225  background: #fef3c7;
9226  border: 2px solid #fbbf24;
9227  border-radius: 12px;
9228  margin-bottom: 16px;
9229}
9230
9231.points-header {
9232  display: flex;
9233  align-items: center;
9234  gap: 10px;
9235  margin-bottom: 12px;
9236}
9237
9238.points-header input[type="checkbox"] {
9239  width: 18px;
9240  height: 18px;
9241  cursor: pointer;
9242}
9243
9244.points-header label {
9245  font-size: 15px;
9246  color: #78350f;
9247  cursor: pointer;
9248  margin: 0;
9249}
9250
9251#points-slider {
9252  padding: 16px;
9253  background: white;
9254  border-radius: 8px;
9255  margin-top: 12px;
9256}
9257
9258#points-slider label {
9259  display: block;
9260  font-size: 14px;
9261  color: #374151;
9262  margin-bottom: 8px;
9263  font-weight: 500;
9264}
9265
9266#points-amount {
9267  width: 100%;
9268  margin-bottom: 8px;
9269}
9270
9271#points-display {
9272  display: flex;
9273  justify-content: space-between;
9274  font-size: 14px;
9275  color: #111827;
9276  margin-top: 8px;
9277}
9278
9279#points-display .discount-amount {
9280  color: #059669;
9281  font-weight: 600;
9282}
9283
9284.price-row.point-discount {
9285  color: #059669;
9286  font-weight: 500;
9287}
9288
9289/* ===================================
9290   REVIEWABLE ORDERS
9291   =================================== */
9292
9293.reviewable-orders {
9294  background: white;
9295  border: 1px solid #e5e7eb;
9296  border-radius: 12px;
9297  padding: 20px;
9298  margin-bottom: 24px;
9299}
9300
9301.reviewable-orders h3 {
9302  font-size: 18px;
9303  margin-bottom: 16px;
9304  color: #111827;
9305}
9306
9307.order-card {
9308  display: flex;
9309  gap: 16px;
9310  padding: 16px;
9311  background: #f9fafb;
9312  border-radius: 8px;
9313  margin-bottom: 12px;
9314}
9315
9316.order-card:last-child {
9317  margin-bottom: 0;
9318}
9319
9320.order-card img {
9321  width: 80px;
9322  height: 80px;
9323  object-fit: cover;
9324  border-radius: 8px;
9325}
9326
9327.order-info {
9328  flex: 1;
9329}
9330
9331.order-info h4 {
9332  font-size: 15px;
9333  margin: 0 0 4px;
9334  color: #111827;
9335}
9336
9337.order-info p {
9338  font-size: 13px;
9339  color: #6b7280;
9340  margin: 0 0 12px;
9341}
9342
9343.btn-review {
9344  padding: 8px 16px;
9345  background: #059669;
9346  color: white;
9347  border: none;
9348  border-radius: 6px;
9349  font-size: 14px;
9350  font-weight: 500;
9351  cursor: pointer;
9352  transition: background 0.2s;
9353}
9354
9355.btn-review:hover {
9356  background: #047857;
9357}
9358
9359/* ===================================
9360   TOAST NOTIFICATIONS
9361   =================================== */
9362
9363.review-toast {
9364  position: fixed;
9365  bottom: 24px;
9366  right: 24px;
9367  padding: 14px 20px;
9368  background: #111827;
9369  color: white;
9370  border-radius: 8px;
9371  font-size: 14px;
9372  line-height: 1.4;
9373  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
9374  transform: translateY(100px);
9375  opacity: 0;
9376  transition: all 0.3s ease;
9377  z-index: 10000;
9378  max-width: 400px;
9379  word-wrap: break-word;
9380}
9381
9382.review-toast.show {
9383  transform: translateY(0);
9384  opacity: 1;
9385}
9386
9387/* Mobile: Position above bottom-nav */
9388@media (max-width: 768px) {
9389  .review-toast {
9390    bottom: 80px; /* Above bottom-nav (50px height + padding + safe margin) */
9391    right: 16px;
9392    left: 16px;
9393    max-width: calc(100% - 32px);
9394  }
9395}
9396
9397.review-toast.toast-success {
9398  background: #059669;
9399}
9400
9401.review-toast.toast-error {
9402  background: #dc2626;
9403}
9404
9405.review-toast.toast-info {
9406  background: #2563eb;
9407}
9408
9409/* ===================================
9410   RESPONSIVE
9411   =================================== */
9412
9413@media (max-width: 768px) {
9414  .point-balance-card {
9415    border-radius: 0;
9416  }
9417
9418  .review-stats {
9419    grid-template-columns: 70px 1fr;
9420    gap: 20px;
9421    padding: 16px;
9422  }
9423
9424  .rating-summary {
9425    text-align: center;
9426  }
9427
9428  .rating-summary .avg-rating {
9429    font-size: 32px;
9430  }
9431
9432  .rating-summary .stars {
9433    font-size: 18px;
9434    margin: 4px 0;
9435  }
9436
9437  .total-reviews {
9438    font-size: 11px;
9439  }
9440
9441  /* Rating breakdown - more compact */
9442  .rating-breakdown {
9443    gap: 2px;
9444  }
9445
9446  .rating-breakdown .rating-bar {
9447    gap: 4px;
9448  }
9449
9450  .rating-breakdown .rating-label {
9451    font-size: 11px;
9452    width: 16px;
9453    min-width: 16px;
9454    text-align: left;
9455  }
9456
9457  .rating-breakdown .bar-container {
9458    height: 6px;
9459  }
9460
9461  .rating-breakdown .rating-count {
9462    font-size: 11px;
9463    width: 16px;
9464    min-width: 16px;
9465  }
9466
9467  .review-header {
9468    flex-direction: column;
9469    gap: 8px;
9470    align-items: flex-start;
9471  }
9472
9473  .review-rating {
9474    order: -1;
9475    margin-bottom: 4px;
9476  }
9477
9478  .review-rating .stars {
9479    font-size: 14px;
9480  }
9481
9482  .review-filters {
9483    flex-direction: column;
9484    gap: 12px;
9485    align-items: stretch;
9486  }
9487
9488  .review-sort {
9489    width: 100%;
9490  }
9491
9492  .point-header .balance {
9493    font-size: 24px;
9494  }
9495
9496  .point-stats {
9497    flex-direction: column;
9498    gap: 8px;
9499  }
9500
9501  .order-card {
9502    flex-direction: column;
9503  }
9504
9505  .order-card img {
9506    width: 100%;
9507    height: 200px;
9508  }
9509}
9510
9511/* ===================================
9512   LOADING & ERROR STATES
9513   =================================== */
9514
9515.review-loading {
9516  text-align: center;
9517  padding: 40px;
9518  color: #9ca3af;
9519  font-size: 14px;
9520}
9521
9522.review-error {
9523  text-align: center;
9524  padding: 40px;
9525  color: #dc2626;
9526  font-size: 14px;
9527  background: #fee2e2;
9528  border-radius: 8px;
9529}
9530
9531.review-empty {
9532  text-align: center;
9533  padding: 10px;
9534  color: #6b7280;
9535  font-size: 14px;
9536}
9537
9538/* ===================================
9539   REVIEWABLE ORDERS LIST
9540   =================================== */
9541
9542.reviewable-orders-list {
9543  display: flex;
9544  flex-direction: column;
9545  gap: 16px;
9546}
9547
9548.reviewable-order-item {
9549  display: grid;
9550  grid-template-columns: 80px 1fr auto;
9551  gap: 16px;
9552  padding: 16px;
9553  background: #fff;
9554  border: 1px solid #e5e7eb;
9555  border-radius: 12px;
9556  align-items: center;
9557  transition: box-shadow 0.2s;
9558}
9559
9560.reviewable-order-item:hover {
9561  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
9562}
9563
9564.reviewable-order-item .roi-image {
9565  width: 80px;
9566  height: 80px;
9567  object-fit: cover;
9568  border-radius: 8px;
9569  border: 1px solid #e5e7eb;
9570}
9571
9572.reviewable-order-item .roi-info {
9573  display: flex;
9574  flex-direction: column;
9575  gap: 4px;
9576}
9577
9578.reviewable-order-item .roi-info h4 {
9579  margin: 0;
9580  font-size: 15px;
9581  font-weight: 600;
9582  color: #111827;
9583  line-height: 1.4;
9584}
9585
9586.reviewable-order-item .roi-meta {
9587  margin: 0;
9588  font-size: 13px;
9589  color: #6b7280;
9590}
9591
9592.reviewable-order-item .btn-write-review {
9593  white-space: nowrap;
9594  padding: 10px 20px;
9595  font-size: 14px;
9596}
9597
9598@media (max-width: 640px) {
9599  .reviewable-order-item {
9600    grid-template-columns: 60px 1fr;
9601    gap: 12px;
9602  }
9603
9604  .reviewable-order-item .roi-image {
9605    width: 60px;
9606    height: 60px;
9607  }
9608
9609  .reviewable-order-item .btn-write-review {
9610    grid-column: 1 / -1;
9611    width: 100%;
9612  }
9613}
9614
9615/* ===================================
9616   REVIEW SUBMISSION MODAL
9617   =================================== */
9618
9619/* Modal Overlay Background */
9620#review-modal-overlay {
9621  position: fixed;
9622  top: 0;
9623  left: 0;
9624  right: 0;
9625  bottom: 0;
9626  background: rgba(0, 0, 0, 0.6) !important;
9627  backdrop-filter: blur(8px);
9628  display: flex;
9629  align-items: center;
9630  justify-content: center;
9631  z-index: 99999 !important;
9632  padding: 20px;
9633}
9634
9635.review-modal {
9636  max-width: 600px !important;
9637  width: 90% !important;
9638  background: white;
9639  border-radius: 16px !important;
9640  box-shadow: 0 25px 50px rgba(0, 0, 0, 0.25);
9641  max-height: 90vh;
9642  display: flex;
9643  flex-direction: column;
9644  overflow: hidden !important;
9645}
9646
9647#review-modal-overlay .modal-content,
9648.review-modal .modal-content {
9649  padding: 0 !important;
9650  flex: initial !important;
9651  overflow-y: visible !important;
9652}
9653
9654.review-modal .modal-header {
9655  display: flex;
9656  justify-content: space-between;
9657  align-items: center;
9658  padding: 20px 24px;
9659  border-bottom: 1px solid #e5e7eb;
9660}
9661
9662.review-modal .modal-title {
9663  margin: 0;
9664  font-size: 20px;
9665  font-weight: 700;
9666  color: #111827;
9667}
9668
9669.review-modal .modal-close {
9670  background: none;
9671  border: none;
9672  cursor: pointer;
9673  padding: 4px;
9674  color: #6b7280;
9675  transition: color 0.2s;
9676  display: flex;
9677  align-items: center;
9678  justify-content: center;
9679}
9680
9681.review-modal .modal-close:hover {
9682  color: #111827;
9683}
9684
9685.review-modal .modal-body {
9686  padding: 24px;
9687  max-height: calc(90vh - 140px);
9688  overflow-y: auto;
9689}
9690
9691/* Custom Scrollbar for Modal */
9692.review-modal .modal-body::-webkit-scrollbar {
9693  width: 8px;
9694}
9695
9696.review-modal .modal-body::-webkit-scrollbar-track {
9697  background: #f1f5f9;
9698  border-radius: 4px;
9699}
9700
9701.review-modal .modal-body::-webkit-scrollbar-thumb {
9702  background: linear-gradient(180deg, #3b82f6 0%, #2563eb 100%);
9703  border-radius: 4px;
9704}
9705
9706.review-modal .modal-body::-webkit-scrollbar-thumb:hover {
9707  background: linear-gradient(180deg, #2563eb 0%, #1d4ed8 100%);
9708}
9709
9710/* Firefox Scrollbar */
9711.review-modal .modal-body {
9712  scrollbar-width: thin;
9713  scrollbar-color: #3b82f6 #f1f5f9;
9714}
9715
9716.review-modal .modal-footer {
9717  display: flex;
9718  gap: 12px;
9719  margin-top: 24px;
9720  padding-top: 20px;
9721  border-top: 1px solid #f1f5f9;
9722}
9723
9724.review-product-info {
9725  display: flex;
9726  gap: 16px;
9727  padding: 16px;
9728  background: #f9fafb;
9729  border-radius: 8px;
9730  margin-bottom: 24px;
9731}
9732
9733.review-product-thumb {
9734  width: 80px;
9735  height: 80px;
9736  object-fit: cover;
9737  border-radius: 6px;
9738  border: 1px solid #e5e7eb;
9739}
9740
9741.review-product-details h4 {
9742  margin: 0 0 8px 0;
9743  font-size: 16px;
9744  font-weight: 600;
9745  color: #111827;
9746}
9747
9748.review-product-details p {
9749  margin: 0;
9750  font-size: 13px;
9751  color: #6b7280;
9752}
9753
9754/* Star Rating Input */
9755.star-rating-input {
9756  display: inline-flex;
9757  gap: 8px;
9758  margin-right: 12px;
9759  vertical-align: middle;
9760}
9761
9762.star-btn {
9763  background: none;
9764  border: none;
9765  font-size: 32px;
9766  color: #d1d5db;
9767  cursor: pointer;
9768  transition: all 0.2s;
9769  padding: 0;
9770  line-height: 1;
9771}
9772
9773.star-btn:hover,
9774.star-btn.active {
9775  color: #fbbf24;
9776  transform: scale(1.1);
9777}
9778
9779.rating-label {
9780  display: inline-block;
9781  font-size: 15px;
9782  color: #6b7280;
9783  margin: 0;
9784  vertical-align: middle;
9785  min-width: 150px;
9786}
9787
9788/* Form Elements */
9789.review-form .form-group {
9790  margin-bottom: 20px;
9791}
9792
9793.review-form .form-label {
9794  display: block;
9795  font-size: 14px;
9796  font-weight: 600;
9797  color: #374151;
9798  margin-bottom: 8px;
9799}
9800
9801.review-form .form-control {
9802  width: 100%;
9803  padding: 12px;
9804  border: 1px solid #d1d5db;
9805  border-radius: 8px;
9806  font-size: 14px;
9807  font-family: inherit;
9808  transition: border-color 0.2s;
9809}
9810
9811.review-form .form-control:focus {
9812  outline: none;
9813  border-color: #2563eb;
9814  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
9815}
9816
9817.review-form .form-hint {
9818  font-size: 13px;
9819  color: #6b7280;
9820  margin-top: 8px;
9821}
9822
9823/* Image Upload */
9824.btn-upload-trigger {
9825  display: inline-flex;
9826  align-items: center;
9827  gap: 8px;
9828  padding: 10px 16px;
9829  background: #f9fafb;
9830  border: 2px dashed #d1d5db;
9831  border-radius: 8px;
9832  color: #374151;
9833  cursor: pointer;
9834  font-size: 14px;
9835  font-weight: 500;
9836  transition: all 0.2s;
9837}
9838
9839.btn-upload-trigger:hover {
9840  background: #f3f4f6;
9841  border-color: #2563eb;
9842  color: #2563eb;
9843}
9844
9845.image-preview-grid {
9846  display: grid;
9847  grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
9848  gap: 12px;
9849  margin-top: 16px;
9850}
9851
9852.image-preview-item {
9853  position: relative;
9854  aspect-ratio: 1;
9855  border-radius: 8px;
9856  overflow: hidden;
9857  border: 1px solid #e5e7eb;
9858}
9859
9860.image-preview-item img {
9861  width: 100%;
9862  height: 100%;
9863  object-fit: cover;
9864}
9865
9866.remove-image {
9867  position: absolute;
9868  top: 4px;
9869  right: 4px;
9870  background: rgba(0, 0, 0, 0.7);
9871  border: none;
9872  color: white;
9873  width: 24px;
9874  height: 24px;
9875  border-radius: 50%;
9876  cursor: pointer;
9877  display: flex;
9878  align-items: center;
9879  justify-content: center;
9880  transition: background 0.2s;
9881}
9882
9883.remove-image:hover {
9884  background: rgba(0, 0, 0, 0.9);
9885}
9886
9887/* Point Reward Info */
9888.point-reward-info {
9889  background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
9890  border: 2px solid #fbbf24;
9891  border-radius: 12px;
9892  padding: 16px;
9893  margin: 20px 0;
9894}
9895
9896.pri-header {
9897  display: flex;
9898  align-items: center;
9899  gap: 8px;
9900  font-size: 16px;
9901  font-weight: 700;
9902  color: #92400e;
9903  margin-bottom: 12px;
9904}
9905
9906.pri-list {
9907  list-style: none;
9908  padding: 0;
9909  margin: 0 0 12px 0;
9910}
9911
9912.pri-list li {
9913  padding: 6px 0;
9914  font-size: 14px;
9915  color: #78350f;
9916  transition: opacity 0.2s;
9917}
9918
9919.pri-total {
9920  padding-top: 12px;
9921  border-top: 2px solid rgba(251, 191, 36, 0.3);
9922  font-size: 16px;
9923  color: #92400e;
9924  font-weight: 600;
9925}
9926
9927.form-error {
9928  background: #fee2e2;
9929  border: 1px solid #f87171;
9930  color: #991b1b;
9931  padding: 12px;
9932  border-radius: 8px;
9933  font-size: 14px;
9934  margin-top: 16px;
9935}
9936
9937/* Buttons */
9938.review-form .btn {
9939  padding: 12px 24px;
9940  border: none;
9941  border-radius: 8px;
9942  font-size: 15px;
9943  font-weight: 600;
9944  cursor: pointer;
9945  transition: all 0.2s;
9946}
9947
9948.review-form .btn-primary {
9949  background: #2563eb;
9950  color: white;
9951}
9952
9953.review-form .btn-primary:hover {
9954  background: #1d4ed8;
9955}
9956
9957.review-form .btn-primary:disabled {
9958  background: #94a3b8;
9959  cursor: not-allowed;
9960}
9961
9962.review-form .btn-secondary {
9963  background: #f1f5f9;
9964  color: #64748b;
9965}
9966
9967.review-form .btn-secondary:hover {
9968  background: #e2e8f0;
9969  color: #475569;
9970}
9971
9972@media (max-width: 768px) {
9973    #review-modal-overlay { padding: 0; }
9974    .review-modal { max-width: 100%; width: 100% !important; }
9975}
9976
9977/* ===================================
9978   POINT SUMMARY GRID (Profile Page)
9979   =================================== */
9980
9981.point-summary-grid {
9982  display: grid;
9983  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
9984  gap: 12px;
9985  margin-bottom: 32px;
9986}
9987
9988.point-summary-card {
9989  background: linear-gradient(135deg, #f9fafb 0%, #ffffff 100%);
9990  border: 1px solid #e5e7eb;
9991  border-radius: 12px;
9992  padding: 16px 20px;
9993  display: flex;
9994  align-items: center;
9995  justify-content: space-between;
9996  gap: 12px;
9997  transition: all 0.2s;
9998}
9999
10000.point-summary-card:hover {
10001  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
10002  transform: translateY(-2px);
10003}
10004
10005.point-summary-card .psc-left {
10006  display: flex;
10007  flex-direction: column;
10008  gap: 2px;
10009}
10010
10011.point-summary-card .psc-icon {
10012  font-size: 24px;
10013  margin-bottom: 4px;
10014}
10015
10016.point-summary-card .psc-label {
10017  font-size: 13px;
10018  color: #6b7280;
10019  font-weight: 500;
10020  text-transform: uppercase;
10021  letter-spacing: 0.5px;
10022}
10023
10024.point-summary-card .psc-value {
10025  font-size: 26px;
10026  font-weight: 700;
10027  color: #111827;
10028  flex-shrink: 0;
10029}
10030
10031/* Point History Section */
10032.point-history-section {
10033  margin-top: 32px;
10034}
10035
10036.point-history-section h3 {
10037  font-size: 18px;
10038  font-weight: 600;
10039  color: #111827;
10040  margin-bottom: 16px;
10041}
10042
10043.point-history-list {
10044  display: flex;
10045  flex-direction: column;
10046  gap: 12px;
10047}
10048
10049.point-history-item {
10050  display: grid;
10051  grid-template-columns: 40px 1fr auto;
10052  gap: 16px;
10053  padding: 16px;
10054  background: white;
10055  border: 1px solid #e5e7eb;
10056  border-radius: 10px;
10057  align-items: center;
10058  transition: all 0.2s;
10059}
10060
10061.point-history-item:hover {
10062  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
10063}
10064
10065.point-history-item.credit {
10066  border-left: 3px solid #10b981;
10067}
10068
10069.point-history-item.debit {
10070  border-left: 3px solid #f59e0b;
10071}
10072
10073.point-history-item .phi-icon {
10074  width: 40px;
10075  height: 40px;
10076  display: flex;
10077  align-items: center;
10078  justify-content: center;
10079  font-size: 20px;
10080  border-radius: 50%;
10081  background: #f9fafb;
10082}
10083
10084.point-history-item.credit .phi-icon {
10085  background: #d1fae5;
10086}
10087
10088.point-history-item.debit .phi-icon {
10089  background: #fef3c7;
10090}
10091
10092.point-history-item .phi-details {
10093  display: flex;
10094  flex-direction: column;
10095  gap: 4px;
10096}
10097
10098.point-history-item .phi-desc {
10099  font-size: 15px;
10100  font-weight: 500;
10101  color: #111827;
10102}
10103
10104.point-history-item .phi-date {
10105  font-size: 13px;
10106  color: #9ca3af;
10107}
10108
10109.point-history-item .phi-amount {
10110  font-size: 18px;
10111  font-weight: 700;
10112}
10113
10114.point-history-item .phi-amount.credit {
10115  color: #10b981;
10116}
10117
10118.point-history-item .phi-amount.debit {
10119  color: #f59e0b;
10120}
10121
10122@media (max-width: 640px) {
10123  .point-summary-grid {
10124    grid-template-columns: 1fr;
10125  }
10126
10127  .point-summary-card .psc-value {
10128    font-size: 22px;
10129  }
10130
10131  .point-history-item {
10132    grid-template-columns: 36px 1fr;
10133    gap: 12px;
10134  }
10135
10136  .point-history-item .phi-amount {
10137    grid-column: 2;
10138    text-align: right;
10139    margin-top: 8px;
10140  }
10141}
10142
10143/* ── Pending points: compact widget indicator ── */
10144.pbc-pending {
10145  font-size: 12px;
10146  opacity: 0.85;
10147  margin-top: 4px;
10148  color: #fde68a;
10149}
10150
10151/* ── Pending points: summary card ── */
10152.point-summary-card.psc-pending-card {
10153  background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);
10154  border-color: #fcd34d;
10155}
10156
10157.point-summary-card.psc-pending-card .psc-value {
10158  color: #b45309;
10159}
10160
10161.point-summary-card .psc-sub {
10162  font-size: 11px;
10163  color: #9ca3af;
10164  margin-top: 4px;
10165}
10166
10167/* ── Pending points: history item ── */
10168.point-history-item.phi-pending {
10169  opacity: 0.75;
10170  border-left-color: #fcd34d;
10171  background: #fffbeb;
10172}
10173
10174.point-history-item.phi-pending .phi-icon {
10175  background: #fef3c7;
10176}
10177
10178.phi-pending-badge {
10179  display: inline-block;
10180  font-size: 11px;
10181  font-weight: 600;
10182  padding: 2px 7px;
10183  border-radius: 10px;
10184  background: #fef3c7;
10185  color: #92400e;
10186  border: 1px solid #fcd34d;
10187  margin-left: 8px;
10188  vertical-align: middle;
10189}
10190
10191/* ── Reversed points: history item ── */
10192.point-history-item.phi-reversed {
10193  opacity: 0.55;
10194  border-left-color: #d1d5db;
10195  background: #f9fafb;
10196}
10197
10198.point-history-item.phi-reversed .phi-icon {
10199  background: #f3f4f6;
10200}
10201
10202.point-history-item.phi-reversed .phi-amount {
10203  text-decoration: line-through;
10204  color: #9ca3af !important;
10205}
10206
10207.phi-reversed-badge {
10208  display: inline-block;
10209  font-size: 11px;
10210  font-weight: 600;
10211  padding: 2px 7px;
10212  border-radius: 10px;
10213  background: #fee2e2;
10214  color: #991b1b;
10215  border: 1px solid #fca5a5;
10216  margin-left: 8px;
10217  vertical-align: middle;
10218}
10219
10220</style>
10221
10222</head>
10223<body>
10224<header class="topbar">
10225  <div class="container topbar-inner">
10226    <div class="topbar-left">
10227      BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler
10228    </div>
10229
10230    <div class="topbar-right">
10231      <a href="https://mgsco.org/">Utama</a>
10232      <a href="https://mgsco.org/">Koleksi Game</a>
10233      <a href="https://mgsco.org/">Game Terpopuler</a>
10234      <a href="https://mgsco.org/boykita/">Daftar BOYTOTO</a>
10235      <a href="https://mgsco.org/boykita/">Login BOYTOTO</a>
10236    </div>
10237  </div>
10238</header>
10239<nav class="navbar">
10240  <nav class="navbar-logo" style="width: 100%;">
10241    <div class="container nav-inner" style="justify-items: center; justify-content: center;">
10242        <a href="https://mgsco.org/" class="logo-mobile" title="BOYTOTO">
10243          <img src="https://ik.imagekit.io/boytoto/logo.webp" alt="BOYTOTO" height="40">
10244        </a>
10245    </div>
10246</nav>
10247  <div class="container nav-inner">
10248    <a href="https://mgsco.org/" class="logo" title="BOYTOTO">
10249      <img src="https://ik.imagekit.io/boytoto/logo.webp" alt="BOYTOTO" height="40">
10250    </a>
10251
10252
10253
10254    <form class="search" method="get" action="#">
10255      <input type="hidden" name="page" value="produk">
10256
10257      <!-- Search Icon (Desktop) -->
10258      <button type="submit" class="search-icon-btn" id="searchBtn" aria-label="Cari">
10259        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
10260          <circle cx="11" cy="11" r="8"/>
10261          <path d="m21 21-4.35-4.35"/>
10262        </svg>
10263      </button>
10264
10265      <input type="text" name="q" id="searchInput" placeholder="Cari BOYTOTO Di Google"
10266             value="" autocomplete="off"> 
10267
10268      <div class="search-suggest" id="searchSuggest" hidden></div>
10269    </form>
10270
10271    <div class="nav-actions">
10272      <!--<button class="icon-btn" id="notifBtn" title="Notifikasi">🔔</button>-->
10273      <button class="icon-btn cart-indicator" id="cartBtn" title="Catatan Shio">
10274        🛒 <span id="cartCount" class="badge" data-role="cart-badge" aria-live="polite">0</span>
10275      </button>
10276
10277      <button class="icon-btn compare-indicator" id="compareBtn" title="Perbandingan">
10278        ⚖️ <span id="compareCount" class="badge" data-role="compare-badge" aria-live="polite">0</span>
10279      </button>
10280
10281      <!-- Dynamic login/logout button -->
10282            <button class="login-btn" id="loginBtn" aria-label="Masuk">
10283        <span class="login-text">Masuk</span>
10284        <span class="login-ico" aria-hidden="true">😊</span>
10285      </button>
10286          </div>
10287  </div>
10288
10289  <div class="container" style="display: flex; align-items: center;">
10290
10291
10292
10293      <div class="mega-cats-wrapper">
10294        <div class="mega-cats container" id="megaCatsScroll" style="padding-left: 5px !important;">
10295                      <a class="" href="https://mgsco.org/">
10296              BOYTOTO           </a>
10297                      <a class="" href="https://mgsco.org/">
10298              Link BOYTOTO            </a>
10299                      <a class="" href="https://mgsco.org/">
10300              Game Online            </a>
10301                      <a class="" href="https://mgsco.org/">
10302              Pelopor Terpercaya           </a>
10303                      <a class="" href="https://mgsco.org/">
10304              Game Resmi           </a>
10305                      <a class="" href="https://mgsco.org/">
10306              Pelopor Gacor           </a>
10307                  </div>
10308        <div class="mega-cats-fade" id="megaCatsFade"></div>
10309      </div>
10310  </div>
10311</nav>
10312
10313<!-- Panel Mega Menu - Outside navbar for full-screen -->
10314<div class="mm2-panel" data-mm2="panel" role="dialog" aria-label="Kategori">
10315  <div class="mm2-view is-active" data-mm2="top"></div>
10316  <div class="mm2-view" data-mm2="detail"></div>
10317</div>
10318
10319<!-- Backdrop - Outside navbar for full-screen -->
10320<div class="mm2-backdrop" data-mm2="backdrop"></div>
10321
10322<!-- Mobile Bottom Navigation -->
10323<nav class="bottom-nav">
10324  <div class="bottom-nav-inner">
10325    <!-- Home -->
10326    <a href="/" class="bottom-nav-item" id="bottomHomeBtn" data-page="home">
10327      <div class="bottom-nav-item-icon">
10328        <!-- Outlined icon (default) -->
10329        <svg class="icon-outlined" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
10330          <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
10331          <polyline points="9 22 9 12 15 12 15 22"/>
10332        </svg>
10333        <!-- Filled icon (active) -->
10334        <svg class="icon-filled" viewBox="0 0 24 24" fill="currentColor">
10335          <path d="M11.47 3.84a.75.75 0 011.06 0l8.69 8.69a.75.75 0 010 1.06l-.03.03a.75.75 0 01-1.06 0l-8.16-8.16-8.16 8.16a.75.75 0 01-1.06 0l-.03-.03a.75.75 0 010-1.06l8.69-8.69z"/>
10336          <path d="M12 5.5l-7.5 7.5v8.25c0 .414.336.75.75.75h3.75v-6h6v6h3.75a.75.75 0 00.75-.75V13l-7.5-7.5z"/>
10337        </svg>
10338      </div>
10339      <span class="bottom-nav-item-label">Home</span>
10340    </a>
10341
10342    <!-- Kategori -->
10343    <button class="bottom-nav-item" id="bottomCategoryBtn" data-mm2="trigger" aria-label="Kategori">
10344      <div class="bottom-nav-item-icon">
10345        <!-- Outlined icon (default) -->
10346        <svg class="icon-outlined" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
10347          <line x1="3" y1="12" x2="21" y2="12"/>
10348          <line x1="3" y1="6" x2="21" y2="6"/>
10349          <line x1="3" y1="18" x2="21" y2="18"/>
10350        </svg>
10351        <!-- Filled icon (active) -->
10352        <svg class="icon-filled" viewBox="0 0 24 24" fill="currentColor">
10353          <path d="M3 5.25A.75.75 0 013.75 4.5h16.5a.75.75 0 010 1.5H3.75A.75.75 0 013 5.25zM3 12a.75.75 0 01.75-.75h16.5a.75.75 0 010 1.5H3.75A.75.75 0 013 12zm0 6.75a.75.75 0 01.75-.75h16.5a.75.75 0 010 1.5H3.75a.75.75 0 01-.75-.75z"/>
10354        </svg>
10355      </div>
10356      <span class="bottom-nav-item-label">Kategori</span>
10357    </button>
10358
10359    <!-- Keranjang -->
10360    <button class="bottom-nav-item" id="bottomCartBtn">
10361      <div class="bottom-nav-item-icon">
10362        <!-- Outlined icon (default) -->
10363        <svg class="icon-outlined" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
10364          <circle cx="9" cy="21" r="1"/>
10365          <circle cx="20" cy="21" r="1"/>
10366          <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/>
10367        </svg>
10368        <!-- Filled icon (active) -->
10369        <svg class="icon-filled" viewBox="0 0 24 24" fill="currentColor">
10370          <path d="M2.25 2.25a.75.75 0 000 1.5h1.386c.17 0 .318.114.362.278l2.558 9.592a3.752 3.752 0 00-2.806 3.63c0 .414.336.75.75.75h15.75a.75.75 0 000-1.5H5.378A2.25 2.25 0 017.5 15h11.218a.75.75 0 00.674-.421 60.358 60.358 0 002.96-7.228.75.75 0 00-.525-.965A60.864 60.864 0 005.68 4.509l-.232-.867A1.875 1.875 0 003.636 2.25H2.25zM3.75 20.25a1.5 1.5 0 113 0 1.5 1.5 0 01-3 0zM16.5 20.25a1.5 1.5 0 113 0 1.5 1.5 0 01-3 0z"/>
10371        </svg>
10372        <span class="bottom-nav-item-badge" id="bottomCartCount">0</span>
10373      </div>
10374      <span class="bottom-nav-item-label">Catatan Shio</span>
10375    </button>
10376
10377    <!-- Compare -->
10378    <button class="bottom-nav-item" id="bottomCompareBtn">
10379      <div class="bottom-nav-item-icon">
10380        <!-- Outlined icon (default) -->
10381        <svg class="icon-outlined" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
10382          <polyline points="17 11 21 7 17 3"/>
10383          <path d="M21 7H9"/>
10384          <polyline points="7 21 3 17 7 13"/>
10385          <path d="M15 17H3"/>
10386        </svg>
10387        <!-- Filled icon (active) -->
10388        <svg class="icon-filled" viewBox="0 0 24 24" fill="currentColor">
10389          <path d="M21.75 6.75a.75.75 0 00-.75-.75H9a.75.75 0 000 1.5h10.19L15.22 11.47a.75.75 0 001.06 1.06l4.5-4.5a.75.75 0 00.22-.53.75.75 0 00-.22-.53l-.03-.03zM8.78 12.47L4.28 16.97a.75.75 0 000 1.06l4.5 4.5a.75.75 0 001.06-1.06L6.06 18H15a.75.75 0 000-1.5H4.81l3.97-3.97a.75.75 0 00-1.06-1.06z"/>
10390        </svg>
10391        <span class="bottom-nav-item-badge" id="bottomCompareCount" style="display:none;">0</span>
10392      </div>
10393      <span class="bottom-nav-item-label">Compare</span>
10394    </button>
10395
10396    <!-- Chat - Always show, check login in JavaScript -->
10397    <button class="bottom-nav-item" id="bottomChatBtn" data-page="chat">
10398      <div class="bottom-nav-item-icon">
10399        <!-- Outlined icon (default) -->
10400        <svg class="icon-outlined" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" opacity="0.85">
10401          <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
10402        </svg>
10403        <!-- Filled icon (active) -->
10404        <svg class="icon-filled" viewBox="0 0 24 24" fill="currentColor">
10405          <path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/>
10406        </svg>
10407        <span class="bottom-nav-item-badge" id="bottomChatCount" style="display:none;">0</span>
10408      </div>
10409      <span class="bottom-nav-item-label">Chat</span>
10410    </button>
10411
10412    <!-- Akun -->
10413        <button class="bottom-nav-item" id="bottomLoginBtn">
10414      <div class="bottom-nav-item-icon">
10415        <!-- Outlined icon (default) -->
10416        <svg class="icon-outlined" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
10417          <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
10418          <circle cx="12" cy="7" r="4"/>
10419        </svg>
10420        <!-- Filled icon (active) -->
10421        <svg class="icon-filled" viewBox="0 0 24 24" fill="currentColor">
10422          <path fill-rule="evenodd" d="M7.5 6a4.5 4.5 0 119 0 4.5 4.5 0 01-9 0zM3.751 20.105a8.25 8.25 0 0116.498 0 .75.75 0 01-.437.695A18.683 18.683 0 0112 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 01-.437-.695z" clip-rule="evenodd"/>
10423        </svg>
10424      </div>
10425      <span class="bottom-nav-item-label">Login</span>
10426    </button>
10427      </div>
10428</nav>
10429
10430<!-- Mobile User Menu Bottom Sheet -->
10431
10432<script nonce="se3iyzEHMc9FQEB4wSyd0w==">
10433(function(){
10434  const btn = document.getElementById('megaMoreBtn');
10435  const dd  = document.getElementById('megaMoreDropdown');
10436  if(!btn || !dd) return;
10437
10438  function openDD(){
10439    dd.hidden = false;
10440    btn.setAttribute('aria-expanded','true');
10441    document.addEventListener('click', outside, {capture:true});
10442    document.addEventListener('keydown', esc);
10443  }
10444  function closeDD(){
10445    if(dd.hidden) return;
10446    dd.hidden = true;
10447    btn.setAttribute('aria-expanded','false');
10448    document.removeEventListener('click', outside, {capture:true});
10449    document.removeEventListener('keydown', esc);
10450  }
10451  function toggle(){ dd.hidden ? openDD() : closeDD(); }
10452  function outside(e){
10453    if(!dd.contains(e.target) && e.target !== btn){
10454      closeDD();
10455    }
10456  }
10457  function esc(e){
10458    if(e.key === 'Escape'){
10459      closeDD();
10460      btn.focus();
10461    }
10462  }
10463  btn.addEventListener('click', toggle);
10464})();
10465
10466// ========== HORIZONTAL SCROLL FADE INDICATOR ==========
10467(function(){
10468  const megaCats = document.getElementById('megaCatsScroll');
10469  const fade = document.getElementById('megaCatsFade');
10470  if (!megaCats || !fade) return;
10471
10472  function updateFade() {
10473    const scrollLeft = megaCats.scrollLeft;
10474    const scrollWidth = megaCats.scrollWidth;
10475    const clientWidth = megaCats.clientWidth;
10476    const maxScroll = scrollWidth - clientWidth;
10477
10478    // Hide fade when scrolled to end (within 10px tolerance)
10479    if (maxScroll - scrollLeft <= 10) {
10480      fade.style.opacity = '0';
10481    } else {
10482      fade.style.opacity = '1';
10483    }
10484  }
10485
10486  // Update on scroll
10487  megaCats.addEventListener('scroll', updateFade, { passive: true });
10488
10489  // Update on resize
10490  window.addEventListener('resize', updateFade, { passive: true });
10491
10492  // Initial check
10493  setTimeout(updateFade, 100);
10494})();
10495
10496document.addEventListener('DOMContentLoaded', function () {
10497  var input = document.getElementById('searchInput');
10498  if (!input) return;
10499  input.addEventListener('keydown', function (e) {
10500    if (e.key === 'Enter') {
10501      e.preventDefault();
10502      var form = input.form;
10503      if (!form) return;
10504      if (typeof form.requestSubmit === 'function') form.requestSubmit();
10505      else form.submit();
10506    }
10507  });
10508});
10509
10510
10511
10512(function(){
10513  const cartBtn = document.getElementById('cartBtn');
10514  if (cartBtn) {
10515    cartBtn.addEventListener('click', function(e) {
10516      e.preventDefault();
10517
10518      const checkLogin = () => {
10519        return window.isUserLoggedIn ? window.isUserLoggedIn() : false;
10520      };
10521
10522      if (checkLogin()) {
10523        window.location.href = '/keranjang';
10524      } else {
10525        showCartLoginModal();
10526      }
10527    });
10528  }
10529
10530  const loginBtn = document.getElementById('loginBtn');
10531  if (loginBtn) {
10532    loginBtn.addEventListener('click', function(e) {
10533      e.preventDefault();
10534      window.location.href = 'https://mgsco.org/';
10535    });
10536  }
10537
10538  const userMenuBtn = document.getElementById('userMenuBtn');
10539  const userDropdown = document.getElementById('userDropdown');
10540
10541  if (userMenuBtn && userDropdown) {
10542    function openDropdown() {
10543      userDropdown.hidden = false;
10544      userMenuBtn.setAttribute('aria-expanded', 'true');
10545      document.addEventListener('click', closeOnOutside, { capture: true });
10546      document.addEventListener('keydown', closeOnEscape);
10547    }
10548
10549    function closeDropdown() {
10550      if (userDropdown.hidden) return;
10551      userDropdown.hidden = true;
10552      userMenuBtn.setAttribute('aria-expanded', 'false');
10553      document.removeEventListener('click', closeOnOutside, { capture: true });
10554      document.removeEventListener('keydown', closeOnEscape);
10555    }
10556
10557    function closeOnOutside(e) {
10558      if (!userDropdown.contains(e.target) && e.target !== userMenuBtn) {
10559        closeDropdown();
10560      }
10561    }
10562
10563    function closeOnEscape(e) {
10564      if (e.key === 'Escape') {
10565        closeDropdown();
10566        userMenuBtn.focus();
10567      }
10568    }
10569
10570    userMenuBtn.addEventListener('click', function(e) {
10571      e.preventDefault();
10572      userDropdown.hidden ? openDropdown() : closeDropdown();
10573    });
10574
10575    // Close dropdown when clicking profile links
10576    userDropdown.querySelectorAll('[data-close-dropdown]').forEach(link => {
10577      link.addEventListener('click', function() {
10578        closeDropdown();
10579      });
10580    });
10581  }
10582
10583  // Bottom nav profile button - toggle mobile bottom sheet
10584  const bottomProfileBtn = document.getElementById('bottomProfileBtn');
10585  const mobileUserSheet = document.getElementById('mobileUserSheet');
10586  const mobileUserSheetBackdrop = document.getElementById('mobileUserSheetBackdrop');
10587
10588  if (bottomProfileBtn && mobileUserSheet) {
10589    function openMobileSheet() {
10590      mobileUserSheet.hidden = false;
10591      document.body.style.overflow = 'hidden';
10592      // Trigger animation
10593      requestAnimationFrame(() => {
10594        mobileUserSheet.classList.add('active');
10595      });
10596    }
10597
10598    function closeMobileSheet() {
10599      mobileUserSheet.classList.remove('active');
10600      document.body.style.overflow = '';
10601      setTimeout(() => {
10602        mobileUserSheet.hidden = true;
10603      }, 300);
10604    }
10605
10606    bottomProfileBtn.addEventListener('click', function(e) {
10607      e.preventDefault();
10608      openMobileSheet();
10609    });
10610
10611    if (mobileUserSheetBackdrop) {
10612      mobileUserSheetBackdrop.addEventListener('click', closeMobileSheet);
10613    }
10614
10615    // Close on escape key
10616    document.addEventListener('keydown', function(e) {
10617      if (e.key === 'Escape' && !mobileUserSheet.hidden) {
10618        closeMobileSheet();
10619      }
10620    });
10621
10622    // Close sheet when clicking profile links
10623    mobileUserSheet.querySelectorAll('[data-close-sheet]').forEach(link => {
10624      link.addEventListener('click', function() {
10625        closeMobileSheet();
10626      });
10627    });
10628  }
10629
10630  // Handle Google avatar error in bottom-nav (fallback to initials)
10631  const bottomNavAvatar = document.querySelector('.bottom-nav-avatar-img');
10632  if (bottomNavAvatar) {
10633    bottomNavAvatar.addEventListener('error', function() {
10634      const initials = this.getAttribute('data-fallback-initials') || 'U';
10635      const initialsDiv = document.createElement('div');
10636      initialsDiv.className = 'bottom-nav-avatar-initials';
10637      initialsDiv.textContent = initials;
10638      this.parentNode.replaceChild(initialsDiv, this);
10639    });
10640  }
10641
10642  // Mobile logout button
10643  const mobileLogoutBtn = document.getElementById('mobileLogoutBtn');
10644  if (mobileLogoutBtn) {
10645    mobileLogoutBtn.addEventListener('click', async function(e) {
10646      e.preventDefault();
10647
10648      // Check push notification before logout
10649      if (window.ChatNotification && typeof window.ChatNotification.confirmLogoutWithPushNotification === 'function') {
10650        const confirmLogout = await window.ChatNotification.confirmLogoutWithPushNotification();
10651        if (!confirmLogout) {
10652          return; // User cancelled logout
10653        }
10654      }
10655
10656      const getCsrfToken = () => document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
10657
10658      try {
10659        const response = await fetch('/auth/logout', {
10660          method: 'POST',
10661          credentials: 'same-origin',
10662          headers: {
10663            'Content-Type': 'application/json',
10664            'X-CSRF-Token': getCsrfToken()
10665          },
10666          body: JSON.stringify({
10667            csrf_token: getCsrfToken()
10668          })
10669        });
10670
10671        if (response.ok) {
10672          if (window.guestCart && typeof window.guestCart.clear === 'function') {
10673            window.guestCart.clear();
10674          }
10675          window.location.href = '/login?logged_out=1';
10676        } else {
10677          console.error('Logout failed');
10678          window.location.href = '/logout';
10679        }
10680      } catch (error) {
10681        console.error('Logout error:', error);
10682        window.location.href = '/logout';
10683      }
10684    });
10685  }
10686
10687  const logoutBtn = document.getElementById('logoutBtn');
10688  if (logoutBtn) {
10689    logoutBtn.addEventListener('click', async function(e) {
10690      e.preventDefault();
10691
10692      // Check push notification before logout
10693      if (window.ChatNotification && typeof window.ChatNotification.confirmLogoutWithPushNotification === 'function') {
10694        const confirmLogout = await window.ChatNotification.confirmLogoutWithPushNotification();
10695        if (!confirmLogout) {
10696          return; // User cancelled logout
10697        }
10698      }
10699
10700      const getCsrfToken = () => document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
10701
10702      try {
10703        const response = await fetch('/auth/logout', {
10704          method: 'POST',
10705          credentials: 'same-origin',
10706          headers: {
10707            'Content-Type': 'application/json',
10708            'X-CSRF-Token': getCsrfToken()
10709          },
10710          body: JSON.stringify({
10711            csrf_token: getCsrfToken()
10712          })
10713        });
10714
10715        if (response.ok) {
10716          if (window.guestCart && typeof window.guestCart.clear === 'function') {
10717            window.guestCart.clear();
10718          }
10719
10720          window.location.href = '/login?logged_out=1';
10721        } else {
10722          console.error('Logout failed');
10723          window.location.href = '/logout';
10724        }
10725      } catch (error) {
10726        console.error('Logout error:', error);
10727        window.location.href = '/logout';
10728      }
10729    });
10730  }
10731
10732  const wishlistBtn = document.getElementById('wishlistBtn');
10733  const notifBtn = document.getElementById('notifBtn');
10734
10735  if (wishlistBtn) {
10736    wishlistBtn.addEventListener('click', function(e) {
10737      e.preventDefault();
10738      alert('Fitur wishlist akan segera tersedia!');
10739    });
10740  }
10741
10742  if (notifBtn) {
10743    notifBtn.addEventListener('click', function(e) {
10744      e.preventDefault();
10745      alert('Fitur notifikasi akan segera tersedia!');
10746    });
10747  }
10748})();
10749
10750// ========== MOBILE BOTTOM NAVIGATION ==========
10751(function initBottomNav() {
10752  // Sync cart badge with bottom nav
10753  function syncBottomCartBadge() {
10754    const mainBadge = document.getElementById('cartCount');
10755    const bottomBadge = document.getElementById('bottomCartCount');
10756    if (mainBadge && bottomBadge) {
10757      const count = mainBadge.textContent;
10758      bottomBadge.textContent = count;
10759      bottomBadge.style.display = parseInt(count) > 0 ? 'flex' : 'none';
10760    }
10761  }
10762
10763  // Sync compare badge with bottom nav
10764  function syncBottomCompareBadge() {
10765    const mainBadge = document.getElementById('compareCount');
10766    const bottomBadge = document.getElementById('bottomCompareCount');
10767    if (mainBadge && bottomBadge) {
10768      const count = mainBadge.textContent;
10769      bottomBadge.textContent = count;
10770      bottomBadge.style.display = parseInt(count) > 0 ? 'flex' : 'none';
10771    }
10772  }
10773
10774  // Initial sync
10775  syncBottomCartBadge();
10776  syncBottomCompareBadge();
10777
10778  // Observe badge changes
10779  const observer = new MutationObserver(function() {
10780    syncBottomCartBadge();
10781    syncBottomCompareBadge();
10782  });
10783
10784  const cartBadge = document.getElementById('cartCount');
10785  const compareBadge = document.getElementById('compareCount');
10786
10787  if (cartBadge) observer.observe(cartBadge, { childList: true, characterData: true, subtree: true });
10788  if (compareBadge) observer.observe(compareBadge, { childList: true, characterData: true, subtree: true });
10789
10790  // Bottom Cart Button
10791  const bottomCartBtn = document.getElementById('bottomCartBtn');
10792  if (bottomCartBtn) {
10793    bottomCartBtn.addEventListener('click', function(e) {
10794      e.preventDefault();
10795
10796      const checkLogin = () => {
10797        return window.isUserLoggedIn ? window.isUserLoggedIn() : false;
10798      };
10799
10800      if (checkLogin()) {
10801        window.location.href = '/keranjang';
10802      } else {
10803        showCartLoginModal();
10804      }
10805    });
10806  }
10807
10808  // Bottom Compare Button
10809  const bottomCompareBtn = document.getElementById('bottomCompareBtn');
10810  if (bottomCompareBtn) {
10811    bottomCompareBtn.addEventListener('click', function(e) {
10812      e.preventDefault();
10813
10814      // Close chat widget if open
10815      if (window.plazaChat && window.plazaChat.isWidgetOpen) {
10816        window.plazaChat.closeWidget();
10817      }
10818
10819      const mainCompareBtn = document.getElementById('compareBtn');
10820      if (mainCompareBtn) {
10821        mainCompareBtn.click();
10822      }
10823    });
10824  }
10825
10826  // Bottom Category Button - triggers mega menu
10827  const bottomCategoryBtn = document.getElementById('bottomCategoryBtn');
10828  if (bottomCategoryBtn) {
10829    bottomCategoryBtn.addEventListener('click', function(e) {
10830      e.preventDefault();
10831      // Trigger desktop mega menu
10832      const desktopTrigger = document.querySelector('.mm2-trigger:not(#bottomCategoryBtn)');
10833      if (desktopTrigger) {
10834        desktopTrigger.click();
10835      }
10836    });
10837  }
10838
10839  // Bottom Login Button
10840  const bottomLoginBtn = document.getElementById('bottomLoginBtn');
10841  if (bottomLoginBtn) {
10842    bottomLoginBtn.addEventListener('click', function(e) {
10843      e.preventDefault();
10844      window.location.href = '/login';
10845    });
10846  }
10847
10848  // Bottom Chat Button
10849  const bottomChatBtn = document.getElementById('bottomChatBtn');
10850  if (bottomChatBtn) {
10851    bottomChatBtn.addEventListener('click', function(e) {
10852      e.preventDefault();
10853
10854      // Trigger chat widget (try multiple times if needed)
10855      const triggerChat = () => {
10856        const chatToggle = document.getElementById('chat-toggle-btn');
10857        if (chatToggle) {
10858          chatToggle.click();
10859          return true;
10860        }
10861        return false;
10862      };
10863
10864      // Immediate attempt
10865      if (!triggerChat()) {
10866        // Retry after 100ms if not found
10867        setTimeout(() => {
10868          if (!triggerChat()) {
10869            // Last resort: manually show popup
10870            const popup = document.getElementById('chat-popup');
10871            if (popup) {
10872              popup.style.display = 'block';
10873            }
10874          }
10875        }, 100);
10876      }
10877    });
10878  }
10879
10880  // Sync chat unread badge
10881  function syncBottomChatBadge() {
10882    const chatBadge = document.getElementById('chat-unread-badge');
10883    const bottomChatBadge = document.getElementById('bottomChatCount');
10884    if (chatBadge && bottomChatBadge) {
10885      const count = chatBadge.textContent;
10886      bottomChatBadge.textContent = count;
10887      bottomChatBadge.style.display = chatBadge.style.display;
10888    }
10889  }
10890
10891  // Observe chat badge changes
10892  const chatBadge = document.getElementById('chat-unread-badge');
10893  if (chatBadge) {
10894    observer.observe(chatBadge, { attributes: true, childList: true, characterData: true, subtree: true });
10895    syncBottomChatBadge();
10896  }
10897
10898  // Active state management based on current page
10899  function updateActiveState() {
10900    const currentPath = window.location.pathname;
10901    const bottomNavItems = document.querySelectorAll('.bottom-nav-item');
10902
10903    bottomNavItems.forEach(item => {
10904      item.classList.remove('active');
10905
10906      const itemId = item.id;
10907      const page = item.getAttribute('data-page');
10908      const href = item.getAttribute('href');
10909
10910      // Check by ID for specific pages
10911      if (itemId === 'bottomHomeBtn' && currentPath === '/') {
10912        item.classList.add('active');
10913      } else if (itemId === 'bottomCartBtn' && (currentPath === '/keranjang' || currentPath === '/cart')) {
10914        item.classList.add('active');
10915      } else if (itemId === 'bottomProfileBtn' && currentPath.startsWith('/profile')) {
10916        item.classList.add('active');
10917      } else if (itemId === 'bottomCategoryBtn' && currentPath.startsWith('/kategori')) {
10918        item.classList.add('active');
10919      } else if (itemId === 'bottomCompareBtn' && currentPath === '/compare') {
10920        item.classList.add('active');
10921      } else if (itemId === 'bottomChatBtn' && currentPath === '/chat') {
10922        item.classList.add('active');
10923      }
10924      // Fallback: check by data-page attribute
10925      else if (page === 'home' && currentPath === '/') {
10926        item.classList.add('active');
10927      } else if (page === 'profile' && currentPath.startsWith('/profile')) {
10928        item.classList.add('active');
10929      }
10930      // Fallback: check by href
10931      else if (href && currentPath === href) {
10932        item.classList.add('active');
10933      }
10934    });
10935  }
10936
10937  // Update active state on load
10938  updateActiveState();
10939
10940  // Bottom nav always visible - auto-hide disabled for better UX
10941  // const bottomNav = document.querySelector('.bottom-nav');
10942  // if (bottomNav && window.innerWidth <= 640) {
10943  //   // Auto-hide feature disabled
10944  // }
10945})();
10946// Cart Login Modal Handler (Global scope)
10947function showCartLoginModal() {
10948  const modal = document.getElementById('cartLoginModal');
10949  if (!modal) return;
10950
10951  modal.classList.add('show');
10952
10953  const confirmBtn = document.getElementById('cartLoginConfirm');
10954  const registerBtn = document.getElementById('cartRegisterBtn');
10955  const cancelBtn = document.getElementById('cartLoginCancel');
10956
10957  const handleLogin = () => {
10958    window.location.href = '/login?return=' + encodeURIComponent('/keranjang');
10959  };
10960
10961  const handleRegister = () => {
10962    window.location.href = '/register?return=' + encodeURIComponent('/keranjang');
10963  };
10964
10965  const handleCancel = () => {
10966    modal.classList.remove('show');
10967    confirmBtn.removeEventListener('click', handleLogin);
10968    registerBtn.removeEventListener('click', handleRegister);
10969    cancelBtn.removeEventListener('click', handleCancel);
10970  };
10971
10972  confirmBtn.removeEventListener('click', handleLogin);
10973  registerBtn.removeEventListener('click', handleRegister);
10974  cancelBtn.removeEventListener('click', handleCancel);
10975
10976  confirmBtn.addEventListener('click', handleLogin);
10977  registerBtn.addEventListener('click', handleRegister);
10978  cancelBtn.addEventListener('click', handleCancel);
10979
10980  modal.addEventListener('click', (e) => {
10981    if (e.target === modal) {
10982      handleCancel();
10983    }
10984  });
10985}
10986</script>
vendor: 2 bytes, line 10986
10986
10987<script data-inlined="cart-badge.js">
10988(function(){
10989  function selBadge(){
10990    return document.querySelector('[data-role="cart-badge"]') || document.getElementById('cartCount');
10991  }
10992  function setBadge(n){
10993    const el = selBadge();
10994    if (!el) return;
10995    const count = Math.max(0, parseInt(n||0,10) || 0);
10996    el.textContent = String(count);
10997    // Sembunyikan jika 0? Jika ingin selalu terlihat, hapus 2 baris di bawah
10998    if (count <= 0) { el.classList.add('is-empty'); }
10999    else { el.classList.remove('is-empty'); }
11000  }
11001  async function refresh(){
11002    try{
11003      // Primary: Use server-side endpoint (handles both logged and guest users)
11004      const res = await fetch('/api/cart/get-simple.php', { credentials:'same-origin' });
11005      if (res.ok) {
11006        const data = await res.json();
11007        if (data && data.ok) {
11008          setBadge(data.items_count || 0);
11009          return;
11010        }
11011      }
11012
11013      // Fallback hanya untuk guest user: check localStorage
11014      const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true' ||
11015                        document.body.classList.contains('logged-in') ||
11016                        window.userLoggedIn === true;
11017
11018      if (!isLoggedIn && window.guestCart && typeof window.guestCart.getItemsCount === 'function') {
11019        setBadge(window.guestCart.getItemsCount());
11020      }
11021    }catch(e){ 
11022      // Final fallback hanya untuk guest
11023      const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true';
11024      if (!isLoggedIn) {
11025        try {
11026          const guestCartData = JSON.parse(localStorage.getItem('plazait_guest_cart') || '{"items":{}}');
11027          const items = guestCartData.items || {};
11028          const count = Object.values(items).reduce((total, item) => total + (item.qty || 0), 0);
11029          setBadge(count);
11030        } catch(e2) {
11031          setBadge(0);
11032        }
11033      } else {
11034        setBadge(0);
11035      }
11036    }
11037  }
11038
11039  // Ekspos untuk dipanggil dari skrip lain (opsional)
11040  window.CartBadge = { set: setBadge, refresh };
11041  window.updateCartCounter = refresh; // Alias for compatibility
11042
11043  // Update saat halaman load
11044  if (document.readyState === 'loading') {
11045    document.addEventListener('DOMContentLoaded', refresh);
11046  } else {
11047    refresh();
11048  }
11049
11050  // Dengarkan event global dari add/remove/update
11051  document.addEventListener('cart:updated', function(ev){
11052    const c = ev && ev.detail && typeof ev.detail.items_count === 'number'
11053      ? ev.detail.items_count : null;
11054    if (c !== null) setBadge(c);
11055    else refresh();
11056  });
11057
11058  // Dengarkan event khusus cart remove untuk update badge
11059  document.addEventListener('cart:removed', function(ev){
11060    // Langsung refresh untuk memastikan data terbaru
11061    refresh();
11062  });
11063
11064  // Jika addToCart sudah ada sebelum file ini, coba wrap agar badge ter-update.
11065  const prevAdd = window.addToCart;
11066  if (typeof prevAdd === 'function'){
11067    window.addToCart = async function(pid, qty, vid, meta){
11068      const res = await prevAdd(pid, qty, vid, meta);
11069      try{
11070        const cnt = res && res.cart ? res.cart.items_count : null;
11071        if (cnt !== null) setBadge(cnt);
11072        // tetap siarkan event agar listener lain ikut sync
11073        document.dispatchEvent(new CustomEvent('cart:updated', { detail: res.cart || {} }));
11074      }catch(_){}
11075      return res;
11076    };
11077  }
11078
11079  // Helper function to check if user is logged in
11080  function isUserLoggedIn() {
11081    const metaTag = document.querySelector('meta[name="user-logged-in"]');
11082    return metaTag ? metaTag.getAttribute('content') === 'true' : false;
11083  }
11084
11085  // Make login check globally accessible
11086  window.isUserLoggedIn = isUserLoggedIn;
11087})();
11088</script>
11088
11089
11090<!-- Product Compare Sidebar -->
11091<div class="compare-sidebar" id="compareSidebar">
11092  <div class="compare-header">
11093    <h4 class="compare-title">
11094      <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
11095        <path d="M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z"/>
11096      </svg>
11097      Bandingkan Panduan
11098      <span class="compare-count" id="compareCountSidebar">(0)</span>
11099    </h4>
11100    <button class="compare-close" id="compareClose" aria-label="Tutup">
11101      <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
11102        <path d="M18.3 5.7a1 1 0 0 0-1.4 0L12 10.6 7.1 5.7a1 1 0 1 0-1.4 1.4L10.6 12l-4.9 4.9a1 1 0 0 0 1.4 1.4L12 13.4l4.9 4.9a1 1 0 0 0 1.4-1.4L13.4 12l4.9-4.9a1 1 0 0 0 0-1.4z"/>
11103      </svg>
11104    </button>
11105  </div>
11106
11107  <div class="compare-content">
11108    <div class="compare-empty" id="compareEmpty">
11109      <div class="empty-icon">
11110        <svg width="48" height="48" viewBox="0 0 24 24" fill="currentColor" opacity="0.3">
11111          <path d="M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z"/>
11112        </svg>
11113      </div>
11114      <h5>Belum ada panduan dipilih</h5>
11115      <p>Pilih panduan shio yang ingin dibandingkan melalui tombol "Bandingkan Panduan" pada halaman ini.</p>
11116    </div>
11117
11118    <div class="compare-list" id="compareList">
11119      <!-- Products will be populated here -->
11120    </div>
11121  </div>
11122
11123  <div class="compare-actions" id="compareActions">
11124    <button class="btn btn-outline-danger btn-sm" id="compareClearAll">
11125      <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
11126        <path d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"/>
11127      </svg>
11128      Hapus Semua
11129    </button>
11130    <button class="btn btn-primary btn-sm" id="compareAnalyze" disabled>
11131      <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
11132        <path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4M11,16.5L18,9.5L16.59,8.09L11,13.67L7.91,10.59L6.5,12L11,16.5Z"/>
11133      </svg>
11134      Analisis AI
11135    </button>
11136  </div>
11137
11138  <!-- AI Disclaimer in Sidebar -->
11139  <div class="compare-disclaimer">
11140    <div class="ai-disclaimer">
11141      <div class="disclaimer-text">
11142       <strong>💡 Catatan:</strong> Variasi game dihadirkan sebagai referensi hiburan interaktif.
11143        Pilihan permainan tidak menjamin hasil atau kemenangan tertentu.
11144        Untuk informasi lebih lanjut mengenai layanan BOYTOTO, silakan hubungi tim BOYTOTO.
11145      </div>
11146    </div>
11147  </div>
11148</div><style data-inlined="pd-lightbox.css">
11149/* PlazaIT Product Lightbox (vanilla, no CDN) — v4 */
11150.pd-lb {
11151  position: fixed; inset: 0; z-index: 99999;
11152  background: rgba(0,0,0,.95);
11153  display: none;
11154  color: #fff;
11155  /* Transition untuk swipe-down feedback */
11156  transition: transform .18s ease, opacity .18s ease;
11157}
11158.pd-lb.show { display: block; }
11159
11160/* Canvas di bawah (z:1), Controls di atas (z:2) */
11161.pd-lb-canvas {
11162  position: absolute; inset: 56px 0 56px 0;
11163  display: flex; align-items: center; justify-content: center;
11164  overflow: hidden;
11165  touch-action: none;
11166  z-index: 1;
11167}
11168
11169.pd-lb-img {
11170  max-width: 96vw; max-height: 86vh;
11171  will-change: transform;
11172  user-select: none;
11173  pointer-events: none;
11174  transform-origin: 50% 50%;
11175}
11176
11177.pd-lb-close, .pd-lb-prev, .pd-lb-next, .pd-lb-counter {
11178  position: absolute;
11179  z-index: 2;
11180}
11181
11182.pd-lb-close, .pd-lb-prev, .pd-lb-next {
11183  background: rgba(17,24,39,.7);
11184  color: #fff; border: 0; cursor: pointer;
11185  width: 40px; height: 40px; border-radius: 999px;
11186  display: inline-flex; align-items: center; justify-content: center;
11187  transition: background-color .15s ease, transform .05s ease;
11188  -webkit-tap-highlight-color: transparent;
11189}
11190.pd-lb-close:hover, .pd-lb-prev:hover, .pd-lb-next:hover { background: rgba(31,41,55,.85); }
11191.pd-lb-close:active, .pd-lb-prev:active, .pd-lb-next:active { transform: translateY(.5px); }
11192
11193.pd-lb-close { top: max(12px, env(safe-area-inset-top, 12px)); right: max(12px, env(safe-area-inset-right, 12px)); }
11194.pd-lb-prev  { top: 50%; left:  10px; transform: translateY(-50%); }
11195.pd-lb-next  { top: 50%; right: 10px; transform: translateY(-50%); }
11196
11197.pd-lb-counter {
11198  top: max(16px, env(safe-area-inset-top, 16px)); left: 50%;
11199  transform: translateX(-50%);
11200  font-weight: 700; font-size: 13px; letter-spacing: .4px;
11201  background: rgba(17,24,39,.65);
11202  padding: 6px 10px; border-radius: 999px; border: 1px solid rgba(255,255,255,.1);
11203}
11204
11205@media (max-width: 640px){
11206  .pd-lb-img { max-width: 98vw; max-height: 80vh; }
11207  .pd-lb-prev, .pd-lb-next { width: 36px; height: 36px; }
11208  .pd-lb-close { width: 38px; height: 38px; }
11209}
11210</style>
11211<style>.paylater-promo{display:inline-flex;align-items:center;gap:8px;background:linear-gradient(135deg,#f0fdf4 0,#dcfce7 100%);border:1px solid #86efac;border-radius:8px;padding:8px 12px;font-size:13px;line-height:1.4;margin:8px 0}.paylater-promo-sm{padding:6px 10px;font-size:11px;gap:6px}.paylater-promo-md{padding:8px 12px;font-size:13px;gap:8px}.paylater-promo-lg{padding:10px 16px;font-size:14px;gap:10px}.paylater-icon{font-size:1.2em;flex-shrink:0}.paylater-text{display:flex;flex-direction:column;gap:2px}.paylater-text strong{color:#15803d;font-weight:600}.paylater-text small{color:#16a34a;font-size:.85em;opacity:.9}.paylater-desktop-only{display:none}.paylater-mobile-only{display:flex}@media (min-width:641px){.paylater-desktop-only{display:flex}.paylater-mobile-only{display:none}}@media (max-width:640px){.paylater-promo{font-size:12px;padding:7px 10px}.paylater-promo-lg{font-size:13px;padding:8px 12px}}@media print{.paylater-promo{display:none!important}}.paylater-compact{display:inline-block;font-size:11px;font-weight:600;color:#059669;background:#d1fae5;padding:3px 8px;border-radius:4px;margin:6px 0 4px;line-height:1.3}@media (max-width:640px){.paylater-compact{font-size:10px;padding:2px 6px;margin:5px 0 3px}}@media print{.paylater-compact{display:none!important}}</style><style nonce="se3iyzEHMc9FQEB4wSyd0w==">.pd-voucher-minimal{margin:10px 0;display:flex;flex-direction:column;gap:6px}.pdv-min-item{display:flex;align-items:center;gap:8px;padding:0;height:32px;transition:all .2s ease}.pdv-min-hidden{display:none}.pdv-min-disabled{opacity:.5}.pdv-badge-btn{display:inline-flex;align-items:center;gap:8px;background:linear-gradient(135deg,#667eea 0,#764ba2 100%);border:none;border-radius:8px;padding:3px;cursor:pointer;transition:all .2s ease;width:auto}.pdv-badge-btn:hover:not(:disabled){transform:translateY(-1px);box-shadow:0 4px 12px rgba(102,126,234,.3)}.pdv-badge-btn:disabled{opacity:.6;cursor:not-allowed}.pdv-badge-icon{color:#fff;flex-shrink:0}.pdv-badge-text{font-size:14px;font-weight:700;color:#fff;white-space:nowrap}.pdv-badge-code{font-family:'Courier New',monospace;font-size:13px;font-weight:700;color:#fff;background:rgba(255,255,255,.2);padding:4px 8px;border-radius:4px;border:none;white-space:nowrap}.pdv-min-copy,.pdv-min-more{padding:4px 12px;font-size:12px;font-weight:600;border-radius:5px;border:1px solid #e2e8f0;background:#fff;color:#667eea;cursor:pointer;transition:all .2s ease;white-space:nowrap}.pdv-min-copy:hover:not(:disabled),.pdv-min-more:hover{background:#667eea;color:#fff;border-color:#667eea}.pdv-min-copy:disabled{opacity:.4;cursor:not-allowed}.pdv-min-copy.copied{background:#22c55e;color:#fff;border-color:#22c55e}.pdv-min-more{margin-left:auto}.modal-close{position:absolute;top:20px;right:20px;background:none;border:none;cursor:pointer;color:#64748b;padding:4px;transition:color .2s ease}.modal-close:hover{color:#1e293b}.modal-body{margin-bottom:8px}.voucher-modal-content{text-align:center}.voucher-modal-badge{display:inline-flex;flex-direction:row;align-items:center;gap:6px;background:linear-gradient(135deg,#667eea 0,#764ba2 100%);border-radius:10px;padding:8px 16px;margin-bottom:12px}.vmb-value{font-size:18px;font-weight:700;color:#fff;line-height:1}.vmb-label{font-size:18px;font-weight:700;color:#fff;text-transform:uppercase;letter-spacing:.5px}.voucher-modal-code{font-family:'Courier New',monospace;font-size:14px;font-weight:700;color:#667eea;background:rgba(102,126,234,.1);padding:6px 12px;border-radius:6px;border:2px dashed rgba(102,126,234,.3);display:inline-block;margin-bottom:10px}.voucher-modal-name{font-size:14px;font-weight:600;color:#1e293b;margin-bottom:16px}.voucher-modal-details{text-align:left;background:#f8fafc;border-radius:8px;padding:12px;margin-bottom:16px}.vmd-item{display:flex;align-items:flex-start;gap:8px;padding:6px 0;font-size:13px;color:#64748b;border-bottom:1px solid #e2e8f0}.vmd-item:last-child{border-bottom:none}.vmd-item svg{color:#667eea;flex-shrink:0;margin-top:2px}.vmd-item strong{color:#1e293b}.modal-actions{padding:20px}.modal-btn-primary{background:linear-gradient(135deg,#667eea 0,#764ba2 100%);color:#fff}.modal-btn-primary:hover{opacity:.9;transform:translateY(-1px)}@media (max-width:640px){.pdv-min-item{gap:6px}.pdv-badge-btn{padding:3px;gap:6px}.pdv-badge-text{font-size:13px}.pdv-badge-code{font-size:12px;padding:3px 6px}.pdv-min-copy,.pdv-min-more{padding:3px 10px;font-size:11px}.modal-dialog{max-width:95%;margin:20px}.vmb-value{font-size:16px}.vmb-label{font-size:16px}.voucher-modal-code{font-size:13px;padding:5px 10px}}</style><style nonce="se3iyzEHMc9FQEB4wSyd0w==">.promo-endyear-mini-banner{display:flex;align-items:center;gap:12px;padding:12px 16px;background:rgba(255,255,255,.8);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:2px solid rgba(242,140,26,.2);border-radius:12px;margin:16px 0;box-shadow:0 4px 16px rgba(0,0,0,.08);transition:all .3s ease}.promo-endyear-mini-banner:hover{transform:translateY(-2px);box-shadow:0 6px 20px rgba(0,0,0,.12);border-color:rgba(242,140,26,.4)}.pey-icon{font-size:32px;line-height:1;flex-shrink:0}.pey-content{flex:1;min-width:0}.pey-content strong{display:block;font-size:14px;font-weight:700;color:#F28C1A;margin-bottom:2px}
11211.pey-content span{display:block;font-size:13px;color:#64748b;line-height:1.4}.pey-link{flex-shrink:0;padding:8px 16px;background:linear-gradient(135deg,#F28C1A,#FFB84D);color:#fff;text-decoration:none;font-size:13px;font-weight:700;border-radius:8px;white-space:nowrap;transition:all .2s ease;box-shadow:0 2px 8px rgba(242,140,26,.25)}.pey-link:hover{transform:translateX(2px);box-shadow:0 4px 12px rgba(242,140,26,.35)}@media(max-width:640px){.promo-endyear-mini-banner{flex-wrap:wrap;gap:8px;padding:10px 12px}.pey-icon{font-size:28px;align-self:flex-start}.pey-content{flex:1;min-width:0}.pey-content strong{font-size:12px}.pey-content span{font-size:11px;line-height:1.3}.pey-link{width:100%;flex-basis:100%;font-size:11px;padding:6px 12px;text-align:center}}@media print{.promo-endyear-mini-banner{display:none!important}}</style>
11212<style nonce="se3iyzEHMc9FQEB4wSyd0w==">
11213.pd-title-row{position:relative;margin-bottom:12px;padding-right:52px}.pd-title{margin:0!important}.pd-share-btn{position:absolute;top:0;right:0;width:40px;height:40px;display:inline-flex;align-items:center;justify-content:center;border-radius:10px;border:1px solid #e2e8f0;background:#fff;color:#64748b;cursor:pointer;transition:all .2s ease;-webkit-tap-highlight-color:transparent}.pd-share-btn:hover{background:#f8fafc;border-color:#cbd5e1;color:#0f172a;transform:translateY(-1px)}.pd-share-btn:active{transform:translateY(0)}.pd-share-btn svg{display:block}
11214
11215.pd-share-modal-overlay{position:fixed;inset:0;background:rgba(15,23,42,.75);backdrop-filter:blur(4px);z-index:99999;display:none;align-items:center;justify-content:center;padding:0;animation:fadeIn .2s ease}.pd-share-modal-overlay.show{display:flex}.pd-share-modal{background:#fff;border-radius:20px;box-shadow:0 25px 50px -12px rgba(0,0,0,.25);max-width:480px;width:100%;max-height:90vh;overflow:hidden;display:flex;flex-direction:column;animation:slideUp .3s cubic-bezier(.16,1,.3,1)}@keyframes fadeIn{from{opacity:0}to{opacity:1}}@keyframes slideUp{from{opacity:0;transform:translateY(20px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}
11216
11217.pd-share-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid #f1f5f9}.pd-share-title{font-size:18px;font-weight:700;color:#0f172a;margin:0}.pd-share-close{width:32px;height:32px;border-radius:8px;border:0;background:transparent;color:#64748b;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:all .15s ease}.pd-share-close:hover{background:#f1f5f9;color:#0f172a}.pd-share-body{padding:24px;overflow-y:auto}
11218
11219.pd-share-preview{display:flex;gap:12px;padding:16px;background:#f8fafc;border-radius:12px;margin-bottom:24px}.pd-share-preview-img{width:64px;height:64px;border-radius:8px;object-fit:cover;flex-shrink:0;border:1px solid #e2e8f0}.pd-share-preview-info{flex:1;min-width:0}.pd-share-preview-name{font-size:14px;font-weight:600;color:#0f172a;margin:0 0 4px 0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.pd-share-preview-price{font-size:16px;font-weight:700;color:#0ea5e9;margin:0}
11220
11221.pd-share-label{font-size:13px;font-weight:600;color:#475569;margin:0 0 12px 0;text-transform:uppercase;letter-spacing:.5px}.pd-share-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:24px}.pd-share-option{display:flex;flex-direction:column;align-items:center;gap:8px;padding:16px 8px;border:1.5px solid #e2e8f0;border-radius:12px;background:#fff;cursor:pointer;transition:all .2s ease;text-decoration:none;color:inherit}.pd-share-option:hover{border-color:#94a3b8;background:#f8fafc;transform:translateY(-2px)}.pd-share-option:active{transform:translateY(0)}
11222
11223.pd-share-icon{width:40px;height:40px;border-radius:10px;display:inline-flex;align-items:center;justify-content:center;position:relative}.pd-share-icon svg{width:24px;height:24px;display:block}.pd-share-icon-whatsapp{background:linear-gradient(135deg,#25D366 0%,#128C7E 100%);color:#fff}.pd-share-icon-facebook{background:linear-gradient(135deg,#1877F2 0%,#0c63d4 100%);color:#fff}.pd-share-icon-twitter{background:linear-gradient(135deg,#1DA1F2 0%,#0d8bd9 100%);color:#fff}.pd-share-icon-telegram{background:linear-gradient(135deg,#0088cc 0%,#0077b3 100%);color:#fff}.pd-share-icon-email{background:linear-gradient(135deg,#EA4335 0%,#c5331f 100%);color:#fff}.pd-share-icon-copy{background:linear-gradient(135deg,#f1db63 0%,#e5d146 100%);color:#fff}.pd-share-icon-pdf{background:linear-gradient(135deg,#ef4444 0%,#dc2626 100%);color:#fff}.pd-share-name{font-size:12px;font-weight:600;color:#475569;text-align:center}
11224
11225.pd-share-copy-section{background:#f8fafc;padding:16px;border-radius:12px}.pd-share-copy-input-wrap{display:flex;gap:8px}.pd-share-copy-input{flex:1;padding:12px 14px;border:1px solid #e2e8f0;border-radius:10px;font-size:14px;color:#0f172a;background:#fff;font-family:'Segoe UI',system-ui,sans-serif}.pd-share-copy-input:focus{outline:0;border-color:#0ea5e9;box-shadow:0 0 0 3px rgba(14,165,233,.1)}.pd-share-copy-btn{padding:12px 20px;border:0;border-radius:10px;background:linear-gradient(135deg,#0ea5e9 0%,#0284c7 100%);color:#fff;font-size:14px;font-weight:600;cursor:pointer;transition:all .2s ease;white-space:nowrap;display:inline-flex;align-items:center;gap:6px}.pd-share-copy-btn:hover{background:linear-gradient(135deg,#0284c7 0%,#0369a1 100%);transform:translateY(-1px);box-shadow:0 4px 12px rgba(14,165,233,.3)}
11225.pd-share-copy-btn:active{transform:translateY(0)}.pd-share-copy-btn svg{width:16px;height:16px}.pd-share-copy-btn.copied{background:linear-gradient(135deg,#22c55e 0%,#16a34a 100%);pointer-events:none}
11226
11227@media(max-width:640px){.pd-share-modal{border-radius:20px 20px 0 0;margin-top:auto;max-height:85vh}.pd-share-grid{grid-template-columns:repeat(3,1fr)}.pd-share-header{padding:16px 20px}.pd-share-body{padding:20px}.pd-share-btn{width:36px;height:36px}.pd-share-btn svg{width:18px;height:18px}}@media(max-width:480px){.pd-share-grid{grid-template-columns:repeat(3,1fr);gap:10px}.pd-share-option{padding:12px 6px}.pd-share-icon{width:36px;height:36px}.pd-share-icon svg{width:20px;height:20px}.pd-share-name{font-size:11px}}
11228
11229.mf-share{background:#fff;color:#fb923c;border:2px solid #fb923c;display:inline-flex;align-items:center;justify-content:center}.mf-share:active{transform:scale(.95)}.mf-share svg{display:block}
11230
11231@media(max-width:640px){.pd-share-btn{display:none!important}}
11232
11233.stock-badge.in{background:#e6f9ec;color:#15803d;border-radius:999px;padding:4px 10px;font-weight:700}.stock-badge.out{background:#d50000;color:#14b1d7;border-radius:999px;padding:4px 10px;font-weight:700}
11234
11235.stock-info-btn{--c:#0ea5e9;--b:#bae6fd;--bg:#f0f9ff;--bg-h:#e0f2fe;--b-h:#0ea5e9;margin-left:0;display:inline-flex;align-items:center;justify-content:center;gap:0.5rem;padding:4px 10px;border-radius:999px;border:1.5px solid var(--b);background:var(--bg);color:var(--c);cursor:pointer;font-size:12px;font-weight:600;transition:all .2s ease;-webkit-tap-highlight-color:transparent}.stock-info-btn:hover{background:var(--bg-h);border-color:var(--b-h);color:#0284c7;transform:translateY(-1px);box-shadow:0 2px 8px rgba(14,165,233,.15)}.stock-info-btn:active{transform:translateY(0)}.stock-info-btn:focus-visible{outline:0;box-shadow:0 0 0 3px rgba(14,165,233,.2)}.stock-info-btn svg{width:14px;height:14px;flex-shrink:0}.stock-info-btn .btn-text{white-space:nowrap}
11236
11237.stock-popover{position:absolute;z-index:9999;min-width:280px;max-width:92vw;background:#0b1220;color:#eef3ff;border:1px solid rgba(255,255,255,.08);border-radius:14px;box-shadow:0 20px 40px rgba(12,12,20,.45);padding:12px 12px 8px;display:none}.stock-popover.show{display:block}.stock-popover::before{content:"";position:absolute;top:-8px;left:24px;width:14px;height:14px;background:#0b1220;border-left:1px solid rgba(255,255,255,.08);border-top:1px solid rgba(255,255,255,.08);transform:rotate(45deg)}.sp-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.sp-title{font-weight:800;font-size:14px}.sp-close{background:transparent;border:0;color:#98a2b3;cursor:pointer}
11238
11239.sp-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.sp-item{display:grid;grid-template-columns:1fr auto 32px;column-gap:12px;align-items:center;padding:10px 10px;min-height:44px;border-radius:10px;background:rgba(255,255,255,.03)}.sp-left{display:flex;align-items:center;gap:10px}.sp-dot{width:10px;height:10px;border-radius:999px;background:var(--dot,#60a5fa);display:inline-block}.sp-label{font-weight:600;font-size:13px}
11240
11241
11242
11243.sp-qty{grid-column:2;justify-self:end;min-width:40px;text-align:right;font-weight:800;font-size:13px;color:#cfe4ff;font-variant-numeric:tabular-nums}
11244
11245.sp-wa{
11246  grid-column:3; justify-self:end;
11247  width:32px; height:32px; min-width:32px; min-height:32px;
11248  box-sizing:border-box;
11249  padding:0; line-height:0; text-decoration:none; outline:0;
11250  display:inline-flex; align-items:center; justify-content:center;
11251  border-radius:999px; border:1px solid rgba(37,211,102,.60);  /* 1px untuk hasil “crisp” */
11252  background:rgba(37,211,102,.08); color:#25D366;
11253  transition:background-color .15s ease, border-color .15s ease, transform .05s ease;
11254  -webkit-tap-highlight-color:transparent;
11255}
11256.sp-wa:hover{background:rgba(37,211,102,.16);border-color:rgba(37,211,102,.75)}
11257.sp-wa:active{transform:translateY(.5px) scale(.98)}
11258
11259.sp-wa svg{width:18px;height:18px;display:block;vertical-align:middle}
11260.stock-popover a{text-decoration:none}
11261
11262#deskripsi img{max-width:450px!important;height:unset!important}@media(max-width:640px){.stock-popover{position:fixed;left:0;right:0;bottom:max(12px,env(safe-area-inset-bottom,12px));margin:0 10px;padding:14px 12px 10px;border-radius:16px}.stock-popover::before{display:none}.stock-info-btn{padding:4px 8px;gap:4px;border:0;background:transparent}.stock-info-btn:hover{background:transparent;box-shadow:none;transform:none}.stock-info-btn .btn-text{display:none}.stock-info-btn svg{width:16px;height:16px}.sp-item{grid-template-columns:1fr auto 36px}.sp-wa{width:36px;height:36px;min-width:36px;min-height:36px}.sp-wa svg{width:19px;height:19px}}
11263
11264.stock-dot{position:absolute;bottom:8px;right:8px;width:8px;height:8px;border-radius:50%;box-shadow:0 1px 3px rgba(0,0,0,0.4);z-index:2;cursor:help}.stock-dot.ready{background:#10b981}.stock-dot.low{background:#f59e0b}.stock-dot.out{background:#ef4444}.stock-dot:hover{transform:scale(1.2);transition:transform 0.2s ease}@media(max-width:640px){.stock-dot{width:7px;height:7px;bottom:6px;right:6px}}
11265
11266.pd-related-card .spec-chips{font-size:11px;color:#64748b;margin:4px 0 6px 0;line-height:1.3;font-weight:500}.pd-related-card .spec-chips .spec-divider{margin:0 4px;opacity:0.6}@media(max-width:640px){.pd-related-card .spec-chips{font-size:10px;margin:3px 0 5px 0}#deskripsi img{max-width:300px!important;height:unset!important;justify-self:center!important}}
11267
11268.pwp-trigger-badge{display:inline-flex;align-items:center;gap:4px;padding:3px 8px 4px;background:linear-gradient(135deg,#fbbf24 0%,#f59e0b 100%);color:#78350f;border-radius:6px;font-size:.65rem;font-weight:600;border:0}.pwp-trigger-badge svg{flex-shrink:0;width:12px;height:12px}
11269.pwp-deals-section{margin:0;padding:12px;background:linear-gradient(135deg,#fef3c7 0%,#fde68a 100%);border-radius:12px;border:2px solid #fbbf24}.pwp-deals-header{display:flex;gap:10px;margin-bottom:10px}.pwp-icon{flex-shrink:0;color:#f59e0b;width:18px;height:18px}.pwp-text .pwp-title{margin:0 0 2px 0;font-size:14px;font-weight:700;color:#78350f}.pwp-text .pwp-subtitle{margin:0;font-size:11px;color:#92400e;opacity:0.9}.pwp-deals-loading{text-align:center;padding:40px 0;color:#92400e}.pwp-spinner{width:40px;height:40px;border:3px solid #fbbf24;border-top-color:#f59e0b;border-radius:50%;animation:spin 0.8s linear infinite;margin:0 auto 12px}@keyframes spin{to{transform:rotate(360deg)}}
11270.pwp-deals-grid{display:flex;flex-direction:column;gap:6px}.pwp-deal-card{background:#fff;border-radius:6px;overflow:hidden;border:1px solid #fbbf24;display:flex;align-items:center;padding:8px;gap:10px}.pwp-deal-card:hover{background:#fef3c7}.pwp-deal-thumb{flex-shrink:0;width:50px;height:50px;background:#f8fafc;overflow:hidden;border-radius:4px}.pwp-deal-thumb img{width:100%;height:100%;object-fit:cover}.pwp-deal-badge{background:#dc2626;color:#fff;padding:2px 5px;border-radius:3px;font-size:9px;font-weight:700;margin-left:auto}.pwp-deal-info{flex:1;display:flex;flex-direction:column;gap:4px}.pwp-deal-name{font-size:12px;font-weight:600;color:#0f172a;margin:0;line-height:1.2}.pwp-deal-prices{display:flex;align-items:center;gap:6px;flex-wrap:wrap}.pwp-deal-price{font-size:14px;font-weight:800;color:#dc2626;white-space:nowrap}.pwp-deal-original{font-size:11px;color:#64748b;text-decoration:line-through;white-space:nowrap}.pwp-cart-note{margin-top:8px;padding:6px 10px;background:#e0f2fe;border-left:2px solid#0ea5e9;font-size:11px;color:#0c4a6e;border-radius:4px}
11271@media(max-width:640px){.pwp-deals-section{margin:12px 0;padding:10px;border-radius:10px}.pwp-deals-header{gap:8px}.pwp-text .pwp-title{font-size:13px}.pwp-text .pwp-subtitle{font-size:10px}.pwp-deal-card{padding:6px;gap:8px}.pwp-deal-thumb{width:45px;height:45px}.pwp-deal-name{font-size:11px}.pwp-deal-price{font-size:13px}.pwp-deal-original{font-size:10px}}
11272
11273.preorder-badge{display:inline-flex;align-items:center;gap:5px;padding:6px 12px;background:linear-gradient(135deg,#8b5cf6 0%,#7c3aed 100%);color:#fff;border-radius:8px;font-size:13px;font-weight:700;border:0;box-shadow:0 2px 8px rgba(139,92,246,.25)}.preorder-badge svg{width:16px;height:16px;flex-shrink:0}.preorder-info{margin:12px 0;padding:14px;background:linear-gradient(135deg,#f5f3ff 0%,#ede9fe 100%);border:1.5px solid #c4b5fd;border-radius:12px}.preorder-info-header{display:flex;align-items:center;gap:8px;margin-bottom:10px}.preorder-info-icon{width:20px;height:20px;color:#7c3aed;flex-shrink:0}.preorder-info-title{font-size:14px;font-weight:700;color:#5b21b6;margin:0}.preorder-info-items{display:flex;flex-direction:column;gap:8px}.preorder-info-row{display:flex;align-items:center;justify-content:space-between;font-size:13px}.preorder-info-label{color:#6b21a8;font-weight:600}.preorder-info-value{color:#5b21b6;font-weight:700}.preorder-info-value.highlight{color:#dc2626;font-size:14px}.preorder-info-note{margin-top:10px;padding:8px 10px;background:#fef3c7;border-left:2px solid #f59e0b;font-size:12px;color:#78350f;border-radius:6px;line-height:1.4}@media(max-width:640px){.preorder-badge{font-size:12px;padding:5px 10px}.preorder-info{margin:10px 0;padding:12px}.preorder-info-title{font-size:13px}.preorder-info-row{font-size:12px}.preorder-info-note{font-size:11px;padding:6px 8px}}
11274</style>
11275
11276<div class="pd-sticky-header" id="pd-sticky-header"><div class="pd-sticky-header-container"><div class="pd-sticky-thumb"><img src="https://ik.imagekit.io/boytoto/banner235.png" alt="BOYTOTO" id="pd-sticky-thumb-img"></div><div class="pd-sticky-info"><h2 class="pd-sticky-title" id="pd-sticky-title">BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler</h2><div class="pd-sticky-price-row"><span class="pd-sticky-price" id="pd-sticky-price">Referensi Shio</span><span class="pd-sticky-price-original" id="pd-sticky-price-original">Nomor 4D</span><span class="pd-sticky-discount" id="pd-sticky-discount">-99.5%</span></div></div><div class="pd-sticky-actions"><button type="button" class="pd-sticky-share-btn" id="pd-sticky-share-btn" aria-label="Bagikan panduan" title="Bagikan"><svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
11276<path fill="currentColor" d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92s2.92-1.31 2.92-2.92-1.31-2.92-2.92-2.92z"/></svg></button><button type="button" class="pd-sticky-btn pd-sticky-btn-chat" id="pd-sticky-chat"><svg viewBox="0 0 640 640" fill="currentColor" aria-hidden="true"><path d="M320 128C241 128 175.3 185.3 162.3 260.7C171.6 257.7 181.6 256 192 256L208 256C234.5 256 256 277.5 256 304L256 400C256 426.5 234.5 448 208 448L192 448C139 448 96 405 96 352L96 288C96 164.3 196.3 64 320 64C443.7 64 544 164.3 544 288L544 456.1C544 522.4 490.2 576.1 423.9 576.1L336 576L304 576C277.5 576 256 554.5 256 528C256 501.5 277.5 480 304 480L336 480C362.5 480 384 501.5 384 528L384 528L424 528C463.8 528 496 495.8 496 456L496 435.1C481.9 443.3 465.5 447.9 448 447.9L432 447.9C405.5 447.9 384 426.4 384 399.9L384 303.9C384 277.4 405.5 255.9 432 255.9L448 255.9C458.4 255.9 468.3 257.5 477.7 260.6C464.7 185.3 399.1 127.9 320 127.9z"/></svg>Tanya</button><button type="button" class="pd-sticky-btn pd-sticky-btn-cart" id="pd-sticky-add-cart" disabled>Simpan Panduan</button><button type="button" class="pd-sticky-btn pd-sticky-btn-buy" id="pd-sticky-buy" disabled>Lihat Daftar BOYTOTO Terbaru</button></div></div></div>
11277
11278<main class="product-single product-single--v3"><nav class="pd-breadcrumb" aria-label="breadcrumb"><a href="https://mgsco.org/">BOYTOTO</a><span>-</span><a href="https://mgsco.org/">BOYTOTO RESMI</a><span>-</span><a href="https://mgsco.org/">BOYTOTO GACOR</a><span>-</span><a href="https://mgsco.org/">BOYTOTO TERPERCAYA</a><span>-</span><a href="https://mgsco.org/">BOYTOTO LOGIN</a><span>-</span><span class="current">BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler
11279
11280</span></nav>
11281
11282<div class="pd-top-layout"><section class="pd-media-card"><figure class="pd-main-image" id="pd-main-figure"><img id="pd-main-img" src="https://ik.imagekit.io/boytoto/banner235.png" alt="BOYTOTO" data-slug="" loading="eager">
11283</figure>
11284
11285<div class="pd-mobile-slider-wrapper">
11286<div class="pd-mobile-slider" id="pd-mobile-slider" aria-label="Galeri gambar (geser)"><div class="pd-mslide"><img src="https://ik.imagekit.io/boytoto/banner235.png" alt="BOYTOTO" loading="eager"></div><div class="pd-mslide"><img src="https://ik.imagekit.io/boytoto/banner235.png" alt="BOYTOTO" loading="lazy"></div><div class="pd-mslide"><img src="https://ik.imagekit.io/boytoto/banner235.png" alt="BOYTOTO" loading="lazy"></div><div class="pd-mslide"><img src="https://ik.imagekit.io/boytoto/banner235.png" alt="BOYTOTO" loading="lazy"></div><div class="pd-mslide"><img src="https://ik.imagekit.io/boytoto/banner235.png" alt="BOYTOTO" loading="lazy"></div><div class="pd-mslide"><img src="https://ik.imagekit.io/boytoto/banner235.png" alt="BOYTOTO" loading="lazy"></div></div><div class="pd-gallery-counter" id="pd-gallery-counter">1/6</div></div>
11287<div class="pd-thumb-gallery" id="pd-thumb-gallery" aria-label="Galeri gambar produk"><button type="button" class="pd-thumb-btn active" data-full="https://ik.imagekit.io/boytoto/banner235.png" data-alt="BOYTOTO" data-slug="" aria-label="Gambar 1"><img src="https://ik.imagekit.io/boytoto/banner235.png" alt="BOYTOTO" loading="lazy"></button><button type="button" class="pd-thumb-btn " data-full="https://ik.imagekit.io/boytoto/banner235.png" data-alt="BOYTOTO" data-slug="https://ik.imagekit.io/boytoto/banner235.png" aria-label="Gambar 2"><img src="https://ik.imagekit.io/boytoto/banner235.png" alt="BOYTOTO" loading="lazy"></button><button type="button" class="pd-thumb-btn " data-full="https://ik.imagekit.io/boytoto/banner235.png" data-alt="BOYTOTO" data-slug="BOYTOTO" aria-label="Gambar 3"><img src="https://ik.imagekit.io/boytoto/banner235.png" alt="BOYTOTO" loading="lazy"></button><button type="button" class="pd-thumb-btn " data-full="https://ik.imagekit.io/boytoto/banner235.png" data-alt
11287="BOYTOTO" data-slug="BOYTOTO" aria-label="Gambar 4"><img src="https://ik.imagekit.io/boytoto/banner235.png" alt="BOYTOTO" loading="lazy"></button><button type="button" class="pd-thumb-btn " data-full="https://ik.imagekit.io/boytoto/banner235.png" data-alt="BOYTOTO" data-slug="BOYTOTO" aria-label="Gambar 5"><img src="https://ik.imagekit.io/boytoto/banner235.png" alt="BOYTOTO" loading="lazy"></button><button type="button" class="pd-thumb-btn " data-full="https://ik.imagekit.io/boytoto/banner235.png" data-alt="BOYTOTO" data-slug="BOYTOTO" aria-label="Gambar 6"><img src="https://ik.imagekit.io/boytoto/banner235.png" alt="BOYTOTO" loading="lazy"></button></div></section><section class="pd-info-card"><div id="pd-variant-combined" class="pd-variant-combined" hidden></div><div class="pd-title-row">
11288        <h1 class="pd-title" id="pd-title">BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler</h1><button type="button" class="pd-share-btn" id="pd-share-btn" aria-label="Bagikan panduan" title="Bagikan"><svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92s2.92-1.31 2.92-2.92-1.31-2.92-2.92-2.92z"/></svg></button></div>
11289<div id="pd-price-anchor">
11290</div>
11291<div class="pd-price-qr-container">
11292        <div class="pd-price-block" id="pd-price-block">
11293                <div class="pd-price-row">
11294                        <span class="pd-price-current" id="pd-price-current" >Daftar Shio</span>
11295                        <span class="pd-price-original" id="pd-price-original">Kategori 4D</span>
11296                        <span class="pd-discount-badge" id="pd-discount-badge">-95%</span></div>
11297                        <div class="pd-meta-row">
11298                                <span class="sold-inline" id="pd-sold-inline" data-role="sold-chip">Terjual Terjual 5</span>
11299                                <span class="pwp-trigger-badge" id="pwp-trigger-badge" hidden>
11300                                        <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
11301                                                <path d="M21.41 11.58l-9-9C12.05 2.22 11.55 2 11 2H4c-1.1 0-2 .9-2 2v7c0 .55.22 1.05.59 1.42l9 9c.36.36.86.58 1.41.58.55 0 1.05-.22 1.41-.59l7-7c.37-.36.59-.86.59-1.41 0-.55-.23-1.06-.59-1.42zM5.5 7C4.67 7 4 6.33 4 5.5S4.67 4 5.5 4 7 4.67 7 5.5 6.33 7 5.5 7z"/></svg>Bisa PWP</span></div>
11302                                                <div class="pd-meta-inline"><span class="stock-badge out" id="pd-stock-badge">BOYTOTO</span>
11303                                                        <span class="sku">SKU: <span id="pd-sku">BOYTOTO</span></span></div>
11304<div class="paylater-promo paylater-promo-sm paylater-mobile-only">
11305    <span class="paylater-icon">💳</span>
11306    <span class="paylater-text">
11307        <strong>Autorized Situs BOYTOTO Online</strong>
11308        <small>BOYTOTO</small>
11309    </span>
11310</div>
11311
11312<div class="stock-popover" id="pd-stock-popover" role="dialog" aria-hidden="true" aria-label="Informasi stok cabang"><div class="sp-head"><div class="sp-title">Info Daftar Shio</div><button type="button" class="sp-close" id="pd-stock-popover-close" aria-label="Tutup"><svg width="18" height="18" viewBox="0 0 24 24"><path fill="currentColor" d="M18.3 5.71L12 12.01l-6.3-6.3-1.4 1.41 6.3 6.3-6.3 6.3 1.4 1.41 6.3-6.3 6.3 6.3 1.41-1.41-6.3-6.3 6.3-6.3z"/></svg></button></div><ul class="sp-list">
11313<li class="sp-item"><div class="sp-left"><span class="sp-dot" style="--dot:#60a5fa;"></span><span class="sp-label">Asia Mega Mas</span></div><span class="sp-qty" id="sp-qty-meg">0</span><a class="sp-wa" href="https://wa.me/6281380009898?text=Halo%2C%20saya%20ingin%20tanya%20stok%20di%20Asia%20Mega%20Mas%20untuk%20produk%20https%3A%2F%2Fplazait.co.id%2Fproduct%2FBOYTOTO" target="_blank" rel="noopener" aria-label="WhatsApp Asia Mega Mas"><svg class="ico-wa" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
11313<path fill="currentColor" d="M17.472 14.382c-.297-.149-1.758-.867-2.029-.967-.273-.099-.471-.148-.669.149-.198.297-.767.966-.94 1.164-.173.198-.347.223-.644.074-.297-.149-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.297-.347.446-.52.149-.173.198-.297.298-.495.099-.198.05-.372-.025-.521-.075-.149-.669-1.612-.916-2.206-.242-.579-.487-.5-.669-.51l-.57-.01c-.198 0-.52.074-.792.372s-1.04 1.016-1.04 2.479 1.065 2.876 1.213 3.074c.149.198 2.095 3.2 5.076 4.487.709.306 1.262.489 1.694.626.712.226 1.36.194 1.872.118.571-.085 1.758-.718 2.006-1.411.248-.694.248-1.29.173-1.411-.074-.123-.272-.198-.57-.347m-5.421 7.403h-.002a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.263c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.887 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.158 11.892c0 2.096.547 4.142 1.588 5.945L0 24l6.305-1.654a11.86 11.86 0 005.67 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.485-8.413"/></svg></a></li>
11314
11315                    <li class="sp-item">
11316                      <div class="sp-left">
11317                        <span class="sp-dot" style="--dot:#34d399;"></span>
11318                        <span class="sp-label">Medan Fair</span>
11319                      </div>
11320                      <span class="sp-qty" id="sp-qty-mdf">0</span>
11321                      <a class="sp-wa"
11322                         href="https://wa.me/6285388992009?text=Halo%2C%20saya%20ingin%20tanya%20stok%20di%20Medan%20Fair%20untuk%20produk%20https%3A%2F%2Fplazait.co.id%2Fproduct%2FBOYTOTO"
11323                         target="_blank" rel="noopener" aria-label="WhatsApp Medan Fair">
11324                        <svg class="ico-wa" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
11325                            <path fill="currentColor" d="M17.472 14.382c-.297-.149-1.758-.867-2.029-.967-.273-.099-.471-.148-.669.149-.198.297-.767.966-.94 1.164-.173.198-.347.223-.644.074-.297-.149-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.297-.347.446-.52.149-.173.198-.297.298-.495.099-.198.05-.372-.025-.521-.075-.149-.669-1.612-.916-2.206-.242-.579-.487-.5-.669-.51l-.57-.01c-.198 0-.52.074-.792.372s-1.04 1.016-1.04 2.479 1.065 2.876 1.213 3.074c.149.198 2.095 3.2 5.076 4.487.709.306 1.262.489 1.694.626.712.226 1.36.194 1.872.118.571-.085 1.758-.718 2.006-1.411.248-.694.248-1.29.173-1.411-.074-.123-.272-.198-.57-.347m-5.421 7.403h-.002a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.263c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.887 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.158 11.892c0 2.096.547 4.142 1.588 5.945L0 24l6.305-1.654a11.86 11.86 0 005.67 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.485-8.413"/>
11326                          </svg>
11327                      </a>
11328                    </li>
11329
11330                    <li class="sp-item">
11331                      <div class="sp-left">
11332                        <span class="sp-dot" style="--dot:#f59e0b;"></span>
11333                        <span class="sp-label">Banda Aceh</span>
11334                      </div>
11335                      <span class="sp-qty" id="sp-qty-bda">0</span>
11336                      <a class="sp-wa"
11337                         href="https://wa.me/628116833411?text=Halo%2C%20saya%20ingin%20tanya%20stok%20di%20Banda%20Aceh%20untuk%20produk%20https%3A%2F%2Fplazait.co.id%2Fproduct%2FBOYTOTO"
11338                         target="_blank" rel="noopener" aria-label="WhatsApp Banda Aceh">
11339                        <svg class="ico-wa" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
11340                            <path fill="currentColor" d="M17.472 14.382c-.297-.149-1.758-.867-2.029-.967-.273-.099-.471-.148-.669.149-.198.297-.767.966-.94 1.164-.173.198-.347.223-.644.074-.297-.149-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.297-.347.446-.52.149-.173.198-.297.298-.495.099-.198.05-.372-.025-.521-.075-.149-.669-1.612-.916-2.206-.242-.579-.487-.5-.669-.51l-.57-.01c-.198 0-.52.074-.792.372s-1.04 1.016-1.04 2.479 1.065 2.876 1.213 3.074c.149.198 2.095 3.2 5.076 4.487.709.306 1.262.489 1.694.626.712.226 1.36.194 1.872.118.571-.085 1.758-.718 2.006-1.411.248-.694.248-1.29.173-1.411-.074-.123-.272-.198-.57-.347m-5.421 7.403h-.002a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.263c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.887 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.158 11.892c0 2.096.547 4.142 1.588 5.945L0 24l6.305-1.654a11.86 11.86 0 005.67 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.485-8.413"/>
11341                          </svg>
11342                      </a>
11343                    </li>
11344                  </ul>
11345                </div>
11346
11347                            </div>
11348            <div class="pd-qr-print" style="display:none;"><div class="pd-qr-label">Scan untuk lihat panduan:</div><img src="https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=https%3A%2F%2Fplazait.co.id%2Fproduct%2FBOYTOTO" alt="QR Code" class="pd-qr-image"></div>
11349            </div>
11350                                    <div id="pd-variants-anchor"></div>
11351
11352
11353
11354
11355
11356            <div class="pd-compare-section">
11357                <button type="button" class="pd-compare-btn" id="pd-compare-btn" data-product-id="325">
11358                    <svg class="compare-icon" width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
11359                        <path d="M21,9L17,5V8H10V10H17V13M7,11L3,15L7,19V16H14V14H7V11Z"/>
11360                    </svg>
11361                    <span class="compare-text">Bandingkan Panduan</span>
11362                    <span class="compare-remove" hidden>
11363                        <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
11364                            <path d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"/>
11365                        </svg>
11366                        Hapus dari Perbandingan
11367                    </span>
11368                </button>
11369                <div class="pd-compare-info">
11370                    <small class="compare-note" id="compare-note">Bandingkan hingga 5 panduan dengan bantuan AI</small>
11371                </div>
11372            </div>
11373
11374           <div class="pd-short-desc"><p><strong>Informasi BOYTOTO:</strong></p><ul><li>Fokus Panduan: Hiburan Game Online</li><li>Fungsi: Menikmati sensasi permainan di atas langit</li><li>Kategori: Pilihan game seru &amp; modern</li><li>Penggunaan: Media hiburan interaktif</li><li>Format: Akses Serbaguna</li><li>Akses: Smartphone &amp; Komputer</li><li>Catatan: Selalu utamakan kenyamanan &amp; strategi bermain</li><li>Daftar Game: <a href="https://mgsco.org/">Lihat Di Sini</a></li></ul></div><div class="BOYTOTO-container-v1 mini">
11375<div class="BOYTOTO-container">
11376
11377
11378  <!-- HEADER -->
11379<div class="BOYTOTO-container-v3 mini">
11380   <div class="rtp-container-v3">
11381      <div class="rtp-machine-v3">
11382         <h2 class="rtp-title-v3">🌌 BOYTOTO - Di Balik Langit Ada Game Seru Menanti 🌌</h2>
11383
11384         <div class="rtp-controls-v3">
11385            <label>
11386               PILIH TIPE
11387               <select id="providerFilter">
11388                  <option value="ALL">SEMUA PERMAINAN</option>
11389               </select>
11390            </label>
11391
11392            <label>
11393               LEVEL SERU
11394               <select id="slotTypeFilter">
11395                  <option value="HIGH">RINGAN</option>
11396                  <option value="MEDIUM">SEDANG</option>
11397                  <option value="BOOST">MAKSIMAL</option>
11398               </select>
11399            </label>
11400
11401            <label>
11402               NOMOR RUANG
11403               <input id="minRtp" type="number" min="0" max="9999" step="1" value="0">
11404            </label>
11405
11406            <button id="btnRtpRandom" type="button">
11407               <span class="icon"></span> TEMUKAN SERUNYA
11408            </button>
11409         </div>
11410
11411         <div class="rtp-main-result-v3" id="rtpResult">
11412            <div class="rtp-status-v3" id="rtpStatus">— TERSEDIA</div>
11413
11414            <div class="rtp-highlight-box">
11415               <div class="rtp-col-left">
11416                  <div class="k">Ruang Game</div>
11417                  <div class="v-game" id="rProvider">-</div>
11418
11419                  <div class="provider-info">
11420                     <span class="k-small">Sensasi:</span>
11421                     <span class="v-small" id="rSlotType">-</span>
11422                     <span class="k-small" style="margin-left:10px;">Indeks:</span>
11423                     <span class="v-small" id="rPercent">-</span>
11424                  </div>
11425               </div>
11426
11427               <div class="rtp-col-right">
11428                  <div class="k-rtp">TEMA</div>
11429                  <div class="v-rtp-huge" id="rRtpValue">-</div>
11430               </div>
11431            </div>
11432
11433            <div class="rtp-details-v3">
11434               <div class="rtp-detail-item">
11435                  <div class="k-detail">Katalog</div>
11436                  <div class="v-detail" id="rJam">-</div>
11437               </div>
11438
11439               <div class="rtp-detail-item">
11440                  <div class="k-detail">Fitur</div>
11441                  <div class="v-detail" id="rPola">-</div>
11442               </div>
11443
11444               <div class="rtp-detail-item">
11445                  <div class="k-detail">Aksesibilitas</div>
11446                  <div class="v-detail" id="rStatusText">-</div>
11447               </div>
11448            </div>
11449
11450            <div class="rtp-tip-v3" id="rTip">
11451               Saran: Rasakan sensasi hiburan game interaktif dengan atmosfer kekinian bersama BOYTOTO.
11452            </div>
11453         </div>
11454
11455         <div class="rtp-alts-v3">
11456            <div class="rtp-alts-title-v3">3 Langkah Menuju Sensasi Game Seru</div>
11457            <div class="rtp-alt-list-v3" id="rtpAltList"></div>
11458         </div>
11459
11460         <div class="rtp-note-v3">
11461            Copyright © BOYTOTO
11462         </div>
11463
11464  <!-- HEADER -->
11465<div class="BOYTOTO-container-v3 mini">
11466   <div class="rtp-container-v3">
11467      <div class="rtp-machine-v3">
11468         <h2 class="rtp-title-v3">✨ BOYTOTO - Di Balik Langit Ada Game Seru Menanti ✨</h2>
11469
11470         <div class="rtp-controls-v3">
11471            <label>
11472               PILIH HIBURAN
11473               <select id="providerFilter">
11474                  <option value="ALL">SEMUA APLIKASI</option>
11475               </select>
11476            </label>
11477
11478            <label>
11479               MODE PERMAINAN
11480               <select id="slotTypeFilter">
11481                  <option value="HIGH">SANTAI</option>
11482                  <option value="MEDIUM">PETUALANG</option>
11483                  <option value="BOOST">TANTANGAN</option>
11484               </select>
11485            </label>
11486
11487            <label>
11488               KODE AKSES
11489               <input id="minRtp" type="number" min="0" max="9999" step="1" value="
114890">
11490            </label>
11491
11492            <button id="btnRtpRandom" type="button">
11493               <span class="icon"></span> MULAI JELAJAHI
11494            </button>
11495         </div>
11496
11497         <div class="rtp-main-result-v3" id="rtpResult">
11498            <div class="rtp-status-v3" id="rtpStatus">— SIAP MAIN</div>
11499
11500            <div class="rtp-highlight-box">
11501               <div class="rtp-col-left">
11502                  <div class="k">Kategori Game</div>
11503                  <div class="v-game" id="rProvider">-</div>
11504
11505                  <div class="provider-info">
11506                     <span class="k-small">Mode:</span>
11507                     <span class="v-small" id="rSlotType">-</span>
11508                     <span class="k-small" style="margin-left:10px;">ID:</span>
11509                     <span class="v-small" id="rPercent">-</span>
11510                  </div>
11511               </div>
11512
11513               <div class="rtp-col-right">
11514                  <div class="k-rtp">ATMOSFER</div>
11515                  <div class="v-rtp-huge" id="rRtpValue">-</div>
11516               </div>
11517            </div>
11518
11519            <div class="rtp-details-v3">
11520               <div class="rtp-detail-item">
11521                  <div class="k-detail">Koleksi</div>
11522                  <div class="v-detail" id="rJam">-</div>
11523               </div>
11524
11525               <div class="rtp-detail-item">
11526                  <div class="k-detail">Petunjuk</div>
11527                  <div class="v-detail" id="rPola">-</div>
11528               </div>
11529
11530               <div class="rtp-detail-item">
11531                  <div class="k-detail">Kondisi</div>
11532                  <div class="v-detail" id="rStatusText">-</div>
11533               </div>
11534            </div>
11535
11536            <div class="rtp-tip-v3" id="rTip">
11537               Tips: Nikmati ragam pilihan permainan seru dan nuansa kekinian yang menanti di balik langit bersama BOYTOTO.
11538            </div>
11539         </div>
11540
11541         <div class="rtp-alts-v3">
11542            <div class="rtp-alts-title-v3">3 Tahap Menemukan Keseruan Game</div>
11543            <div class="rtp-alt-list-v3" id="rtpAltList"></div>
11544         </div>
11545
11546         <div class="rtp-note-v3">
11547            Hak Cipta © BOYTOTO
11548         </div>
11549      </div>
11550   </div>
11551
11552   <style>
11553@import url("https://fonts.googleapis.com/css2?family=Orbitron:wght@600;700;800&display=swap");
11554
11555.rtp-container-v3{
11556   display:flex;
11557   justify-content:center;
11558   padding:16px 10px;
11559   font-family:"Orbitron","Poppins",system-ui,sans-serif;
11560}
11561
11562.rtp-machine-v3{
11563   position: relative;
11564    width: 100%;
11565    max-width: 520px;
11566    padding: 16px;
11567    border-radius: 18px;
11568    overflow: hidden;
11569    isolation: isolate;
11570    background: radial-gradient(circle at 15% 0%, rgb(255 0 0 / 56%), transparent 48%), radial-gradient(circle at 85% 100%, rgb(255 0 0 / 63%), #8a0000f5 55%), linear-gradient(145deg, #3f0808, #050805);
11571    border: 1px solid rgb(231 0 0);
11572    box-shadow: inset 0 0 18px rgb(0 0 0), 0 0 18px rgb(255 0 0), 0 0 40px rgb(255 0 0);
11573    color: #ff0000;
11574    filter: drop-shadow(0 0 14px rgba(170, 255, 0, .35));
11575}
11576
11577.rtp-machine-v3::before{
11578   content:"";
11579   position:absolute;
11580   inset:0;
11581   background:
11582   linear-gradient(
11583   120deg,
11584   transparent 40%,
11585   rgba(183,255,0,.08),
11586   transparent 65%
11587   );
11588   animation:hudSweep 7s linear infinite;
11589   pointer-events:none;
11590   opacity:.8;
11591}
11592
11593.rtp-machine-v3::after{
11594   content:"";
11595   position:absolute;
11596   inset:0;
11597   background:
11598   repeating-linear-gradient(
11599   0deg,
11600   rgba(255,255,255,.02) 0px,
11601   rgba(255,255,255,.02) 1px,
11602   transparent 1px,
11603   transparent 3px
11604   );
11605   mix-blend-mode:soft-light;
11606   pointer-events:none;
11607}
11608
11609.rtp-title-v3{
11610   text-align:center;
11611   margin:0 0 12px;
11612   font-size:15px;
11613   font-weight:900;
11614   letter-spacing:1px;
11615   color:#000000;
11616   text-shadow:
11617   0 0 8px rgb(255 0 0 / 90%), 0 0 18px rgb(255 0 0 / 45%);
11618}
11619
11620.rtp-controls-v3{
11621   display:grid;
11622   grid-template-columns:1fr 1fr;
11623   gap:10px;
11624   padding:12px;
11625   border-radius:14px;
11626
11627   background:rgba(10,18,5,0.72);
11628
11629   border:1px solid rgba(166,255,0,0.28);
11630
11631   box-shadow:
11632   inset 0 0 14px rgba(166,255,0,0.12),
11633   0 0 12px rgba(60,255,0,0.08);
11634}
11635
11636.rtp-controls-v3 label{
11637   display:grid;
11638   gap:6px;
11639   font-size:10px;
11640   letter-spacing:.8px;
11641   text-transform:uppercase;
11642   color:#ff5757;
11643}
11644
11645.rtp-controls-v3 select,
11646.rtp-controls-v3 input{
11647       background: rgba(5, 10, 4, .95);
11648    color: #ffd7d7;
11649    border: 1px solid rgb(255 0 0 / 35%);
11650    border-radius: 10px;
11651    padding: 7px 9px;
11652    font-weight: 700;
11653    font-size: 12px;
11654    outline: none;
11655    box-shadow: inset 0 0 10px rgb(255 0 0 / 80%), 0 0 8px rgba(120, 255, 0, .05);
11656}
11657
11658.rtp-controls-v3 select:focus,
11659.rtp-controls-v3 input:focus{
11660   border-color:#b7ff00;
11661   box-shadow:
11662   0 0 12px rgba(166,255,0,.35),
11663   inset 0 0 12px rgba(166,255,0,.15);
11664}
11665
11666#btnRtpRandom{
11667   grid-column:span 2;
11668
11669   display:flex;
11670   align-items:center;
11671   justify-content:center;
11672   gap:8px;
11673
11674   background:linear-gradient(145deg, #ff0000, #ff0000);
11675
11676   border:none;
11677   color:#111;
11678
11679   font-weight:900;
11680   letter-spacing:.8px;
11681
11682   border-radius:14px;
11683   cursor:pointer;
11684
11685   padding:10px 12px;
11686   font-size:12px;
11687
11688   box-shadow:
11689   0 0 18px rgb(255 0 0), inset 0 0 10px rgba(255, 255, 255, .18);
11690
11691   transition:.25s ease;
11692}
11693
11694#btnRtpRandom:hover{
11695   transform:translateY(-2px);
11696   box-shadow:
116970 0 24px rgba(139, 0, 0, 0.8),
116980 0 45px rgba(139, 0, 0, 0.45);
11699}
11700
11701#btnRtpRandom:active{
11702   transform:scale(.98);
11703}
11704
11705#btnRtpRandom:disabled{
11706   opacity:.75;
11707   cursor:not-allowed;
11708}
11709
11710.rtp-main-result-v3{
11711       margin-top: 12px;
11712    padding: 12px;
11713    border-radius: 14px;
11714    background: rgb(57 6 6 / 82%);
11715    border: 1px solid rgb(255 0 0);
11716    box-shadow: inset 0 0 16px rgb(255 0 0 / 80%), 0 0 10px rgb(255 0 0 / 65%);
11717}
11718
11719.rtp-status-v3{
11720   text-align:center;
11721   font-weight:800;
11722   letter-spacing:.8px;
11723   color:#f60000;
11724   margin-bottom:10px;
11725   font-size:11px;
11726}
11727
11728.rtp-highlight-box{
11729   display:flex;
11730   justify-content:space-between;
11731   gap:12px;
11732   padding-bottom:10px;
11733   margin-bottom:10px;
11734
11735   border-bottom:1px dashed rgba(166,255,0,0.22);
11736}
11737
11738.k,
11739.k-rtp{
11740   font-size:10px;
11741   color:#ff0000;
11742   text-transform:uppercase;
11743}
11744
11745.v-game{
11746   font-size:14px;
11747   font-weight:900;
11748   color:#ffffff;
11749   margin-top:6px;
11750   line-height:1.2;
11751
11752   text-shadow:0 0 12px rgba(166,255,0,.22);
11753}
11754
11755.provider-info{
11756   margin-top:8px;
11757   font-size:11px;
11758   color:#e9ffd0;
11759   opacity:.95;
11760}
11761
11762.k-small{
11763   color:#d10000;
11764   font-size:10px;
11765}
11766
11767.v-small{
11768   color:#ffffff;
11769   font-weight:900;
11770}
11771
11772.v-rtp-huge{
11773   font-size:2.4em;
11774   font-weight:900;
11775
11776   color:#b7ff00;
11777
11778   text-shadow:
11779   0 0 10px rgba(166,255,0,0.7),
11780   0 0 20px rgba(60,255,0,0.4);
11781
11782   line-height:1;
11783   letter-spacing:1px;
11784}
11785
11786.rtp-details-v3{
11787   display:grid;
11788   grid-template-columns:repeat(3,1fr);
11789   gap:8px;
11790
11791   background:rgba(5,12,4,.45);
11792
11793   padding:10px;
11794   border-radius:12px;
11795}
11796
11797.k-detail{
11798   font-size:9px;
11799   color:#ff0000;
11800   text-transform:uppercase;
11801}
11802
11803.v-detail{
11804   font-size:12px;
11805   font-weight:800;
11806   color:#ffffff;
11807   margin-top:4px;
11808   letter-spacing:.8px;
11809}
11810
11811.rtp-tip-v3{
11812   margin-top:10px;
11813   font-size:10px;
11814   color:#e6ffd0;
11815   opacity:.9;
11816   text-align:center;
11817}
11818
11819.rtp-alts-v3{
11820   margin-top:12px;
11821   padding:12px;
11822   border-radius:14px;
11823
11824   background:rgba(5,10,4,.62);
11825
11826   border:1px solid rgba(166,255,0,0.22);
11827}
11828
11829.rtp-alts-title-v3{
11830   font-weight:900;
11831   color:#ed0000;
11832   margin-bottom:10px;
11833   font-size:12px;
11834}
11835
11836.rtp-alt-list-v3{
11837   display:grid;
11838   grid-template-columns:1fr;
11839   gap:8px;
11840}
11841
11842.rtp-alt-item{
11843   background:rgba(5,15,5,.88);
11844
11845   border-radius:12px;
11846   padding:10px;
11847
11848   border-left:3px solid rgba(166,255,0,0.9);
11849
11850   box-shadow:
11851   0 0 12px rgba(60,255,0,.08);
11852}
11853
11854.rtp-alt-item .g{
11855   font-weight:900;
11856   color:#ffffff;
11857   font-size:12px;
11858}
11859
11860.rtp-alt-item .p{
11861   font-size:10px;
11862   color:#d9ff7a;
11863   margin-top:4px;
11864}
11865
11866.rtp-alt-item .r{
11867   margin-top:6px;
11868   font-weight:900;
11869   color:#b7ff00;
11870   letter-spacing:.8px;
11871   font-size:12px;
11872
11873   text-shadow:0 0 8px rgba(166,255,0,.4);
11874}
11875
11876.rtp-note-v3{
11877   margin-top:10px;
11878   font-size:10px;
11879   color:#e8ffd4;
11880   opacity:.85;
11881   text-align:center;
11882}
11883
11884@media(max-width:420px){
11885   .rtp-highlight-box{
11886      flex-direction:column;
11887   }
11888
11889   .rtp-details-v3{
11890      grid-template-columns:1fr;
11891   }
11892}
11893
11894@keyframes hudSweep{
11895   0%{
11896      transform:translateX(-110%);
11897   }
11898   100%{
11899      transform:translateX(110%);
11900   }
11901}
11902</style>
11903
11904   
11904<script>
11905      const SHIO_LIST = ["Tikus","Kerbau","Macan","Kelinci","Naga","Ular","Kuda","Kambing","Monyet","Ayam","Anjing","Babi"];
11906      const $ = (q) => document.querySelector(q);
11907
11908      function renderGuide(){
11909         const wrap = $("#rtpAltList");
11910         if(!wrap) return;
11911         const steps = [
11912            ["1. Tentukan Nomor", "Siapkan nomor 2D, 3D, atau 4D yang ingin kamu cek."],
11913            ["2. Buka Daftar Shio", "Gunakan daftar shio yang sedang berlaku pada halaman hari ini."],
11914            ["3. Cocokkan Kelompok", "Cari nomor tersebut pada kelompok shio yang sesuai dan jadikan sebagai referensi kategori."]
11915         ];
11916         wrap.innerHTML = "";
11917         steps.forEach(([title,text]) => {
11918            const el = document.createElement("div");
11919            el.className = "rtp-alt-item";
11920            el.innerHTML = `<div class="g">${title}</div><div class="p">${text}</div>`;
11921            wrap.appendChild(el);
11922         });
11923      }
11924
11925      (function initShioGuide(){
11926         const select = $("#providerFilter");
11927         const format = $("#slotTypeFilter");
11928         const input = $("#minRtp");
11929         const button = $("#btnRtpRandom");
11930         if(!select || !format || !input || !button) return;
11931
11932         SHIO_LIST.forEach(name => {
11933            const option = document.createElement("option");
11934            option.value = name;
11935            option.textContent = name.toUpperCase();
11936            select.appendChild(option);
11937         });
11938
11939         renderGuide();
11940         $("#rtpStatus").textContent = "— SIAP —";
11941         $("#rTip").textContent = "Tips: Shio hanya dipakai sebagai kategori angka dan tidak menjamin hasil permainan.";
11942
11943         button.addEventListener("click", () => {
11944            const shio = select.value === "ALL" ? "Pilih salah satu dari 12 shio" : select.value;
11945            const formatLabel = format.options[format.selectedIndex].textContent + "D".replace("DD","D");
11946            const nomor = String(Math.max(0, Number(input.value) || 0));
11947
11948            $("#rProvider").textContent = shio;
11949            $("#rSlotType").textContent = format.options[format.selectedIndex].textContent;
11950            $("#rPercent").textContent = `Nomor ${nomor}`;
11951            $("#rRtpValue").textContent = format.options[format.selectedIndex].textContent;
11952            $("#rJam").textContent = "Daftar Shio Hari Ini";
11953            $("#rPola").textContent = "Cocokkan nomor ke kelompok shio";
11954            $("#rStatusText").textContent = "REFERENSI";
11955            $("#rtpStatus").textContent = "— CATATAN SIAP —";
11956         });
11957      })();
11958   </script>
11958
11959
11960
11961
11962
11963
11964
11965
11966
11967
11968<style>
11969body{background:#ebf2fc; font-family:Poppins,sans-serif; color:white;}
11970.BOYTOTO-container{
11971max-width: 520px;
11972    margin: auto;
11973    padding: 15px;
11974    border-radius: 12px;
11975    background: #a10808;
11976    border: 2px solid #ca0000;
11977    box-shadow: 0 0 20px #d90000;
11978}
11979
11980.BOYTOTO-header{
11981  font-weight:900;
11982  text-align:center;
11983  color:#ff4c4c;
11984  text-shadow:0 0 12px #5f90d9;
11985  margin-bottom:12px;
11986}
11987
11988.BOYTOTO-controls{
11989  display:grid;
11990  grid-template-columns:1fr 1fr;
11991  gap:10px;
11992  margin-bottom:12px;
11993}
11994.BOYTOTO-controls label{
11995  display:flex; flex-direction:column;
11996  font-size:11px; color:#ff9999;
11997}
11998.BOYTOTO-controls select,
11999.BOYTOTO-controls input{
12000  padding:8px;
12001  border-radius:8px;
12002  border:1px solid #ff4c4c;
12003  background:#2b0000;
12004  color:white;
12005}
12006#btnGenerate{
12007  grid-column:span 2;
12008  padding:12px;
12009  background:#5f90d9;
12010  border:none;
12011  color:black;
12012  font-weight:900;
12013  cursor:pointer;
12014  margin-top:5px;
12015  box-shadow:0 0 15px #ff4c4c;
12016}
12017
12018.BOYTOTO-result{
12019  margin-top:12px;
12020  padding:12px;
12021  border-radius:10px;
12022  background:rgba(255,0,0,0.05);
12023  border:1px solid #ff4c4c;
12024}
12025
12026.BOYTOTO-main div, .BOYTOTO-details div{
12027  margin:5px 0;
12028}
12029
12030#rPrediction{
12031  font-size:24px;
12032  font-weight:900;
12033  color:#5f90d9;
12034}
12035
12036.BOYTOTO-alts{
12037  margin-top:10px;
12038}
12039.BOYTOTO-alts h4{margin-bottom:6px;}
12040#BOYTOTOAltList div{
12041  background:#2b0000;
12042  padding:6px 10px;
12043  margin:3px 0;
12044  border-radius:8px;
12045  border-left:3px solid #5f90d9;
12046}
12047  .bank-tab.active, .bank-tab:hover {
12048    background: #df0000;
12049    color: #fff;
12050    border-color: #df0000;
12051    box-shadow: 0 3px 10px -4px rgb(240 29 29 / 60%);
12052}
12053
12054.BOYTOTO-disclaimer{
12055  font-size:10px;
12056  margin-top:8px;
12057  text-align:center;
12058  color:#ff9999;
12059}
12060</style>
12061
12062<script>
12063const leagues = ["Premier League","La Liga","Serie A"];
12064const tips = ["Analisis statistik tim","Performa terakhir","Cek head-to-head"];
12065function rand(arr){return arr[Math.floor(Math.random()*arr.length)];}
12066
12067var _bgBtn=document.getElementById("btnGenerate");if(_bgBtn)_bgBtn.onclick=function(){
12068  const type = document.getElementById("betTypeFilter").value;
12069  const league = rand(leagues);
12070
12071  let pred;
12072  if(type==="1X2") pred=rand(["1","X","2"]);
12073  else if(type==="OVER_UNDER") pred=rand(["Over","Under"]);
12074  else pred=rand(["Home -1","Away +1","Draw"]);
12075
12076  document.getElementById("rLeague").innerText=league;
12077  document.getElementById("rPrediction").innerText=pred;
12078  document.getElementById("rBetType").innerText=type;
12079  document.getElementById("rConf").innerText=Math.floor(Math.random()*40+60)+"%";
12080  document.getElementById("rTip").innerText=rand(tips);
12081  document.getElementById("rOdds").innerText=(Math.random()*3+1).toFixed(2);
12082
12083  // alternatif
12084  const alt=document.getElementById("BOYTOTOAltList"); alt.innerHTML="";
12085  for(let i=0;i<3;i++){let d=document.createElement("div"); d.innerText=rand(["1","X","2"]); alt.appendChild(d);}
12086
12087  document.getElementById("BOYTOTOStatus").innerText="— READY —";
12088}
12089</script>
12089
12090
12091
12092
12093
12094
12095            <div class="pwp-deals-section" id="pwp-deals-section" hidden data-product-id="325" data-variant-id="0" data-category-path="Panduan Shio/BOYTOTO/Kategori Angka"><div class="pwp-deals-header"><svg class="pwp-icon" width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M21.41 11.58l-9-9C12.05 2.22 11.55 2 11 2H4c-1.1 0-2 .9-2 2v7c0 .55.22 1.05.59 1.42l9 9c.36.36.86.58 1.41.58.55 0 1.05-.22 1.41-.59l7-7c.37-.36.59-.86.59-1.41 0-.55-.23-1.06-.59-1.42zM5.5 7C4.67 7 4 6.33 4 5.5S4.67 4 5.5 4 7 4.67 7 5.5 6.33 7 5.5 7z"/></svg><div class="pwp-text"><h3 class="pwp-title">Penawaran Spesial PWP (Purchase with Purchase)</h3><p class="pwp-subtitle">Gunakan daftar shio ini sebagai referensi kategori angka 4D</p></div></div><div class="pwp-deals-loading"><div class="pwp-spinner"></div><p>Memuat penawaran...</p></div><div class="pwp-deals-grid" id="pwp-deals-grid"></div><p class="pwp-cart-note">💡 Cocokkan nomor ke kelompok shio yang sesuai dan gunakan sebagai catatan pendukung</p></div>
12096        </div>
12097        </div>
12098        </section>
12099
12100        <aside class="pd-sidebar">
12101            <div class="pd-side-card pd-buy-card">
12102                <form class="pd-buy-form" id="pd-buy-form" data-pid="325">
12103                    <input type="hidden" name="variant_id" id="pd-variant-id" value="0">
12104                    <div>
12105                        <label for="qty" class="qty-label">Qty</label>
12106                        <input type="number" id="qty" name="qty" value="0" min="1"
12107                               max="0"
12108                               disabled>
12109                    </div>
12110                    <div class="pd-actions">
12111                        <button type="submit" class="btn-add-cart" id="btn-add-cart" disabled>Simpan Panduan</button>
12112                        <button type="button" class="btn-buy" id="btn-buy" data-id="278426" disabled >Beli</button>
12113                        <button type="button" class="btn-chat-icon" id="btn-chat-icon" aria-label="Tanya Shio" title="Tanya Shio"><svg viewBox="0 0 640 640" fill="currentColor"><path d="M320 128C241 128 175.3 185.3 162.3 260.7C171.6 257.7 181.6 256 192 256L208 256C234.5 256 256 277.5 256 304L256 400C256 426.5 234.5 448 208 448L192 448C139 448 96 405 96 352L96 288C96 164.3 196.3 64 320 64C443.7 64 544 164.3 544 288L544 456.1C544 522.4 490.2 576.1 423.9 576.1L336 576L304 576C277.5 576 256 554.5 256 528C256 501.5 277.5 480 304 480L336 480C362.5 480 384 501.5 384 528L384 528L424 528C463.8 528 496 495.8 496 456L496 435.1C481.9 443.3 465.5 447.9 448 447.9L432 447.9C405.5 447.9 384 426.4 384 399.9L384 303.9C384 277.4 405.5 255.9 432 255.9L448 255.9C458.4 255.9 468.3 257.5 477.7 260.6C464.7 185.3 399.1 127.9 320 127.9z"/></svg></button>
12114                                            </div>
12115                </form>
12116                <div class="paylater-promo paylater-promo-md paylater-desktop-only">
12117    <span class="paylater-icon">💳</span>
12118    <span class="paylater-text">
12119       <strong>Layanan BOYTOTO</strong>
12120        <small>Keseruan Game Di Balik Langit</small>
12121    </span>
12122</div>            </div>
12123
12124
12125
12126                        <div class="pd-side-card qoala-banner-card">
12127                <div class="qoala-banner">
12128                    <div class="qoala-banner-content">
12129                        <div class="qoala-icon">
12130                            <center><img src="https://ik.imagekit.io/boytoto/logo.webp" alt="BOYTOTO" class="qoala-shield-img"></center>
12131                        </div>
12132                        <div class="qoala-text">
12133                            <div class="qoala-title">Lihat Daftar BOYTOTO Terbaru</div>
12134                            <div class="qoala-subtitle">Verified</div>
12135                        </div>
12136                    </div>
12137                    <a href="https://mgsco.org/boykita/" class="qoala-cta"
12138                       data-ga-event="qoala_banner_click"
12139                       data-product-id="325"
12140                       data-product-name="BOYTOTO"
12141                       data-category-path="Panduan Shio/BOYTOTO/Kategori Angka" target="_blank">Info</a>
12142                </div>
12143<style>
12144.qoala-banner-card{padding:0;overflow:hidden}
12145
12146.qoala-banner{
12147  background:linear-gradient(135deg, #cd0000 0%, #c90000 100%);
12148  padding:12px 16px;
12149  display:flex;
12150  align-items:center;
12151  justify-content:space-between;
12152  color:white;
12153  text-decoration:none;
12154  min-height:60px;
12155}
12156
12157.qoala-banner-content{
12158  display:flex;
12159  align-items:center;
12160  gap:12px;
12161}
12162
12163.qoala-icon{
12164  display:flex;
12165  align-items:center;
12166  justify-content:center;
12167}
12168
12169.qoala-shield-img{
12170  width:36px;
12171  height:36px;
12172  object-fit:contain;
12173  opacity:0.9;
12174}
12175
12176.qoala-title{
12177  font-weight:600;
12178  font-size:14px;
12179  line-height:1.2;
12180}
12181
12182.qoala-subtitle{
12183  font-size:12px;
12184  opacity:0.9;
12185  line-height:1.2;
12186}
12187
12188.qoala-cta{
12189  background:rgba(255,255,255,0.15);
12190  color:white;
12191  padding:6px 12px;
12192  border-radius:6px;
12193  font-size:12px;
12194  font-weight:600;
12195  text-decoration:none;
12196  transition:background 0.2s ease;
12197  border:1px solid rgba(255,80,80,0.4);
12198}
12199
12200.qoala-cta:hover{
12201  background:rgba(255,60,60,0.25);
12202  color:white;
12203}
12204
12205@media(max-width:768px){
12206  .qoala-banner{padding:10px 12px}
12207  .qoala-title{font-size:17px}
12208  .qoala-subtitle{font-size:11px}
12209}
12210</style>
12211            </div>
12212
12213                        <div class="pd-side-card pd-installment-card2"
12214                 id="inst-card"
12215                 data-base-price="20789000"
12216                 data-zero-smart="0">
12217                <div class="inst-head">
12218                   <h3>Panduan & Fitur Game BOYTOTO</h3>
12219                                    </div>
12220
12221                <div class="inst-bank-tabs" role="tablist" aria-label="Bank">
12222                    <button type="button" class="bank-tab active" data-bank="Lucky Draw" aria-selected="true">Koleksi Game</button>
12223                    <button type="button" class="bank-tab" data-bank="Cara Cocokkan Nomor" aria-selected="false">Cara Bermain</button>
12224                    <button type="button" class="bank-tab" data-bank="Catatan Penggunaan" aria-selected="false">Panduan Fitur</button>
12225                </div>
12226
12227                <ul class="inst-month-list" id="inst-month-list" aria-label="Daftar cicilan per bulan"></ul>
12228                <div class="inst-note" id="inst-zero-note" style="display:none;">0% Cicilan khusus bank terpilih*</div>
12229            </div>
12230
12231            <!-- MECHA BLUE FINAL BOSS REVIEW SLIDER -->
12232<div class="tnt-review-mecha">
12233
12234<div class="tnt-header">
12235<h2>Komentar Pengguna Tentang Keseruan BOYTOTO</h2>
12236
12237</div>
12238
12239<div class="tnt-slider" id="tntSlider">
12240
12241<div class="tnt-slide active">
12242  <div class="tnt-name">Bintang – Jakarta</div>
12243  <div class="tnt-comment">"Main di BOYTOTO beneran seru banget! Pilihan game di balik langit ini beragam dan tampilannya kekinian, bikin betah buat ngisi waktu luang sambil nikmati hiburan menarik."</div>
12244</div>
12245
12246<div class="tnt-slide">
12247  <div class="tnt-name">Reza – Medan</div>
12248  <div class="tnt-comment">"Sensasi gamenya terasa beda dan fresh. Penataan mode permainannya simpel jadi gampang diakses dari HP, pas banget buat yang cari suasana bermain baru yang interaktif."</div>
12249</div>
12250
12251<div class="tnt-slide">
12252  <div class="tnt-name">Aditya – Yogyakarta</div>
12253  <div class="tnt-comment">"Fitur-fiturnya lengkap dan banyak pilihan mode seru. Atmosfer gamenya bener-bener modern, jadi makin asik jelajahi tiap tantangan game yang ada di BOYTOTO."</div>
12254</div>
12255
12256
12257
12258
12259
12260</div>
12261</div>
12262
12263</aside>
12264</div>
12265
12266<style>
12267.tnt-review-mecha{
12268  background: radial-gradient(circle at center, #da0000, #350000);
12269  border: 2px solid #aa0000;
12270  border-radius: 14px;
12271  padding: 20px;
12272  color: #dbdbdb;
12273  font-family: 'Orbitron', sans-serif;
12274  position: relative;
12275  overflow: hidden;
12276  box-shadow: 0 0 25px #990000, inset 0 0 20px rgba(255, 0, 0, 0.2);
12277}
12278
12279/* scanline */
12280.tnt-review-mecha::before{
12281  content:"";
12282  position:absolute;
12283  inset:0;
12284  background: repeating-linear-gradient(
12285    to bottom,
12286    rgba(255,0,0,0.08),
12287    rgba(255,0,0,0.08) 1px,
12288    transparent 2px,
12289    transparent 4px
12290  );
12291  pointer-events:none;
12292}.bank-tab.active, .bank-tab:hover
12293
12294
12295
12296.tnt-header h2{
12297  font-size: 18px;
12298  color:#000000;
12299  text-shadow:0 0 10px #d71414, 0 0 20px #d10000;
12300  margin-bottom:5px;
12301}
12302
12303.tnt-header p{
12304  font-size:12px;
12305  opacity:0.85;
12306  margin-bottom:15px;
12307}
12308
12309.tnt-slider{
12310  position:relative;
12311  height:90px;
12312}
12313
12314.tnt-slide{
12315  position:absolute;
12316  width:100%;
12317  opacity:0;
12318  transform:translateX(40px);
12319  transition:all .6s ease;
12320}
12321  .tenor-badge {
12322    background: #df0000;
12323    color: #fff;
12324    font-weight: 700;
12325    min-width: 42px;
12326    text-align: center;
12327    padding: .38rem .4rem;
12328    font-size: .8rem;
12329    border-radius: 10px;
12330    box-shadow: 0 3px 10px -4px rgb(211 3 3);
12331}
12332
12333.tnt-slide.active{
12334  opacity:1;
12335  transform:translateX(0);
12336}
12337
12338.tnt-name{
12339font-size: 14px;
12340    color: #000000;
12341    margin-bottom: 5px;
12342    text-shadow: 0 0 6px #290000;
12343}
12344
12345.tnt-comment{
12346  font-size:13px;
12347  line-height:1.5;
12348}
12349
12350/* glowing border animation */
12351@keyframes pulseGlow{
12352  0%{box-shadow:0 0 10px #d71414;}
12353  50%{box-shadow:0 0 25px #d71414;}
12354  100%{box-shadow:0 0 10px #d71414;}
12355}
12356
12357.tnt-review-mecha{
12358  animation:pulseGlow 2.5s infinite;
12359}
12360</style>
12361
12362<script>
12363let slides = document.querySelectorAll('.tnt-slide');
12364let index = 0;
12365
12366setInterval(()=>{
12367  slides[index].classList.remove('active');
12368  index = (index + 1) % slides.length;
12369  slides[index].classList.add('active');
12370}, 3000);
12371</script>
12371
12372
12373        <div class="artikel-glow-BOYTOTO">
12374
12375  <div class="artikel-shine"></div>
12376
12377  <h1>BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler</h1>
12378  <p style="text-align: justify;">Di tengah berkembangnya dunia permainan online, banyak pemain mencari platform yang mampu menghadirkan pengalaman bermain yang nyaman, lengkap, dan mudah diakses. <a href="https://mgsco.org/">BOYTOTO</a> hadir sebagai showroom situs toto online yang menyediakan beragam pilihan permainan menarik dari provider ternama. Dengan koleksi game yang selalu diperbarui, setiap pemain dapat menikmati variasi permainan tanpa merasa bosan.</p>
12379
12380  <p style="text-align: justify;">Boytoto menghadirkan berbagai permainan <a href="https://mgsco.org/">Toto Online</a> dari sejumlah provider populer yang dikenal memiliki kualitas grafis, efek suara, dan fitur permainan yang menarik. Kehadiran banyak provider membuat variasi game semakin lengkap sehingga pemain dapat mencoba pengalaman bermain yang berbeda setiap harinya. Selain menghadirkan tema permainan yang beragam, setiap provider juga memiliki ciri khas tersendiri dalam menyajikan fitur bonus, animasi, dan mekanisme permainan.</p>
12381</div>
12382
12383<style>
12384.artikel-glow-BOYTOTO{
12385      max-width: 820px;
12386    margin: 40px auto;
12387    padding: 36px;
12388    box-sizing: border-box;
12389    position: relative;
12390    overflow: hidden;
12391    border-radius: 30px;
12392    font-family: Arial, sans-serif;
12393    color: #ffffff;
12394    background: radial-gradient(circle at top left, rgba(177 2 2), transparent 20%), radial-gradient(circle at bottom right, rgb(75 0 0), transparent 28%), linear-gradient(135deg, #cd0707, #a90000, #c90303);
12395    border: 2px solid rgba(255, 255, 255, .55);
12396    box-shadow: 0 0 25px rgb(207 3 3), 0 0 55px rgb(225 0 0), 0 20px 45px rgba(0, 0, 0, .25), inset 0 0 35px rgba(145 0 0);
12397}
12398
12399.artikel-glow-BOYTOTO:before{
12400  content:"";
12401  position:absolute;
12402  inset:10px;
12403  border-radius:24px;
12404  border:1px solid rgba(255,255,255,.32);
12405  pointer-events:none;
12406}
12407
12408.artikel-shine{
12409  position:absolute;
12410  top:-120px;
12411  left:-180px;
12412  width:280px;
12413  height:650px;
12414  background:linear-gradient(120deg,transparent,rgba(255,255,255,.55),transparent);
12415  transform:rotate(25deg);
12416  animation:artikelShine 4.5s infinite;
12417  pointer-events:none;
12418}
12419
12420@keyframes artikelShine{
12421  0%{left:-220px;}
12422  55%{left:110%;}
12423  100%{left:110%;}
12424}
12425
12426.artikel-glow-BOYTOTO h1,
12427.artikel-glow-BOYTOTO h2{
12428  position:relative;
12429  color:#000000;
12430  text-align:center;
12431  line-height:1.35;
12432  text-shadow:
12433    0 0 8px rgba(179 0 0 / 95%),
12434    0 0 18px rgba(197 0 0),
12435    0 2px 8px rgba(149 0 0);
12436}
12437
12438.artikel-glow-BOYTOTO h1{
12439  font-size:31px;
12440  margin:0 0 20px;
12441}
12442
12443.artikel-glow-BOYTOTO h2{
12444  font-size:24px;
12445  margin:28px 0 14px;
12446}
12447
12448.artikel-glow-BOYTOTO p{
12449  position:relative;
12450  margin:0 0 16px;
12451  color:#f8ffff;
12452  font-size:15px;
12453  line-height:1.85;
12454  text-align:justify;
12455  text-shadow:0 1px 4px rgba(0,0,0,.28);
12456}
12457
12458.artikel-glow-BOYTOTO a{
12459  color:#000000;
12460  font-weight:800;
12461  text-shadow:0 0 8px rgba(153 0 0);
12462}
12463
12464.artikel-glow-BOYTOTO img{
12465  position:relative;
12466  display:block;
12467  max-width:100%;
12468  margin:16px auto 24px;
12469  border-radius:16px;
12470  border:1px solid rgba(255,255,255,.55);
12471  box-shadow:
12472    0 0 18px rgba(255,255,255,.55),
12473    0 12px 28px rgba(0,0,0,.28);
12474}
12475
12476.artikel-highlight{
12477  position:relative;
12478  margin:24px 0;
12479  padding:22px;
12480  border-radius:22px;
12481  background:rgba(255,255,255,.20);
12482  border:1px solid rgba(255,255,255,.45);
12483  box-shadow:
12484    inset 0 1px 0 rgba(255,255,255,.65),
12485    inset 0 0 22px rgba(255,255,255,.14),
12486    0 10px 25px rgba(0,0,0,.18);
12487  backdrop-filter:blur(8px);
12488}
12489
12490.artikel-highlight:before{
12491  content:"";
12492  position:absolute;
12493  left:0;
12494  right:0;
12495  top:0;
12496  height:45%;
12497  border-radius:22px 22px 0 0;
12498  background:linear-gradient(rgba(255,255,255,.35),rgba(255,255,255,.04));
12499  pointer-events:none;
12500}
12501
12502.artikel-glow-BOYTOTO ul{
12503  position:relative;
12504  margin:15px 0 0 20px;
12505  padding:0;
12506}
12507
12508.artikel-glow-BOYTOTO li{
12509  margin-bottom:11px;
12510  padding:10px 12px;
12511  border-radius:12px;
12512  background:rgba(255,255,255,.16);
12513  color:#ffffff;
12514  line-height:1.6;
12515  box-shadow:inset 0 0 10px rgba(255,255,255,.08);
12516}
12517
12518@media(max-width:600px){
12519  .artikel-glow-BOYTOTO{
12520    margin:25px 12px;
12521    padding:25px;
12522    border-radius:24px;
12523  }
12524
12525  .artikel-glow-BOYTOTO h1{
12526    font-size:24px;
12527  }
12528
12529  .artikel-glow-BOYTOTO h2{
12530    font-size:21px;
12531  }
12532}
12533</style>
12534
12535<div class="BOYTOTO-faq-rainbow">
12536  <div class="BOYTOTO-shine"></div>
12537
12538  <div class="BOYTOTO-faq-top">
12539    <span>★ FAQ ★</span>
12540    <h2>Pertanyaan Umum Game Seru BOYTOTO</h2>
12541
12542  </div>
12543
12544    <div class="BOYTOTO-rainbow-item">
12545    <button type="button">Apa yang membuat keseruan BOYTOTO berbeda?</button>
12546    <div class="BOYTOTO-rainbow-answer">
12547      <p>BOYTOTO menghadirkan hiburan di balik langit dengan ragam pilihan game seru kekinian. Platform ini dirancang interaktif agar setiap pengguna bisa menikmati sensasi bermain yang menyegarkan kapan saja.</p>
12548    </div>
12549  </div>
12550  <div class="BOYTOTO-rainbow-item">
12551    <button type="button">Bagaimana cara mulai menikmati permainan di BOYTOTO hari ini?</button>
12552    <div class="BOYTOTO-rainbow-answer">
12553      <p>Pilih kategori game favorit Anda dari daftar yang tersedia, lalu sesuaikan mode permainan yang diinginkan. Akses yang fleksibel memudahkan Anda langsung menjelajahi tantangan seru secara instan.</p>
12554    </div>
12555  </div>
12556  <div class="BOYTOTO-rainbow-item">
12557    <button type="button">Apakah ada banyak pilihan mode permainan yang bisa dimainkan?</button>
12558    <div class="BOYTOTO-rainbow-answer">
12559      <p>Ya. BOYTOTO menyediakan berbagai variasi permainan mulai dari tingkat santai hingga tantangan seru, sehingga setiap pemain dapat memilih mode yang paling sesuai dengan selera.</p>
12560    </div>
12561  </div>
12562  <div class="BOYTOTO-rainbow-item">
12563    <button type="button">Untuk apa variasi game dihadirkan di platform ini?</button>
12564    <div class="BOYTOTO-rainbow-answer">
12565      <p>Pilihan game yang beragam berfungsi memberikan referensi hiburan yang kaya, sehingga pengguna tidak merasa bosan dan dapat terus merasakan pengalaman bermain dengan nuansa kekinian.</p>
12566    </div>
12567  </div>
12568  <div class="BOYTOTO-rainbow-item">
12569    <button type="button">Apakah ada jaminan kemenangan dalam setiap game?</button>
12570    <div class="BOYTOTO-rainbow-answer">
12571      <p>Tidak. Setiap permainan bergantung pada keberuntungan serta strategi masing-masing pemain. Pilihan game di sini hadir sebagai media hiburan untuk dinikmati dengan seru dan menyenangkan.</p>
12572    </div>
12573  </div>
12574</div>
12575
12576<style>
12577.BOYTOTO-faq-rainbow{
12578      max-width: 760px;
12579    margin: 40px auto;
12580    padding: 30px;
12581    box-sizing: border-box;
12582    position: relative;
12583    overflow: hidden;
12584    border-radius: 32px;
12585    font-family: Arial, sans-serif;
12586    background: radial-gradient(circle at 15% 10%, rgba(177 2 2), transparent 18%), radial-gradient(circle at 85% 15%, rgb(207 3 3), transparent 25%), radial-gradient(circle at 20% 90%, rgb(207 3 3), transparent 25%), linear-gradient(135deg, #c70000, #8e0b0b, #960e0e, #c30000);
12587    border: 2px solid rgba(177 2 2);
12588    box-shadow: 0 0 25px rgb(207 3 3), 0 0 55px rgba(223 0 0), inset 0 0 35px rgba(145 0 0);
12589}
12590
12591.BOYTOTO-shine{
12592  position:absolute;
12593  top:-90px;
12594  left:-120px;
12595  width:260px;
12596  height:520px;
12597  background:linear-gradient(120deg,transparent,rgba(255,255,255,.55),transparent);
12598  transform:rotate(25deg);
12599  animation:BOYTOTOShine 4s infinite;
12600  pointer-events:none;
12601}
12602
12603@keyframes BOYTOTOShine{
12604  0%{left:-180px;}
12605  55%{left:110%;}
12606  100%{left:110%;}
12607}
12608
12609.BOYTOTO-faq-top{
12610  position:relative;
12611  text-align:center;
12612  margin-bottom:24px;
12613}
12614
12615.BOYTOTO-faq-top span{
12616  display:inline-block;
12617  padding:8px 22px;
12618  border-radius:999px;
12619  background:rgba(185 0 0 / 0%);
12620  color:#dbdbdb;
12621  font-size:12px;
12622  font-weight:900;
12623  letter-spacing:2px;
12624  border:1px solid rgba(215 215 215 / 62%);
12625  box-shadow:inset 0 0 14px rgba(255,255,255,.25);
12626}
12627
12628.BOYTOTO-faq-top h2{
12629  margin:16px 0 8px;
12630  color:#000000;
12631  font-size:34px;
12632  line-height:1.15;
12633  text-shadow:
12634    0 0 10px rgba(177 0 0),
12635    0 0 22px rgba(173 0 0);
12636}
12637
12638.BOYTOTO-faq-top p{
12639  margin:0;
12640  color:#f3ffff;
12641  font-size:14px;
12642}
12643
12644.BOYTOTO-rainbow-item{
12645  position:relative;
12646  overflow:hidden;
12647  margin-bottom:14px;
12648  border-radius:20px;
12649  background:rgba(255,255,255,.20);
12650  border:1px solid rgba(255,255,255,.48);
12651  box-shadow:
12652    inset 0 1px 0 rgba(255,255,255,.6),
12653    0 10px 25px rgba(0,0,0,.18);
12654  backdrop-filter:blur(10px);
12655}
12656
12657.BOYTOTO-rainbow-item:before{
12658  content:"";
12659  position:absolute;
12660  top:0;
12661  left:0;
12662  right:0;
12663  height:45%;
12664  background:linear-gradient(rgba(255,255,255,.32),rgba(255,255,255,.04));
12665  pointer-events:none;
12666}
12667
12668.BOYTOTO-rainbow-item button{
12669  width:100%;
12670  padding:18px 58px 18px 20px;
12671  border:0;
12672  background: #af0000;
12673  color:#dbdbdb;
12674  text-align:left;
12675  cursor:pointer;
12676  font-size:15px;
12677  font-weight:900;
12678  position:relative;
12679  z-index:2;
12680  text-shadow:0 2px 6px rgba(0,0,0,.35);
12681}
12682
12683.BOYTOTO-rainbow-item button:after{
12684  content:"+";
12685  position:absolute;
12686  right:18px;
12687  top:50%;
12688  transform:translateY(-50%);
12689  width:34px;
12690  height:34px;
12691  border-radius:50%;
12692  background:linear-gradient(135deg,#b10000, #9b0000);
12693  color:#d1d1d1;
12694  text-align:center;
12695  line-height:34px;
12696  font-size:24px;
12697  font-weight:900;
12698  box-shadow:
12699    0 0 14px rgba(255,255,255,.8),
12700    inset 0 -4px 8px rgba(0,0,0,.18);
12701}
12702
12703.BOYTOTO-rainbow-item.active button:after{
12704  content:"−";
12705  background:linear-gradient(135deg,#ff0000, #5b0000);
12706}
12707
12708.BOYTOTO-rainbow-answer{
12709  max-height:0;
12710  overflow:hidden;
12711  transition:max-height .35s ease;
12712  position:relative;
12713  z-index:2;
12714}
12715
12716.BOYTOTO-rainbow-item.active .BOYTOTO-rainbow-answer{
12717  max-height:170px;
12718}
12719
12720.BOYTOTO-rainbow-answer p{
12721  margin:0;
12722  padding:0 20px 18px;
12723  color:#dbdbdb;
12724  font-size:14px;
12725  line-height:1.7;
12726  text-shadow:0 2px 5px rgba(0 0 0);
12727}
12728
12729@media(max-width:600px){
12730  .BOYTOTO-faq-rainbow{
12731    margin:25px 12px;
12732    padding:22px;
12733    border-radius:24px;
12734  }
12735
12736  .BOYTOTO-faq-top h2{
12737    font-size:26px;
12738  }
12739}
12740</style>
12741
12742<script>
12743document.querySelectorAll(".BOYTOTO-rainbow-item button").forEach(function(btn){
12744  btn.addEventListener("click", function(){
12745    this.parentElement.classList.toggle("active");
12746  });
12747});
12748</script>
12748
12749<style>
12750@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@500;700;900&display=swap');
12751
12752.cyber-mecha {
12753  max-width: 800px;
12754  margin: 40px auto;
12755
12756  /* deep luxury red */
12757  background: radial-gradient(circle at top, #14b1d7, #14b1d7 70%);
12758
12759  border-radius: 16px;
12760  border: 1px solid rgba(255, 90, 90, 0.35);
12761
12762  box-shadow:
12763    0 0 25px rgba(140, 30, 30, 0.35),
12764    inset 0 0 20px rgba(255, 60, 60, 0.10);
12765
12766  padding: 25px;
12767  font-family: 'Orbitron', sans-serif;
12768  color: #f7e6e6;
12769  position: relative;
12770  overflow: hidden;
12771}
12772
12773/* scanline overlay */
12774.cyber-mecha::before {
12775  content: "";
12776  position: absolute;
12777  inset: 0;
12778  background: repeating-linear-gradient(
12779    to bottom,
12780    rgba(255,90,90,0.03),
12781    rgba(255,90,90,0.03) 1px,
12782    transparent 1px,
12783    transparent 3px
12784  );
12785  pointer-events: none;
12786}
12787
12788.pd-full-desc-title {
12789  text-align: center;
12790  font-size: 22px;
12791
12792  /* elegant muted red */
12793  color: #e08b8b;
12794
12795  letter-spacing: 2px;
12796  text-shadow: 0 0 10px rgba(224, 139, 139, 0.4);
12797  margin-bottom: 20px;
12798}
12799
12800.mecha-header h3 {
12801  text-align: center;
12802  font-size: 18px;
12803  color: #ffb3b3;
12804  text-shadow: 0 0 8px rgba(255, 90, 90, 0.5);
12805  margin-bottom: 15px;
12806}
12807
12808.mecha-image img {
12809  width: 100%;
12810  border-radius: 10px;
12811  border: 1px solid rgba(255, 90, 90, 0.4);
12812  box-shadow: 0 0 20px rgba(255, 60, 60, 0.25);
12813  margin-bottom: 20px;
12814}
12815
12816.mecha-content p {
12817  font-size: 14px;
12818  line-height: 1.8;
12819  margin-bottom: 14px;
12820  color: #f3dede;
12821}
12822
12823.glow {
12824  color: #e08b8b;
12825  text-shadow: 0 0 6px rgba(224, 139, 139, 0.5);
12826  font-weight: 600;
12827}
12828
12829.glow-strong {
12830  color: #ff6b6b;
12831  text-shadow:
12832    0 0 10px rgba(255, 90, 90, 0.6),
12833    0 0 20px rgba(255, 60, 60, 0.4);
12834  font-weight: 700;
12835}
12836
12837.final-line {
12838  margin-top: 18px;
12839  padding: 10px;
12840  border: 1px solid rgba(255, 90, 90, 0.35);
12841  background: rgba(60, 0, 0, 0.25);
12842  border-radius: 8px;
12843  text-align: center;
12844  color: #ffd0d0;
12845}
12846</style>
12847
12848
12849
12850
12851
12852        <section class="pd-related-full">
12853        <h2 class="pd-related-title">Panduan Shio Terkait</h2>
12854        <div class="pd-related-grid pd-related-grid--full">
12855                        <a class="pd-related-card" href="https://mgsco.org/">
12856                <div class="thumb">
12857                    <img src="https://ik.imagekit.io/boytoto/banner235.png" alt="BOYTOTO">
12858                    <span class="mini-disc">SHIO</span>                </div>
12859                <div class="info">
12860                    <h3>BOYTOTO</h3>
12861                    <div class="spec-chips">12 Shio <span class="spec-divider">|</span> Kategori Angka <span class="spec-divider">|</span> Referensi 4D</div>                    <span class="price">
12862                        <span class="final">Daftar Shio</span>
12863                                                  <span class="ori">Referensi 4D</span>
12864                                            </span>s
12865                    <span class="sold">Kategori Angka</span>                    <span class="stock-dot low" title="Stok Terbatas"></span>
12866                </div>
12867            </a>
12868                        <a class="pd-related-card" href="https://mgsco.org/">
12869                <div class="thumb">
12870                    <img src="https://ik.imagekit.io/boytoto/banner235.png" alt="BOYTOTO">
12871                    <span class="mini-disc">SHIO</span>                </div>
12872                <div class="info">
12873                    <h3>BOYTOTO</h3>
12874                    <div class="spec-chips">Shio Harian <span class="spec-divider">|</span> Cocokkan Nomor <span class="spec-divider">|</span> Referensi 4D</div>                    <span class="price">
12875                        <span class="final">Daftar Shio</span>
12876                                                  <span class="ori">Referensi 4D</span>
12877                                            </span>
12878                    <span class="sold">Kategori Angka</span>                    <span class="stock-dot out" title="Habis"></span>
12879                </div>
12880            </a>
12881                        <a class="pd-related-card" href="https://mgsco.org/">
12882                <div class="thumb">
12883                    <img src="https://ik.imagekit.io/boytoto/banner235.png" alt="BOYTOTO">
12884                    <span class="mini-disc">SHIO</span>                </div>
12885                <div class="info">
12886                    <h3>BOYTOTO</h3>
12887                    <div class="spec-chips">Nomor 4D <span class="spec-divider">|</span> Kelompok Shio <span class="spec-divider">|</span> Catatan Angka</div>                    <span class="price">
12888                        <span class="final">Daftar Shio</span>
12889                                                  <span class="ori">Referensi 4D</span>
12890                                            </span>
12891                    <span class="sold">Kategori Angka</span>                    <span class="stock-dot ready" title="Ready"></span>
12892                </div>
12893            </a>
12894                        <a class="pd-related-card" href="https://mgsco.org/">
12895                <div class="thumb">
12896                    <img src="https://ik.imagekit.io/boytoto/banner235.png" alt="SITUS BOYTOTO">
12897                    <span class="mini-disc">SHIO</span>                </div>
12898                <div class="info">
12899                    <h3>SITUS BOYTOTO</h3>
12900                    <div class="spec-chips">Nomor Harian <span class="spec-divider">|</span> Daftar Shio <span class="spec-divider">|</span> Kategori Angka</div>                    <span class="price">
12901                        <span class="final">Daftar Shio</span>
12902                                                  <span class="ori">Referensi 4D</span>
12903                                            </span>
12904                    <span class="sold">Kategori Angka</span>                    <span class="stock-dot ready" title="Ready"></span>
12905                </div>
12906            </a>
12907                        <a class="pd-related-card" href="https://mgsco.org/">
12908                <div class="thumb">
12909                    <img src="https://ik.imagekit.io/boytoto/banner235.png" alt="SITUS PARLAY">
12910                    <span class="mini-disc">SHIO</span>                </div>
12911                <div class="info">
12912                    <h3>BANDAR BOYTOTO</h3>
12913                    <div class="spec-chips">Shio Harian <span class="spec-divider">|</span> Cocokkan Nomor <span class="spec-divider">|</span> Referensi 4D</div>                    <span class="price">
12914                        <span class="final">Daftar Shio</span>
12915                                                  <span class="ori">Referensi 4D</span>
12916                                            </span>
12917                    <span class="sold">Kategori Angka</span>                    <span class="stock-dot low" title="Stok Terbatas"></span>
12918                </div>
12919            </a>
12920                        <a class="pd-related-card" href="https://mgsco.org/">
12921                <div class="thumb">
12922                    <img src="https://ik.imagekit.io/boytoto/banner235.png" alt="MIX PARLAY">
12923                    <span class="mini-disc">SHIO</span>                </div>
12924                <div class="info">
12925                    <h3>BOYTOTO ONLINE</h3>
12926                    <div class="spec-chips">Toto 4D <span class="spec-divider">|</span> Shio <span class="spec-divider">|</span> Referensi Kategori</div>                    <span class="price">
12927                        <span class="final">Daftar Shio</span>
12928                                                  <span class="ori">Referensi 4D</span>
12929                                            </span>
12930                                        <span class="stock-dot low" title="Stok Terbatas"></span>
12931                </div>
12932            </a>
12933
12934                    </div>
12935    </section>
12936
12937    <div class="pd-mobile-fab" id="pd-mobile-fab" data-pid="325" data-price="20789000" data-voucher-code="" data-voucher-value="0"><div class="mf-actions"><button type="button" class="mf-btn mf-share" id="mf-share" aria-label="Bagikan"><svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92s2.92-1.31 2.92-2.92-1.31-2.92-2.92-2.92z"/>
12937</svg></button><button type="button" class="mf-btn mf-chat" id="mf-chat" aria-label="Tanya Shio"><svg viewBox="0 0 640 640" fill="currentColor"><path d="M320 128C241 128 175.3 185.3 162.3 260.7C171.6 257.7 181.6 256 192 256L208 256C234.5 256 256 277.5 256 304L256 400C256 426.5 234.5 448 208 448L192 448C139 448 96 405 96 352L96 288C96 164.3 196.3 64 320 64C443.7 64 544 164.3 544 288L544 456.1C544 522.4 490.2 576.1 423.9 576.1L336 576L304 576C277.5 576 256 554.5 256 528C256 501.5 277.5 480 304 480L336 480C362.5 480 384 501.5 384 528L384 528L424 528C463.8 528 496 495.8 496 456L496 435.1C481.9 443.3 465.5 447.9 448 447.9L432 447.9C405.5 447.9 384 426.4 384 399.9L384 303.9C384 277.4 405.5 255.9 432 255.9L448 255.9C458.4 255.9 468.3 257.5 477.7 260.6C464.7 185.3 399.1 127.9 320 127.9z"/></svg></button><button type="button" class="mf-btn mf-cart" id="mf-cart" disabled aria-label="Tambah ke keranjang"><svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M7 18c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2zM1 2v2h2l3.6 7.59-1.35 2.45c-.16.28-.25.61-.25.96 0 1.1.9 2 2 2h12v-2H7.42c-.14 0-.25-.11-.25-.25l.03-.12.9-1.63h7.45c.75 0 1.41-.41 1.75-1.03l3.58-6.49c.08-.14.12-.31.12-.48 0-.55-.45-1-1-1H5.21l-.94-2H1zm16 16c-1.1 0-1.99.9-1.99 2s.89 2 1.99 2 2-.9 2-2-.9-2-2-2z"/></svg></button><button type="button" class="mf-btn mf-buy" id="mf-buy" disabled><span class="mf-buy-text">Lihat Daftar BOYTOTO Terbaru</span><span class="mf-buy-price">Referensi Shio</span></button></div></div>
12938
12939    <button type="button"
12940            class="pd-scroll-top"
12941            id="pd-scroll-top"
12942            aria-label="Kembali ke atas"
12943            title="Kembali ke atas">
12944        <svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
12945            <path fill="currentColor" d="M7.41 14.59 12 10l4.59 4.59L18 13.17l-6-6-6 6z"/>
12946        </svg>
12947    </button>
12948</main>
12949
12950<div class="pd-share-modal-overlay" id="pd-share-modal-overlay"><div class="pd-share-modal"><div class="pd-share-header"><h2 class="pd-share-title">Bagikan Panduan</h2><button type="button" class="pd-share-close" id="pd-share-close" aria-label="Tutup"><svg width="20" height="20" viewBox="0 0 24 24"><path fill="currentColor" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg></button></div><div class="pd-share-body"><div class="pd-share-preview"><img src="https://ik.imagekit.io/boytoto/banner235.png" alt="BOYTOTO" class="pd-share-preview-img"><div class="pd-share-preview-info"><h3 class="pd-share-preview-name">BOYTOTO</h3><p class="pd-share-preview-price">Referensi 4D</p></div></div><p class="pd-share-label">Bagikan ke</p><div class="pd-share-grid"><a href="#" class="pd-share-option" data-share="whatsapp" target="_blank" rel="noopener"><span class="pd-share-icon pd-share-icon-whatsapp"><svg viewBox="0 0 24 24"><path fill="currentColor" d="M17.472 14.382c-.297-.149-1.758-.867-2.029-.967-.273-.099-.471-.148-.669.149-.198.297-.767.966-.94 1.164-.173.198-.347.223-.644.074-.297-.149-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.297-.347.446-.52.149-.173.198-.297.298-.495.099-.198.05-.372-.025-.521-.075-.149-.669-1.612-.916-2.206-.242-.579-.487-.5-.669-.51l-.57-.01c-.198 0-.52.074-.792.372s-1.04 1.016-1.04 2.479 1.065 2.876 1.213 3.074c.149.198 2.095 3.2 5.076 4.487.709.306 1.262.489 1.694.626.712.226 1.36.194 1.872.118.571-.085 1.758-.718 2.006-1.411.248-.694.248-1.29.173-1.411-.074-.123-.272-.198-.57-.347m-5.421 7.403h-.002a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.263c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.887 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.158 11.892c0 2.096.547 4.142 1.588 5.945L0 24l6.305-1.654a11.86 11.86 0 005.67 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.485-8.413"/></svg></span><span class="pd-share-name">WhatsApp</span></a><a href="#" class="pd-share-option" data-share="facebook" target="_blank" rel="noopener"><span class="pd-share-icon pd-share-icon-facebook"><svg viewBox="0 0 24 24">
12950<path fill="currentColor" d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg></span><span class="pd-share-name">Facebook</span></a><a href="#" class="pd-share-option" data-share="twitter" target="_blank" rel="noopener"><span class="pd-share-icon pd-share-icon-twitter"><svg viewBox="0 0 24 24"><path fill="currentColor" d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/></svg></span><span class="pd-share-name">Twitter</span></a><a href="#" class="pd-share-option" data-share="telegram" target="_blank" rel="noopener"><span class="pd-share-icon pd-share-icon-telegram"><svg viewBox="0 0 24 24"><path fill="currentColor" d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"/></svg></span><span class="pd-share-name">Telegram</span></a><a href="#" class="pd-share-option" data-share="email"><span class="pd-share-icon pd-share-icon-email"><svg viewBox="0 0 24 24"><path fill="currentColor" d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"/></svg></span><span class="pd-share-name">Email</span></a><button type="button" class="pd-share-option" data-share="copy"><span class="pd-share-icon pd-share-icon-copy"><svg viewBox="0 0 24 24"><path fill="currentColor" d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg></span><span class="pd-share-name">Salin Link</span></button><button type="button" class="pd-share-option" data-share="pdf"><span class="pd-share-icon pd-share-icon-pdf"><svg viewBox="0 0 24 24"><path fill="currentColor" d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11zM8 15.5h1.5c.83 0 1.5-.67 1.5-1.5v-1c0-.83-.67-1.5-1.5-1.5H8v4zm6 0h1c1.1 0 2-.9 2-2v-2c0-1.1-.9-2-2-2h-1v6zm-4.5-1.5H8v-1h1.5v1zM14 13h1v1h-1v-1z"/></svg></span><span class="pd-share-name">Export PDF</span></button></div><div class="pd-share-copy-section"><div class="pd-share-copy-input-wrap"><input type="text" class="pd-share-copy-input" id="pd-share-url-input" value="https://mgsco.org/" readonly><button type="button" class="pd-share-copy-btn" id="pd-share-copy-url-btn"><svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg><span class="pd-share-copy-text">Salin</span></button></div></div></div></div></div>
12951
12952<style>.toast-root{position:fixed;left:50%;bottom:18px;transform:translateX(-50%);display:flex;flex-direction:column;gap:10px;z-index:99999;pointer-events:none;max-width:min(92vw,540px)}.toast{display:grid;grid-template-columns:28px 1fr auto;gap:12px;background:#0b1220;color:#eef3ff;border:1px solid rgba(255,255,255,.08);padding:12px 14px;border-radius:12px;box-shadow:0 10px 30px rgba(10,10,20,.35);pointer-events:auto;overflow:hidden}.toast .ico{align-self:start;margin-top:2px}.toast .ttl{font-weight:700;line-height:1.25;font-size:14px;margin-bottom:4px}.toast .msg{opacity:.95;line-height:1.45;font-size:13px}.toast .actions{display:flex;gap:8px;align-self:center}.toast .btn{background:#0ea5e9;color:#fff;border:0;border-radius:999px;padding:8px 12px;font-size:12px;font-weight:700;cursor:pointer}.toast .btn.alt{background:transparent;color:#8fb5ff;border:1px solid rgba(143,181,255,.35)}.toast .close{background:transparent;border:0;color:#98a2b3;cursor:pointer;padding:4px;margin-left:6px}.toast.success{border-left:4px solid #22c55e}.toast.info{border-left:4px solid #0ea5e9}.toast.warn{border-left:4px solid #f59e0b}.toast .progress{position:absolute;
12952left:0;bottom:0;height:3px;background:linear-gradient(90deg,#60a5fa,#22c55e);width:100%;transform-origin:left;animation:toastBar linear forwards}@keyframes toastBar{from{transform:scaleX(1)}to{transform:scaleX(0)}}@media(max-width:640px){.toast{grid-template-columns:24px 1fr auto;padding:12px}.toast .btn{padding:7px 10px}}</style>
12953<style>
12954.toast .msg { word-break: break-word; overflow-wrap: anywhere; }
12955
12956.toast-root { bottom: max(12px, env(safe-area-inset-bottom, 12px)); }
12957
12958@media (max-width: 540px) {
12959  .toast-root {
12960    left: 0; right: 0; transform: none;
12961    padding: 0 12px;
12962    bottom: calc(max(12px, env(safe-area-inset-bottom, 12px)) + var(--fab-offset, 72px));
12963    max-width: none;
12964  }
12965  .toast {
12966    grid-template-columns: 1fr; /* 1 kolom */
12967    position: relative;
12968    padding: 14px 14px 12px 14px;
12969    border-radius: 16px;
12970  }
12971  .toast .ico {
12972    position: absolute; left: 14px; top: 14px;
12973    margin: 0; transform: translateY(1px);
12974  }
12975  .toast .cnt { padding-left: 34px; }
12976  .toast .ttl { font-size: 15px; margin-bottom: 6px; }
12977  .toast .msg { font-size: 13px; opacity: .96; }
12978  .toast .actions {
12979    margin-top: 12px; gap: 10px; width: 100%;
12980    display: grid; grid-template-columns: 1fr 1fr;
12981  }
12982  .toast .btn { width: 100%; min-width: 0; padding: 10px 0; }
12983  .toast .close { position: absolute; right: 6px; top: 6px; }
12984}
12985</style>
12986
12987<script nonce="se3iyzEHMc9FQEB4wSyd0w==">
12988(function adjustToastFabOffset(){
12989  const root = document.getElementById('toastRoot');
12990  if (!root) return;
12991
12992  function isVisible(el){
12993    if (!el) return false;
12994    const cs = getComputedStyle(el);
12995    if (cs.display === 'none' || cs.visibility === 'hidden' || cs.opacity === '0') return false;
12996    const r = el.getBoundingClientRect();
12997    return r.width > 0 && r.height > 0;
12998  }
12999
13000  function update(){
13001    const fab = document.getElementById('pd-mobile-fab');
13002    let extra = 16;
13003    if (isVisible(fab)) {
13004      const h = Math.round(fab.getBoundingClientRect().height) || 56;
13005      extra = h + 16; // FAB height + margin
13006    }
13007    root.style.setProperty('--fab-offset', extra + 'px');
13008  }
13009
13010  window.addEventListener('resize', update, { passive: true });
13011  const fab = document.getElementById('pd-mobile-fab');
13012  if (window.ResizeObserver && fab) {
13013    const ro = new ResizeObserver(update);
13014    ro.observe(fab);
13015  }
13016
13017  update();
13018})();
13019</script>
13019
13020<div class="toast-root" id="toastRoot" aria-live="polite" aria-atomic="true"></div>
13021
13022<script nonce="se3iyzEHMc9FQEB4wSyd0w==">(function(){const root=document.getElementById('toastRoot');function svgIcon(kind){if(kind==='success')return '<svg width="24" height="24" viewBox="0 0 24 24" fill="#22c55e"><path d="M9 16.2l-3.5-3.5 1.4-1.4L9 13.4l7.7-7.7 1.4 1.4z"/></svg>';if(kind==='warn')return '<svg width="24" height="24" viewBox="0 0 24 24" fill="#f59e0b"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>';return '<svg width="24" height="24" viewBox="0 0 24 24" fill="#0ea5e9"><path d="M11 7h2v2h-2zm0 4h2v6h-2z"/><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2z" fill="none" stroke="#0ea5e9"/></svg>'}
13023window.showToast=function(opts){const o=Object.assign({title:'',message:'',type:'info',duration:4000,actions:[]},opts||{});const el=document.createElement('div');el.className='toast '+(o.type||'info');el.setAttribute('role','status');el.style.position='relative';const acts=(o.actions||[]).map(a=>{const lab=a.label||'OK';const cls='btn'+(a.variant==='alt'?' alt':'');return `<button type="button" class="${cls}" data-act="${lab}">${lab}</button>`}).join('');el.innerHTML=`<div class="ico" aria-hidden="true">${svgIcon(o.type)}</div><div class="cnt">${o.title?`<div class="ttl">${o.title}</div>`:''}${o.message?`<div class="msg">${o.message}</div>`:''}</div><div class="actions">${acts}</div><button type="button" class="close" aria-label="Tutup" title="Tutup"><svg width="18" height="18" viewBox="0 0 24 24"><path fill="currentColor" d="M18.3 5.71L12 12.01l-6.3-6.3-1.4 1.41 6.3 6.3-6.3 6.3 1.4 1.41 6.3-6.3 6.3 6.3 1.41-1.41-6.3-6.3 6.3-6.3z"/></svg></button><div class="progress" style="animation-duration:${Math.max(1500,o.duration)}ms"></div>`;
13024
13025    const close = ()=> {
13026      if (!el.parentNode) return;
13027      el.style.transition = 'transform .2s ease, opacity .2s ease';
13028      el.style.opacity = '0'; el.style.transform = 'translateY(10px)';
13029      setTimeout(()=>{ if (el.parentNode) el.parentNode.removeChild(el); }, 220);
13030    };
13031
13032    el.querySelector('.close').addEventListener('click', close);
13033    el.querySelectorAll('.actions .btn').forEach(btn=>{
13034      btn.addEventListener('click', ()=>{
13035        const lab = btn.getAttribute('data-act');
13036        try {
13037          (o.actions||[]).forEach(a=>{ if (a.label===lab && typeof a.onClick==='function') a.onClick(); });
13038        } catch(_){}
13039        close();
13040      });
13041    });
13042
13043    root.appendChild(el);
13044    setTimeout(close, o.duration);
13045    return el;
13046  };
13047})();
13048
13049const _0x1a2b=[{"url":"https://ik.imagekit.io/boytoto/banner235.png","alt":"BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler","slug":""},{"url":"https://ik.imagekit.io/boytoto/banner235.png","alt":"BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler","slug":"https://ik.imagekit.io/boytoto/banner235.png"},{"url":"https://ik.imagekit.io/boytoto/banner235.png","alt":"BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler","slug":"BOYTOTO"},{"url":"https://ik.imagekit.io/boytoto/banner235.png","alt":"BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler","slug":"BOYTOTO"},{"url":"https://ik.imagekit.io/boytoto/banner235.png","alt":"BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler","slug":"BOYTOTO"},{"url":"https://ik.imagekit.io/boytoto/banner235.png","alt":"BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler","slug":"BOYTOTO"}],_0x3c4d="BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler",_0x5e6f="SB0B3T";function getVariantSummaryText(){const a=document.getElementById('pd-variant-summary');if(!a)return '';const b=Array.from(a.querySelectorAll('.vs-item strong')).map(c=>c.textContent.trim()).filter(Boolean);return b.length?b.join(' / '):''}
13050function encodeVariantHash(id){if(!id||id<=0)return '';const xor=id^0x4A5D;return xor.toString(36)}
13051function decodeVariantHash(hash){if(!hash)return 0;const xor=parseInt(hash,36);return isNaN(xor)?0:xor^0x4A5D}
13052
13053function showSuccessToast(productId,qty,variantId){const variantText=getVariantSummaryText();const skuText=(function(){const el=document.getElementById('pd-sku');return el?el.textContent.trim():PD_SKU})();showToast({type:'warn',title:'Fitur Segera Hadir',message:`<strong>${escapeHtml(PD_NAME)}</strong>${variantText?` · <em>${escapeHtml(variantText)}</em>`:''}<br>SKU: ${escapeHtml(skuText)}
13053 · Qty: ${qty}`,duration:5000,actions:[{label:'Lihat Keranjang',onClick:()=>{}},{label:'Lanjut Belanja',variant:'alt',onClick:()=>{}}]});try{if(window.ga4Ecomm){const qtySend=qty||1;window.ga4Ecomm.addToCart({currency:'IDR',price:20789000,quantity:qtySend,item_id:"SB0B3T",item_name:"BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler",item_brand:"BOYTOTO",item_category:(["Komputer & Laptop","Laptop","Gaming"]||[]).join(' / ')})}}catch(_){}return false}function showErrorToast(){showToast({type:'error',title:'Gagal Menambah ke Keranjang',message:'Terjadi kesalahan saat memproses permintaan. Silakan coba lagi.',duration:5000})}
13054
13055function executeBuyNowLegacy(id,variantId){const a=getVariantSummaryText(),b=(function(){const c=document.getElementById('pd-sku');return c?c.textContent.trim():_0x5e6f})();
13056
13057    let willRedirect = false;
13058    try {
13059        if (window.addToCart) {
13060            window.addToCart(id, 1, variantId);
13061            willRedirect = false;
13062        }
13063    } catch(_){}
13064
13065    if (willRedirect) {
13066        showToast({
13067            type: 'info',
13068            title: 'Menuju Keranjang',
13069            message: `<strong>${escapeHtml(PD_NAME)}</strong>${variantText?` · <em>${escapeHtml(variantText)}</em>`:''}<br>SKU: ${escapeHtml(skuText)} · Qty: 1`,
13070            duration: 1800
13071        });
13072        setTimeout(()=>{ window.location.href='/keranjang'; }, 1200);
13073    } else {
13074        showToast({
13075            type: 'warn',
13076            title: 'Fitur Beli Langsung Segera Hadir',
13077            message: `Sementara ini Anda bisa menambahkan produk ke keranjang terlebih dahulu.<br><strong>${escapeHtml(PD_NAME)}</strong>${variantText?` · <em>${escapeHtml(variantText)}</em>`:''}`,
13078            duration: 6000,
13079            actions: [
13080                { label: 'Simpan Panduan', onClick: ()=> { try{ if (window.addToCart) window.addToCart(id,1,variantId); }catch(_){} } },
13081                { label: 'Hubungi CS', variant: 'alt', onClick: ()=> { window.open('https://wa.me/6281380009898','_blank'); } }
13082            ]
13083        });
13084    }
13085}
13086
13087document.addEventListener('DOMContentLoaded',function(){var a=document.getElementById('pd-buy-form');if(a){a.addEventListener('submit',function(e){var b=parseInt(a.getAttribute('data-pid'),10)||0;if(window.addToCartSubmit){return window.addToCartSubmit(e,b)}})}});document.addEventListener('DOMContentLoaded',function(){var a=document.getElementById('btn-buy');if(a){a.addEventListener('click',function(){var b=a.getAttribute('data-id');buyNow(b)})}});
13088
13089function _0x2m3n(s){return String(s||'').replace(/[&<>"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[m]))}
13090
13091(function initGallery(){const mainImg=document.getElementById('pd-main-img');const gallery=document.getElementById('pd-thumb-gallery');if(!mainImg||!gallery)return;gallery.addEventListener('click',function(ev){const btn=ev.target.closest('.pd-thumb-btn');if(!btn)return;const full=btn.getAttribute('data-full');if(full){mainImg.src=full;const alt=btn.getAttribute('data-alt')||'';if(alt)mainImg.alt=alt;const slug=btn.getAttribute('data-slug')||'';if(slug)mainImg.dataset.slug=slug;gallery.querySelectorAll('.pd-thumb-btn').forEach(b=>b.classList.remove('active'));btn.classList.add('active')}})})();
13092
13093(function initMobileSliderCounter(){const slider=document.getElementById('pd-mobile-slider');const counter=document.getElementById('pd-gallery-counter');if(!slider||!counter)return;let ticking=false;slider.addEventListener('scroll',function(){if(!ticking){window.requestAnimationFrame(function(){const slides=slider.querySelectorAll('.pd-mslide');if(!slides.length)return;const containerRect=slider.getBoundingClientRect();const containerCenter=containerRect.left+containerRect.width/2;let currentIdx=0;let minDist=Infinity;slides.forEach((slide,idx)=>{const slideRect=slide.getBoundingClientRect();const slideCenter=slideRect.left+slideRect.width/2;const dist=Math.abs(slideCenter-containerCenter);if(dist<minDist){minDist=dist;currentIdx=idx}});counter.textContent=(currentIdx+1)+'/'+slides.length;ticking=false});ticking=true}},{passive:true})})();
13094
13095/* ================= Simulasi Cicilan ================= */
13096(function initInstallmentList(){const card=document.getElementById('inst-card');
13096if(!card)return;function getBasePrice(){return parseInt(card.getAttribute('data-base-price')||'0',10)||0}function setBasePrice(v){card.setAttribute('data-base-price',String(parseInt(v||'0',10)||0))}const zeroEligible=card.getAttribute('data-zero-smart')==='1';window.initMobile=zeroEligible;const listEl=document.getElementById('inst-month-list');const zeroNote=document.getElementById('inst-zero-note');const tenors=[3,6,12];const rates={BRI:{3:0.975,6:0.975,12:0.975},Mandiri:{3:0.984,6:0.9725,12:0.96},BCA:{3:0.97,6:0.94,12:0.91}};function formatRupiah(num){return 'Rp '+(Math.round(num)).toString().replace(/B(?=(d{3})+(?!d))/g,'.')}function activeBank(){const tab=card.querySelector('.bank-tab.active');return tab?tab.dataset.bank:'BRI'}
13097
13098function _0x4o5p(bank){const a=getBasePrice();listEl.innerHTML='';const b=rates[bank]||{};let c=false;tenors.forEach(m=>{let d=b[m]??1;if(zeroEligible&&(bank==='BRI'||bank==='Mandiri')){d=1;c=true}const e=a/d;const f=m>0?Math.ceil((e/m)/100)*100:0;const g=document.createElement('li');g.className='inst-month-item';g.innerHTML=`<span class="tenor-badge">${m}x</span><span class="amount">${formatRupiah(f)} / bln</span>${zeroEligible&&(bank==='BRI'||bank==='Mandiri')?'<span class="tag tag-zero">0%</span>':''}`;listEl.appendChild(g)});zeroNote.style.display=c?'block':'none'}
13099
13100card.addEventListener('click',function(ev){const a=ev.target.closest('.bank-tab');if(!a)return;card.querySelectorAll('.bank-tab').forEach(b=>{b.classList.remove('active');b.setAttribute('aria-selected','false')});a.classList.add('active');a.setAttribute('aria-selected','true');_0x4o5p(a.dataset.bank)});
13101
13102window.instSetBasePrice=function(newPrice){setBasePrice(newPrice);_0x4o5p(activeBank())};_0x4o5p(activeBank());
13103})();
13104
13105/* ====== Fokus gambar varian ====== */
13106function _0x6q7r(s){return String(s||'').toLowerCase().replace(/[^a-z0-9]+/g,'')}function _0x8s9t(u){try{return new URL(u,window.location.origin).pathname.split('/').pop().toLowerCase()}catch(e){try{return(u||'').split('/').pop().toLowerCase()}catch(_){return ''}}}
13107function _0xau1v(url,altOpt){const a=document.getElementById('pd-main-img'),b=document.getElementById('pd-thumb-gallery'),c=document.getElementById('pd-mobile-slider');if(a&&url){a.src=url;if(altOpt)a.alt=altOpt}if(b){const d=_0x8s9t(url);let e=null;b.querySelectorAll('.pd-thumb-btn').forEach(btn=>{const f=btn.getAttribute('data-full')||'',g=_0x8s9t(f)===d;if(g)e=btn;btn.classList.toggle('active',g)});if(e){const h=e.getAttribute('data-alt')||'',i=e.getAttribute('data-slug')||'';if(h&&a)a.alt=h;if(i&&a)a.dataset.slug=i}}
13108  if (c){
13109    const targetName = _0x8s9t(url);
13110    const slideImg = Array.from(c.querySelectorAll('img')).find(im => _0x8s9t(im.src) === targetName);
13111    if (slideImg){
13112
13113      try {
13114        const container = c;
13115        const imgRect = slideImg.getBoundingClientRect();
13116        const contRect = container.getBoundingClientRect();
13117        const offsetLeft = slideImg.offsetLeft - container.offsetLeft;
13118        const center = offsetLeft - (container.clientWidth - slideImg.clientWidth) / 2;
13119        container.scrollTo({ left: Math.max(0, center), behavior: 'smooth' });
13120      } catch(_){
13121
13122        slideImg.scrollIntoView({ behavior:'smooth', inline:'center', block:'nearest' });
13123      }
13124    }
13125  }
13126}
13127function findImageForVariant(variant){
13128  if (!variant || !variant.attrs) return null;
13129  const tokens = [];
13130  try{
13131    Object.values(variant.attrs).forEach(v=>{
13132      if (!v) return;
13133      const raw = String(v);
13134      tokens.push(raw);
13135      tokens.push(normalize(raw));
13136    });
13137  }catch(_){}
13138  if (variant.sku){
13139    tokens.push(String(variant.sku));
13140    tokens.push(normalize(String(variant.sku)));
13141  }
13142  if (variant.image_url) {
13143      tokens.push(String(variant.image_url));
13144  }
13145  let best = null;
13146  let bestScore = -1;
13147
13148  for (const g of (galleryData||[])){
13149    const slugN = normalize(g.slug||'');
13150    const altN  = normalize(g.alt||'');
13151    const file  = _0x8s9t(g.url||'');
13152    const fileN = normalize(file);
13153    const imgU  = g.url;
13154
13155    for (const t of tokens){
13156      const tN = normalize(t);
13157      if (!tN) continue;
13158
13159      let score = -1;
13160      if (imgU === tN) score = 300;
13161      if (slugN && slugN === tN) score = 300;
13162      else if (fileN && fileN.includes(tN)) score = 200;
13163      else if (altN && altN.includes(tN)) score = 120;
13164
13165      if (score > bestScore){
13166        bestScore = score;
13167        best = g;
13168      }
13169      if (bestScore >= 300) break;
13170    }
13171    if (bestScore >= 300) break;
13172  }
13173  return bestScore >= 120 ? best : null;
13174}
13175function _0xey3z(variant){const a=_0xcw2x(variant);if(a&&a.url){_0xau1v(a.url,a.alt||'')}}
13176
13177/* ====== Layout mobile ====== */
13178(function _0xg04h(){const a=document.querySelector('.pd-media-card'),b=document.getElementById('pd-price-block'),c=document.getElementById('pd-price-anchor'),d=document.querySelector('.pd-info-card'),e=document.getElementById('pd-title'),f=document.getElementById('pd-variant-combined'),g=document.getElementById('pd-variants'),h=document.getElementById('pd-variants-anchor'),i=document.getElementById('pd-variant-summary'),j=document.getElementById('pd-summary-anchor'),titleRow=document.querySelector('.pd-title-row'),promoBanner=document.querySelector('.promo-endyear-mini-banner'),compareSection=document.querySelector('.pd-compare-section');if(!a||!b||!c||!d||!e)return;
13179
13180    let pricePlaced = false;
13181    let varPlaced = false;
13182    let titlePlaced = false;
13183    let promoPlaced = false;
13184    const mq = window.matchMedia('(max-width: 640px)');
13185
13186    function apply(){
13187        if (mq.matches) {
13188            if (!titlePlaced && a && titleRow && d.contains(titleRow)) { try{a.appendChild(titleRow);titlePlaced=true;}catch(_){} }
13189            if (!pricePlaced && a && b) { try{a.appendChild(b);pricePlaced=true;}catch(_){} }
13190            if (!varPlaced && f && d && e) {
13191                try{
13192                    f.hidden = false;
13193                    if (i && !f.contains(i)) f.appendChild(i);
13194                    if (g && !f.contains(g)) f.appendChild(g);
13195                    if(d.contains(e))d.insertBefore(f, e);
13196                    varPlaced = true;
13197                }catch(_){}
13198            }
13199            if (!promoPlaced && promoBanner && compareSection && compareSection.parentNode) {
13200                try{
13201                    compareSection.parentNode.insertBefore(promoBanner, compareSection);
13202                    promoPlaced = true;
13203                }catch(_){}
13204            }
13205        } else {
13206            if (titlePlaced && titleRow && d) {
13207                try{
13208                    const vcombined = document.getElementById('pd-variant-combined');
13209                    if (vcombined && d.contains(vcombined)) {
13210                        d.insertBefore(titleRow, vcombined);
13211                    } else {
13212                        d.insertBefore(titleRow, d.firstChild);
13213                    }
13214                    titlePlaced=false;
13215                }catch(_){}
13216            }
13217            if (pricePlaced && b && c && c.parentNode) {
13218                try{c.parentNode.insertBefore(b, c.nextSibling);pricePlaced=false;}catch(_){}
13219            }
13220            if (varPlaced) {
13221                try{
13222                    if (i && j && j.parentNode && !j.parentNode.contains(i)) j.parentNode.insertBefore(i, j.nextSibling);
13223                    if (g && h && h.parentNode && !h.parentNode.contains(g)) h.parentNode.insertBefore(g, h.nextSibling);
13224                    if (f) { f.hidden = true; f.innerHTML = ''; }
13225                    varPlaced = false;
13226                }catch(_){}
13227            }
13228            if (promoPlaced && promoBanner && b) {
13229                try{
13230                    b.appendChild(promoBanner);
13231                    promoPlaced = false;
13232                }catch(_){}
13233            }
13234        }
13235    }
13236
13237    mq.addEventListener ? mq.addEventListener('change', apply) : mq.addListener(apply);
13238    apply();
13239
13240    const fab = document.getElementById('pd-mobile-fab');
13241    if (fab) {
13242        const pid = parseInt(fab.getAttribute('data-pid')||'0',10) || 325;
13243        const btnCart = document.getElementById('mf-cart');
13244
13245        if (btnCart) btnCart.addEventListener('click', function(){
13246            const varInput = document.getElementById('pd-variant-id');
13247            const variantId = varInput ? parseInt(varInput.value||'0',10) : 0;
13248            try { if (window.addToCart) addToCart(pid, 1, variantId); } catch(_){}
13249            const variantText = getVariantSummaryText();
13250            const skuText = (function(){ const el=document.getElementById('pd-sku'); return el?el.textContent.trim():PD_SKU; })();
13251            showToast({
13252                type: 'success',
13253                title: 'Berhasil ditambahkan ke Keranjang',
13254                message: `<strong>${escapeHtml(PD_NAME)}</strong>${variantText?` · <em>${escapeHtml(variantText)}</em>`:''}<br>SKU: ${escapeHtml(skuText)} · Qty: 1`,
13255                duration: 5000,
13256                actions: [
13257                    { label: 'Lihat Keranjang', onClick: ()=> { window.location.href = '/keranjang'; } },
13258                    { label: 'Lanjut Belanja', variant: 'alt', onClick: ()=>{} }
13259                ]
13260            });
13261        });
13262    }
13263})();
13264
13265/* ====== Auto-hide installment banner if sidebar too tall (Desktop only) ====== */
13266(function(){
13267    function adjustSidebarBanner() {
13268        if (window.innerWidth <= 768) return;
13269        const sidebar = document.querySelector('.pd-sidebar');
13270        const infoCard = document.querySelector('.pd-info-card');
13271        const banner = document.querySelector('.pd-side-card.inst-alt-banner.inst-alt-banner--compact');
13272        if (!sidebar || !infoCard || !banner) return;
13273        const sidebarHeight = sidebar.offsetHeight;
13274        const infoCardHeight = infoCard.offsetHeight;
13275        if (sidebarHeight > infoCardHeight) {
13276            banner.style.display = 'none';
13277        } else {
13278            banner.style.display = '';
13279        }
13280    }
13281    if (document.readyState === 'loading') {
13282        document.addEventListener('DOMContentLoaded', adjustSidebarBanner);
13283    } else {
13284        adjustSidebarBanner();
13285    }
13286    let resizeTimer;
13287    window.addEventListener('resize', function() {
13288        clearTimeout(resizeTimer);
13289        resizeTimer = setTimeout(adjustSidebarBanner, 250);
13290    });
13291    const pwpSection = document.getElementById('pwp-deals-section');
13292    if (pwpSection) {
13293        const observer = new MutationObserver(function(mutations) {
13294            let shouldRecheck = false;
13295            mutations.forEach(function(mutation) {
13296                if (mutation.type === 'attributes' && mutation.attributeName === 'hidden') {
13297                    shouldRecheck = true;
13298                } else if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
13299                    shouldRecheck = true;
13300                }
13301            });
13302            if (shouldRecheck) {
13303                setTimeout(adjustSidebarBanner, 100);
13304            }
13305        });
13306        observer.observe(pwpSection, {
13307            attributes: true,
13308            childList: true,
13309            subtree: true
13310        });
13311    }
13312    window.adjustSidebarBanner = adjustSidebarBanner;
13313})();
13314
13315/* ====== Global constants ====== */
13316const IS_DIGITAL=false;
13317const IS_PREORDER=false;
13318
13319/* ====== Variants state (REVISED) ====== */
13320(function initVariants(){
13321const _0x7g8h=[];window.variants=_0x7g8h;
13322window.isPreorderProduct=false;
13323    const attrOrder = [];
13324
13325    if (!variants.length || !attrOrder.length) return;
13326
13327    function comboKey(sel){
13328        return attrOrder.map(a => a + '=' + (sel[a] ?? '')).join('|');
13329    }
13330
13331    const variantMap = {};
13332    variants.forEach(v=>{
13333        const key = comboKey(v.attrs);
13334        variantMap[key] = v;
13335    });
13336
13337    function readSelection(){
13338        const sel = {};
13339        attrOrder.forEach(a=>{
13340            const r = document.querySelector(`.v-group[data-attr="${CSS.escape(a)}"] input[type=radio]:checked`);
13341            sel[a] = r ? r.value : '';
13342        });
13343        return sel;
13344    }
13345
13346    function findVariant(sel){
13347        return variantMap[comboKey(sel)] || null;
13348    }
13349
13350    function autoFillMissing(sel){
13351        let changed = false;
13352        for (const a of attrOrder) {
13353            if (sel[a]) continue;
13354            const possible = [];
13355            const seen = new Set();
13356            variants.forEach(v=>{
13357                let ok = true;
13358                for (const a2 of attrOrder) {
13359                    if (a2 === a) continue;
13360                    if (sel[a2] && v.attrs[a2] !== sel[a2]) { ok = false; break; }
13361                }
13362                if (ok) {
13363                    const val = v.attrs[a];
13364                    if (!seen.has(val)) { seen.add(val); possible.push(val); }
13365                }
13366            });
13367            if (possible.length === 1) {
13368                const val = possible[0];
13369                const input = document.querySelector(`.v-group[data-attr="${CSS.escape(a)}"] input[type=radio][value="${CSS.escape(val)}"]`);
13370                if (input && !input.checked) { input.checked = true; sel[a] = val; changed = true; }
13371            }
13372        }
13373        return changed;
13374    }
13375
13376    function rupiah(n){
13377        n = parseInt(n||0,10)||0;
13378        return 'Rp ' + n.toString().replace(/B(?=(d{3})+(?!d))/g,'.');
13379    }
13380
13381    function focusGalleryByUrl(imageUrl) {
13382        const isMobile = window.matchMedia && window.matchMedia('(max-width: 640px)').matches;
13383          if (isMobile) {
13384            _0xau1v(imageUrl);
13385            return;
13386          }
13387
13388          const gallery = document.getElementById('pd-thumb-gallery');
13389          const targetName = _0x8s9t(imageUrl);
13390
13391          if (gallery) {
13392            const btns = Array.from(gallery.querySelectorAll('.pd-thumb-btn'));
13393            const btn = btns.find(b => _0x8s9t(b.getAttribute('data-full') || '') === targetName);
13394            if (btn) { btn.click(); return; }
13395          }
13396
13397          _0xau1v(imageUrl);
13398    }
13399
13400
13401
13402function formatStockLabel(n){n=parseInt(n||0,10)||0;return(n>10)?'10+':String(n)}
13403function updateBranchPopover(v){try{if(!v)return;const a=document.getElementById('sp-qty-meg'),b=document.getElementById('sp-qty-mdf'),c=document.getElementById('sp-qty-bda');if(a)a.textContent=formatStockLabel(Math.max(0,v.smm|0));if(b)b.textContent=formatStockLabel(Math.max(0,v.smf|0));if(c)c.textContent=formatStockLabel(Math.max(0,v.sba|0))}catch(_){}}
13404
13405    function calcVoucherDisc(type,value,price,maxDisc){if(type==='PERCENTAGE'){let d=(price*value)/100;if(maxDisc>0&&d>maxDisc)d=maxDisc;return Math.round(d)}return Math.round(value)}
13406
13407    function applyVariant(v, focusImage = true){
13408        const elCur  = document.getElementById('pd-price-current');
13409        const elOri  = document.getElementById('pd-price-original');
13410        const elDisc = document.getElementById('pd-discount-badge');
13411        const elSku  = document.getElementById('pd-sku');
13412        const elStock= document.getElementById('pd-stock-badge');
13413        const elQty  = document.getElementById('qty');
13414        const btnAdd = document.getElementById('btn-add-cart');
13415        const btnBuy = document.getElementById('btn-buy');
13416        const hidVar = document.getElementById('pd-variant-id');
13417
13418        const mfCart= document.getElementById('mf-cart');
13419        const mfBuy = document.getElementById('mf-buy');
13420        const fab = document.getElementById('pd-mobile-fab');
13421
13422        if (hidVar) hidVar.value = v ? v.id : 0;
13423        if (elCur) elCur.textContent = rupiah(v ? v.final : 0);
13424        if (elSku && v && v.sku) elSku.textContent = v.sku;
13425
13426        if (v && v.ori > v.final) {
13427            if (elOri) { elOri.style.display='inline'; elOri.textContent = rupiah(v.ori); }
13428            if (elDisc) { elDisc.style.display='inline'; elDisc.textContent = '-' + (v.disc||0) + '%'; }
13429        } else {
13430            if (elOri) elOri.style.display='none';
13431            if (elDisc) elDisc.style.display='none';
13432        }
13433
13434        const stock = v ? (parseInt(v.stock||0,10)||0) : 0;
13435        let effectiveStock = stock;
13436        let isActivePreorder = false;
13437        if (window.isPreorderProduct && v && 'preorder_remaining' in v && v.preorder_remaining !== null) {
13438            effectiveStock = parseInt(v.preorder_remaining, 10);
13439            if (isNaN(effectiveStock)) effectiveStock = 0;
13440            isActivePreorder = true;
13441        }
13442        if (elStock){
13443            elStock.classList.remove('in','out');
13444            if (effectiveStock>0){ 
13445                elStock.classList.add('in'); 
13446                elStock.textContent = IS_DIGITAL ? 'Tersedia' : (isActivePreorder ? 'Sisa Slot: ' + effectiveStock : 'Stok: ' + formatStockLabel(effectiveStock)); 
13447            } else { 
13448                elStock.classList.add('out'); 
13449                elStock.textContent = 'Habis'; 
13450            }
13451        }
13452        if (elQty){
13453            elQty.max = String(effectiveStock>0?effectiveStock:0);
13454            if (effectiveStock<=0) { elQty.value = '0'; elQty.disabled = true; }
13455            else { if (parseInt(elQty.value||'1',10) < 1) elQty.value='1'; elQty.disabled=false; }
13456        }
13457
13458        const shouldDisable = !(v && effectiveStock>0);
13459        const updateButtons = () => {
13460            const btnAddCart = document.getElementById('btn-add-cart');
13461            const btnBuyNow = document.getElementById('btn-buy');
13462            const mobileCart = document.getElementById('mf-cart');
13463            const mobileBuy = document.getElementById('mf-buy');
13464            [btnAddCart, btnBuyNow, mobileCart, mobileBuy].forEac
13464h(btn => {
13465                if (!btn) return;
13466                if (shouldDisable) {
13467                    btn.disabled = true;
13468                    btn.setAttribute('disabled', 'disabled');
13469                    btn.classList.add('stock-disabled');
13470                    btn.setAttribute('data-stock-blocked', 'true');
13471                    btn.setAttribute('data-stock-reason', window.isPreorderProduct ? 'Pre-order habis' : 'Stok habis');
13472                } else {
13473                    btn.disabled = false;
13474                    btn.removeAttribute('disabled');
13475                    btn.classList.remove('stock-disabled');
13476                    btn.removeAttribute('data-stock-blocked');
13477                    btn.removeAttribute('data-stock-reason');
13478                }
13479            });
13480        };
13481        updateButtons();
13482        setTimeout(updateButtons, 10);
13483        setTimeout(updateButtons, 50);
13484        setTimeout(updateButtons, 100);
13485        if (mfBuy && v) {
13486            const voucherBtn = document.getElementById('btn-buy-voucher');
13487            const voucherCode = fab ? fab.dataset.voucherCode : '';
13488            let voucherDiscount = 0;
13489
13490            if (voucherBtn && voucherCode) {
13491                const vType = voucherBtn.dataset.voucherType;
13492                const vValue = parseFloat(voucherBtn.dataset.voucherValue) || 0;
13493                const vMax = parseFloat(voucherBtn.dataset.voucherMax) || 0;
13494                const finalPrice = v.final || 0;
13495                if (vType && vValue > 0) {
13496                    voucherDiscount = calcVoucherDisc(vType, vValue, finalPrice, vMax);
13497                    if (fab) fab.dataset.voucherValue = voucherDiscount.toString();
13498                }
13499            }
13500
13501            const finalPrice = v.final || 0;
13502            const textEl = mfBuy.querySelector('.mf-buy-text');
13503            const priceEl = mfBuy.querySelector('.mf-buy-price');
13504
13505            if (effectiveStock > 0) {
13506                    if (IS_PREORDER) {
13507                        if (textEl) textEl.textContent = 'Pre Order Sekarang';
13508                        if (priceEl) priceEl.textContent = rupiah(finalPrice);
13509                    } else if (voucherCode && voucherDiscount > 0) {
13510                        if (textEl) textEl.textContent = 'Beli dengan Voucher';
13511                        if (priceEl) priceEl.textContent = rupiah(Math.max(0, finalPrice - voucherDiscount));
13512                    } else {
13513                        if (textEl) textEl.textContent = 'Lihat Daftar BOYTOTO Terbaru';
13514                        if (priceEl) priceEl.textContent = rupiah(finalPrice);
13515                    }
13516                } else {
13517                    if (textEl) textEl.textContent = 'Stok Habis';
13518                    if (priceEl) priceEl.textContent = rupiah(finalPrice);
13519                }
13520            }
13521
13522        const isMobile = window.initMobile ?? 0;
13523        if (window.instSetBasePrice) window.instSetBasePrice(v ? (isMobile == '1' ? v.ori : v.final) : 0);
13524        if (window.updateSoldBySku) window.updateSoldBySku(v.sku);
13525        updateBranchPopover(v);
13526        if (v && v.image_url && focusImage) focusGalleryByUrl(v.image_url);
13527        if (v) {
13528            window.dispatchEvent(new CustomEvent('pd-variant-changed', {
13529                detail: {
13530                    price: v.final,
13531                    originalPrice: v.ori,
13532                    discount: v.disc,
13533                    stock: effectiveStock,
13534                    sku: v.sku,
13535                    variant_id: v.id
13536                }
13537            }));
13538
13539            const encodedHash = encodeVariantHash(v.id);
13540            if (encodedHash && window.history && window.history.replaceState) {
13541                const newHash = '#v=' + encodedHash;
13542                const newUrl = window.location.pathname + window.location.search + newHash;
13543                window.history.replaceState(null, '', newUrl);
13544            }
13545        }
13546    }
13547
13548    let sel = readSelection();
13549    if (autoFillMissing(sel)) sel = readSelection();
13550
13551    let v = findVariant(sel);
13552    if (!v) {
13553        const candidates = variants.filter(variant=>{
13554            return attrOrder.every(a=>{
13555                if (!sel[a]) return true;
13556                return variant.attrs[a] === sel[a];
13557            });
13558        });
13559        if (candidates.length >= 1) {
13560            v = candidates[0];
13561            attrOrder.forEach(a=> sel[a] = v.attrs[a]);
13562            attrOrder.forEach(a=>{
13563                const val = sel[a];
13564                const input = document.querySelector(`.v-group[data-attr="${CSS.escape(a)}"] input[type=radio][value="${CSS.escape(val)}"]`);
13565                if (input) input.checked = true;
13566            });
13567        }
13568    }
13569
13570    const initSel = readSelection();
13571    const initVar = findVariant(initSel) || variants[0] || null;
13572    if (document.readyState === 'loading') {
13573        document.addEventListener('DOMContentLoaded', () => applyVariant(initVar, false));
13574    } else {
13575        applyVariant(initVar, false);
13576    }
13577
13578function updateSummary(sel){Object.entries(sel).forEach(([a,val])=>{const el=document.getElementById('vs-'+a);if(el)el.textContent=val||'-'})}
13579    function refreshOptions(sel){
13580        attrOrder.forEach(a=>{
13581            const group = document.querySelector(`.v-group[data-attr="${CSS.escape(a)}"]`);
13582            if (!group) return;
13583            const chips = group.querySelectorAll('.v-chip');
13584            chips.forEach(ch=>{
13585                const val = ch.getAttribute('data-value');
13586                const possible = variants.some(v=>{
13587                    if (v.attrs[a] !== val) return false;
13588                    for (const a2 of attrOrder) {
13589                        if (a2 === a) continue;
13590                        if (sel[a2] && v.attrs[a2] !== sel[a2]) return false;
13591                    }
13592                    return true;
13593                });
13594                const input = ch.querySelector('input[type=radio]');
13595                const disabled = !possible;
13596                if (input) input.disabled = disabled;
13597                ch.classList.toggle('disabled', disabled);
13598
13599                let candidateVariant = null;
13600                if (possible) {
13601                    candidateVariant = variants.find(v=>{
13602                        if (v.attrs[a] !== val) return false;
13603                        for (const a2 of attrOrder) {
13604                            if (a2 === a) continue;
13605                            if (sel[a2] && v.attrs[a2] !== sel[a2]) return false;
13606                        }
13607                        return true;
13608                    });
13609                }
13610                const outOfStock = candidateVariant ? (window.isPreorderProduct && candidateVariant.preorder_remaining !== null ? candidateVariant.preorder_remaining <= 0 : candidateVariant.stock <= 0) : false;
13611                ch.classList.toggle('out', !disabled && outOfStock);
13612                const badge = ch.querySelector('.chip-badge');
13613                if (badge) badge.hidden = !(candidateVariant && outOfStock);
13614            });
13615            group.querySelectorAll('.v-chip').forEach(ch=>{
13616                const input = ch.querySelector('input[type=radio]');
13617                ch.classList.toggle('selected', input && input.checked);
13618            });
13619        });
13620    }
13621    updateSummary(sel);
13622    refreshOptions(sel);
13623    document.querySelectorAll('.v-chip').forEach(chip=>{const input=chip.querySelector('input[type=radio]');if(input&&input.checked){chip.classList.add('selected');}});
13624
13625    const container = document.getElementById('pd-variants');
13626    container && container.addEventListener('change', function(ev){
13627        const radio = ev.target.closest('input[type=radio]');
13628        if (!radio) return;
13629        sel = readSelection();
13630        if (autoFillMissing(sel)) sel = readSelection();
13631        const chosen = findVariant(sel);
13632        applyVariant(chosen || null, true);
13633
13634        updateSummary(sel);
13635        refreshOptions(sel);
13636    });
13637
13638window.applyVariant=applyVariant;
13639})();
13640
13641(function cleanBrokenDescImages(){
13642    const containers = [
13643        document.querySelector('.pd-full-desc-body'),
13644        document.querySelector('.pd-short-desc')
13645    ].filter(Boolean);
13646
13647    containers.forEach(root => {
13648        const imgs = root.querySelectorAll('img');
13649        imgs.forEach(img => {
13650            if (img.complete && img.naturalWidth === 0) {
13651                img.remove();
13652                return;
13653            }
13654            img.addEventListener('error', () => {
13655                img.remove();
13656            }, { once:true });
13657        });
13658    });
13659})();
13660
13661/* ====== Scroll To Top ====== */
13662(function _0xi25j(){const a=document.getElementById('pd-scroll-top');if(!a)return;function b(){if(window.matchMedia('(max-width:640px)').matches){const d=document.querySelector('.bottom-nav'),e=document.querySelector('.pd-mobile-fab');if(!d&&!e){setTimeout(b,100);return}const f=d&&window.getComputedStyle(d).display!=='none'&&window.getComputedStyle(d).visibility!=='hidden',g=e&&window.getComputedStyle(e).display!=='none'&&window.getComputedStyle(e).visibility!=='hidden';let h;if(f&&g){h='calc(68px + 54px + env(safe-area-inset-bottom))'}else if(g){h='calc(68px + 8px + env(safe-area-inset-bottom))'}else if(f){h='calc(60px + env(safe-area-inset-bottom))'}else{h='calc(20px + env(safe-area-inset-bottom))'}a.style.setProperty('--scroll-top-bottom',h)}else{a.style.removeProperty('--scroll-top-bottom')}}function c(){const d=window.pageYOffset||document.documentElement.scrollTop||0,e=d>300;a.classList.toggle('visible',e);a.setAttribute('aria-hidden',e?'false':'true');b()}window.addEventListener('scroll',c,{passive:!0});window.addEventListener('resize',b,{passive:!0});setTimeout(b,100);c();a.addEventListener('click',function(){window.scrollTo({top:0,behavior:'smooth'})})})();
13663
13664/* Format stok 10+ */
13665function _0xk67l(n){n=parseInt(n||0,10)||0;return n>10?'10+':String(n)}
13666
13667/* Toggle popover stok per cabang */
13668(function _0xm89n(){const a=document.getElementById('pd-stock-info-btn'),b=document.getElementById('pd-stock-popover'),c=document.getElementById('pd-stock-popover-close');if(!a||!b)return;function d(){if(window.matchMedia('(min-width: 641px)').matches){const e=document.getElementById('pd-stock-badge');if(!e)return;const f=e.getBoundingClientRect(),g=window.pageXOffset||document.documentElement.scrollLeft||0,h=window.pageYOffset||document.documentElement.scrollTop||0;b.style.left=(f.left+g-10)+'px';b.style.top=(f.bottom+h+10)+'px'}else{b.style.left='';b.style.top=''}}
13669function e(){d();b.classList.add('show');a.setAttribute('aria-expanded','true');b.setAttribute('aria-hidden','false');document.addEventListener('click',g,{capture:true});window.addEventListener('resize',d,{passive:true});window.addEventListener('scroll',d,{passive:true})}function f(){b.classList.remove('show');a.setAttribute('aria-expanded','false');b.setAttribute('aria-hidden','true');document.removeEventListener('click',g,{capture:true});window.removeEventListener('resize',d);window.removeEventListener('scroll',d)}function g(h){if(b.contains(h.target)||a.contains(h.target))return;f()}
13670a.addEventListener('click',function(h){h.preventDefault();b.classList.contains('show')?f():e()});if(c)c.addEventListener('click',f)})();
13671
13672/* ===== Share Product Modal ===== */
13673(function(){const a=document.getElementById('pd-share-btn'),z=document.getElementById('mf-share'),b=document.getElementById('pd-share-modal-overlay'),c=document.getElementById('pd-share-close'),d=document.getElementById('pd-share-copy-url-btn'),e=document.getElementById('pd-share-url-input');if((!a&&!z)||!b)return;const f="https://mgsco.org/",g="BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler",h="Referensi Shio";const discountPercent=1;let i='';if(discountPercent>0){i=`🔥 Lagi Promo Nih! Diskon ${discountPercent}%nn✨ ${g}n💰 Cuma ${h} aja!nn🛒 Buruan cek sebelum kehabisan!n👉 `}else{i=`✨ Ada barang bagus nih!nn📦 ${g}n💸 Harga: ${h}nn🛍️ Yuk, langsung cek!n👉 `}function j(){b.classList.add('show');document.body.style.overflow='hidden'}function k(){b.classList.remove('show');document.body.style.overflow=''}if(a)a.addEventListener('click',j);if(z)z.addEventListener('click',j);if(c)c.addEventListener('click',k);b.addEventListener('click',function(l){if(l.target===b)k()});document.addEventListener('keydown',function(l){if(l.key==='Escape'&&b.classList.contains('show'))k()});b.addEventListener('click',function(l){const m=l.target.closest('[data-share]');if(!m)return;const n=m.getAttribute('data-share'),o=encodeURIComponent(f),p=e
13673ncodeURIComponent(i),q=encodeURIComponent(g);let r='';switch(n){case 'whatsapp':r=`https://wa.me/?text=${p}${o}`;break;case 'facebook':r=`https://www.facebook.com/sharer/sharer.php?u=${o}`;break;case 'twitter':r=`https://twitter.com/intent/tweet?text=${p}${o}`;break;case 'telegram':r=`https://t.me/share/url?url=${o}&text=${p}`;break;case 'email':r=`mailto:?subject=${encodeURIComponent('🔥 Cek Produk Keren Ini!')}&body=${p}${o}`;window.location.href=r;k();return;case 'copy':s();return;case 'pdf':const ps=document.querySelector('.product-single');if(ps){ps.setAttribute('data-print-date',new Date().toLocaleDateString('id-ID',{year:'numeric',month:'long',day:'numeric'}))}k();setTimeout(function(){window.print();try{if(window.ga4Ecomm&&window.ga4Ecomm.trackEvent)window.ga4Ecomm.trackEvent('share',{method:'pdf_export',content_type:'product',item_id:"SB0B3T"})}catch(_){}},300);return}if(r){window.open(r,'_blank','noopener,noreferrer');try{if(window.ga4Ecomm&&window.ga4Ecomm.trackEvent)window.ga4Ecomm.trackEvent('share',{method:n,content_type:'product',item_id:"SB0B3T"})}catch(_){}}});function s(){if(!e)return;e.select();e.setSelectionRange(0,99999);try{if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(f).then(function(){t()}).catch(function(){document.execCommand('copy');t()})}else{document.execCommand('copy');t()}}catch(u){console.error('Failed to copy:',u)}}function t(){if(!d)return;const v=d.innerHTML;d.classList.add('copied');d.innerHTML='<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg><span class="pd-share-copy-text">Tersalin!</span>';if(window.showToast)showToast({type:'success',title:'✨ Link Tersalin!',message:'Link produk berhasil disalin. Yuk bagikan ke teman!',duration:3000});setTimeout(function(){d.classList.remove('copied');d.innerHTML=v},2000)}if(d)d.addEventListener('click',s);if(navigator.share){const shareHandler=function(w){w.preventDefault();navigator.share({title:g,text:i+f,url:f}).catch(function(x){console.log('Native share cancelled or failed:',x)})};if(a)a.addEventListener('dblclick',shareHandler);if(z)z.addEventListener('dblclick',shareHandler)}})();
13674
13675/* ===== Embedding variants (server → client) ===== */
13676(function(){
13677  window.__PD_VARIANTS_WITH_BRANCH__ = [];
13678})();
13679
13680/* ===== Helper: update angka popover ===== */
13681function updateStockPopoverForVariant(v){
13682  try{
13683    const elMeg = document.getElementById('sp-qty-meg');
13684    const elMdf = document.getElementById('sp-qty-mdf');
13685    const elBda = document.getElementById('sp-qty-bda');
13686
13687    if (typeof v?.smm === 'number' && elMeg) elMeg.textContent = formatStockLabel(Math.max(0, v.smm|0));
13688    if (typeof v?.smf === 'number' && elMdf) elMdf.textContent = formatStockLabel(Math.max(0, v.smf|0));
13689    if (typeof v?.sba === 'number' && elBda) elBda.textContent = formatStockLabel(Math.max(0, v.sba|0));
13690  }catch(_){}
13691}
13692
13693/* ===== applyVariant: 10+ dan update popover ===== */
13694(function patchApplyVariant(){
13695  if (typeof applyVariant !== 'function') return;
13696  const original = applyVariant;
13697  window.applyVariant = function(v){
13698    original(v);
13699
13700    try {
13701      const elStock = document.getElementById('pd-stock-badge');
13702      if (elStock){
13703        let txt = elStock.textContent || '';
13704        let m = txt.match(/d+/);
13705        if (m) {
13706          const n = parseInt(m[0]||'0',10)||0;
13707          elStock.textContent = (n>0) ? ('Stok: ' + formatStockLabel(n)) : 'Habis';
13708        }
13709      }
13710    } catch(_){}
13711if(window.__PD_VARIANTS_WITH_BRANCH__&&v){const vv=window.__PD_VARIANTS_WITH_BRANCH__.find(x=>x.id===v.id)||v;updateStockPopoverForVariant(vv)}
13712  };
13713})();
13714
13715(function initCurrentVariantBranch(){
13716  const list = window.__PD_VARIANTS_WITH_BRANCH__ || [];
13717  if (!list.length) return;
13718  const hid = document.getElementById('pd-variant-id');
13719  const vid = hid ? parseInt(hid.value||'0',10) : 0;
13720  const current = list.find(x => x.id === vid) || list[0] || null;
13721  if (current) updateStockPopoverForVariant(current);
13722})();
13723
13724(function(){
13725    if (!window.ga4Ecomm) return;
13726    const catSegs = ["Komputer & Laptop","Laptop","Gaming"];
13727    window.ga4Ecomm.viewItem({
13728      currency: 'IDR',
13729      value: 20789000,
13730      item_id: "SB0B3T",
13731      item_name: "BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler",
13732      item_brand: "BOYTOTO",
13733      item_category: catSegs && catSegs.length ? catSegs.join(' / ') : ''
13734    });
13735  })();
13736
13737(function patchApplyVariantFocus(){
13738  if (typeof applyVariant !== 'function') return;
13739  const original = applyVariant;
13740  window.applyVariant = function(v, focusImage = true){
13741
13742    original(v, focusImage);
13743
13744    if (focusImage && v && v.image_url) {
13745      _0xau1v(v.image_url);
13746    }
13747  };
13748})();
13749
13750(function() {
13751  var soldMap = [];
13752  var soldTotalLabel = "Terjual 2";
13753
13754  function formatSoldLabel(qty) {
13755    if (qty >= 1000000) {
13756      var v = Math.floor((qty / 100000) ) / 10;
13757      return 'Terjual ' + (String(v).replace('.', ',')) + 'jt+';
13758    }
13759    if (qty >= 1000) {
13760      var v2 = Math.floor((qty / 100) ) / 10;
13761      return 'Terjual ' + (String(v2).replace('.', ',')) + 'rb+';
13762    }
13763    if (qty >= 100) {
13764      var v2 = Math.floor((qty / 100) ) * 100;
13765      return 'Terjual ' + (String(v2).replace('.', ',')) + '+';
13766    }
13767    if (qty >= 10) {
13768      var v2 = Math.floor((qty / 10) ) * 10;
13769      return 'Terjual ' + (String(v2).replace('.', ',')) + '+';
13770    }
13771    return 'Terjual ' + (qty.toLocaleString('id-ID'));
13772  }
13773
13774  function updateSoldLabel(labelText) {
13775    var el = document.querySelector('#pd-sold-inline, [data-role="sold-chip"]');
13776    if (el) el.textContent = labelText;
13777  }
13778
13779  window.updateSoldBySku = function(sku) {
13780    if (!sku || typeof sku !== 'string') {
13781      updateSoldLabel(soldTotalLabel);
13782      return;
13783    }
13784    var qty = soldMap[sku] || 0;
13785    updateSoldLabel(formatSoldLabel(qty));
13786  };
13787
13788    updateSoldLabel(soldTotalLabel);
13789  })();
13790
13791(function(){const h=document.getElementById('pd-sticky-header');if(!h)return;const p=document.getElementById('pd-price-anchor');const ac=document.getElementById('pd-sticky-add-cart');const by=document.getElementById('pd-sticky-buy');let lt=window.scrollY;let tk=false;function u(){if(!p)return;const r=p.getBoundingClientRect();const s=window.scrollY;if(r.top<0){h.classList.add('visible')}else{h.classList.remove('visible')}lt=s;tk=false}function rt(){if(!tk){window.requestAnimationFrame(u);tk=true}}window.addEventListener('scroll',rt,{passive:true});u();function sy(){const ma=document.getElementById('btn-add-cart');const mb=document.getElementById('btn-buy');if(ac&&ma){ac.disabled=ma.disabled}if(by&&mb){by.disabled=mb.disabled}}sy();const ob=new MutationObserver(sy);const ma=document.getElementById('btn-add-cart');const mb=document.getElementById('btn-buy');if(ma)ob.observe(ma,{attributes:true,attributeFilter:['disabled']});if(mb)ob.observe(mb,{attributes:true,attributeFilter:['disabled']});const ss=document.getElementById('pd-sticky-share-btn');const ms=document.getElementById('pd-share-btn');if(ss&&ms){ss.addEventListener('click',function(e){e.preventDefault();ms.click()})}if(ac){ac.addEventListener('click',function(e){e.preventDefault();if(ac.disabled)return;const m=document.getElementById('btn-add-cart');if(m&&!m.disabled){m.click()}})}if(by){by.addEventListener('click',function(e){e.preventDefault();if(by.disabled)return;const m=document.getElementById('btn-buy');if(m&&!m.disabled&&window.buyNow){window.buyNow(e)}})}
13792function getChatProductData(){const varInput=document.getElementById('pd-variant-id');const variantId=varInput?parseInt(varInput.value||'0',10):0;const variantSummary=getVariantSummaryText();const priceEl=document.getElementById('pd-price-current');const currentPrice=priceEl?priceEl.textContent.trim():'Referensi Shio';const pd={id:325,name:"BOYTOTO: Showroom Situs Boy Toto Kita Online dengan Beragam Games Menarik dan Terpopuler",price:currentPrice,image:"/uploads/products/BOYTOTO",url:window.location.href};if(variantId>0){pd.variant_id=variantId}if(variantSummary){pd.variant_summary=variantSummary}return pd}
13793const ch=document.getElementById('pd-sticky-chat');if(ch){ch.addEventListener('click',function(e){e.preventDefault();if(!window.plazaChat){console.warn('Chat widget not loaded');return}window.plazaChat.attachProduct(getChatProductData());window.plazaChat.openWidget()})}const dch=document.getElementById('btn-chat-icon');if(dch){dch.addEventListener('click',function(e){e.preventDefault();if(!window.plazaChat){console.warn('Chat widget not loaded');return}window.plazaChat.attachProduct(getChatProductData());window.plazaChat.openWidget()})}const mch=document.getElementById('mf-chat');if(mch){mch.addEventListener('click',function(e){e.preventDefault();if(!window.plazaChat){console.warn('Chat widget not loaded');return}window.plazaChat.attachProduct(getChatProductData());window.plazaChat.openWidget()})}window.addEventListener('pd-variant-changed',function(e){const d=e.detail||{};if(d.price!==undefined){const sp=document.getElementById('pd-sticky-price');if(sp){sp.textContent=formatRupiah(d.price)}}if(d.originalPrice!==undefined&&d.originalPrice>d.price){const so=document.getElementById('pd-sticky-price-original');if(so){so.textContent=formatRupiah(d.originalPrice);so.style.display=''}}else{const so=document.getElementById('pd-sticky-price-original');if(so){so.style.display='none'}}if(d.discount!==undefined&&d.discount>0){const sd=document.getElementById('pd-sticky-discount');if(sd){sd.textContent='-'+d.discount+'%';sd.style.display=''}}else{const sd=document.getElementById('pd-sticky-discount');if(sd){sd.style.display='none'}}const hs=d.stock>0;if(ac){ac.disabled=!hs}if(by){by.disabled=!hs}});
13793function formatRupiah(n){return'Rp'+Math.floor(n).toLocaleString('id-ID')}})();
13794
13795(function(){const t=document.getElementById('pdv-toggle-btn');const v=document.getElementById('pd-voucher-section');if(t&&v){const h=v.querySelectorAll('.pdv-min-hidden');const n=h.length;t.addEventListener('click',function(){const e=t.classList.toggle('expanded');h.forEach(function(i){if(e){i.classList.remove('pdv-min-hidden')}else{i.classList.add('pdv-min-hidden')}});t.textContent=e?'Tutup':'+'+n})}const m=document.getElementById('voucher-detail-modal');const mc=document.getElementById('voucher-modal-close');const ma=document.getElementById('voucher-modal-cancel');const mcp=document.getElementById('voucher-modal-copy');if(m){const bb=document.querySelectorAll('.pdv-badge-btn');bb.forEach(function(btn){btn.addEventListener('click',function(){if(btn.disabled)return;const c=btn.getAttribute('data-voucher-code');const nm=btn.getAttribute('data-voucher-name');const dl=btn.getAttribute('data-discount-label');const bl=btn.getAttribute('data-badge-label');const mp=parseInt(btn.getAttribute('data-min-purchase'));const md=parseInt(btn.getAttribute('data-max-discount'));const vu=btn.getAttribute('data-valid-until');document.getElementById('voucher-modal-badge').innerHTML='<span class="vmb-value">'+dl+'</span><span class="vmb-label">'+bl+'</span>';document.getElementById('voucher-modal-code').textContent=c;document.getElementById('voucher-modal-name').textContent=nm;const me=document.getElementById('voucher-modal-min');if(mp>0){me.style.display='';document.getElementById('voucher-modal-min-val').textContent=fr(mp)}else{me.style.display='none'}const xe=document.getElementById('voucher-modal-max');if(md>0){xe.style.display='';document.getElementById('voucher-modal-max-val').textContent=fr(md)}else{xe.style.display='none'}if(vu){const d=new Date(vu);const ft=d.toLocaleDateString('id-ID',{day:'numeric',month:'long',year:'numeric',hour:'2-digit',minute:'2-digit'});document.getElementById('voucher-modal-valid-val').textContent=ft}mcp.setAttribute('data-code',c);m.classList.add('show');document.body.style.overflow='hidden'})});function cm(){m.classList.remove('show');document.body.style.overflow=''}if(mc)mc.addEventListener('click',cm);if(ma)ma.addEventListener('click',cm);m.addEventListener('click',function(e){if(e.target===m)cm()});if(mcp){mcp.addEventListener('click',function(){const c=mcp.getAttribute('data-code');if(!c)return;if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(c).then(function(){smc()}).catch(function(err){fc(c)})}else{fc(c)}})}}const b=document.querySelectorAll('.pdv-min-copy');b.forEach(function(btn){btn.addEventListener('click',function(){if(btn.disabled)return;const code=btn.getAttribute('data-code');if(!code)return;if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(code).then(function(){sc(btn)}).catch(function(err){fc(code,btn)})}else{fc(code,btn)}})});function fc(text,btn){const tx=document.createElement('textarea');tx.value=text;tx.style.position='fixed';tx.style.opacity='0';document.body.appendChild(tx);tx.select();try{document.execCommand('copy');if(btn)sc(btn);else smc()}catch(err){}document.body.removeChild(tx)}function sc(btn){const o=btn.textContent;btn.classList.add('copied');btn.textContent='✓ Tersalin';setTimeout(function(){btn.classList.remove('copied');btn.textContent=o},2000)}function smc(){const o=mcp.textContent;mcp.textContent='✓ Kode Tersalin';mcp.style.background='#22c55e';setTimeout(function(){mcp.textContent=o;mcp.style.background=''},2000)}function fr(a){return'Rp'+Math.floor(a).toLocaleString('id-ID')}})();
13796</script>
vendor: 2 bytes, line 13796
13796
13797<script nonce="se3iyzEHMc9FQEB4wSyd0w==">
13798window.PD_ID=325;window.PD_NAME=_0x3c4d;window.PD_SKU=_0x5e6f;window.PD_CATEGORY_PATH="Komputer & Laptop/Laptop/Gaming";window.PD_BEST_VOUCHER=null;
13799window.IS_DIGITAL=IS_DIGITAL;
13800window.isPreorderProduct=false;
13801window.PD_PREORDER_DATA=null;
13802</script>
13802
13803
13804<script data-inlined="pd-lightbox.js">
13805// PlazaIT Product Lightbox (vanilla, no CDN) — v4 (swipe-down to close)
13806(function(){
13807  const state = {
13808    items: [],
13809    open: false,
13810    idx: 0,
13811    scale: 1,
13812    minScale: 1,
13813    maxScale: 4,
13814    tx: 0, ty: 0,
13815    isDragging: false,
13816    dragStartX: 0, dragStartY: 0,
13817    baseTx: 0, baseTy: 0,
13818    touchStartX: 0, touchStartY: 0,
13819    lastTap: 0,
13820    swipeMode: 'none', // 'none' | 'h' | 'v'
13821    totalDx: 0, totalDy: 0
13822  };
13823
13824  let root, imgEl, counterEl, btnPrev, btnNext, btnClose, canvas;
13825
13826  const ORIGIN = (location.origin || (location.protocol + '//' + location.host));
13827
13828  function absUrl(u){
13829    if (!u) return '';
13830    try {
13831      if (/^https?:///i.test(u)) return u;
13832      return new URL(u, ORIGIN).href;
13833    } catch(_) { return u; }
13834  }
13835  function fileName(u){
13836    try { return new URL(u, ORIGIN).pathname.split('/').pop().toLowerCase(); }
13837    catch(_){ return String(u||'').split('/').pop().toLowerCase(); }
13838  }
13839
13840  function buildItems(){
13841    const gd = (window.galleryData || []).filter(Boolean);
13842    if (gd.length){
13843      state.items = gd.map(g => ({
13844        src: absUrl(g.url), alt: g.alt || '', slug: g.slug || '', w: g.w || 1200, h: g.h || 1200
13845      })).filter(it => !!it.src);
13846      return;
13847    }
13848    const thumbs = document.querySelectorAll('.pd-thumb-btn');
13849    state.items = Array.from(thumbs).map(btn => {
13850      const raw = btn.getAttribute('data-full') || '';
13851      return { src: absUrl(raw), alt: btn.getAttribute('data-alt') || '', slug: btn.getAttribute('data-slug') || '', w: 1200, h: 1200 };
13852    }).filter(it => !!it.src);
13853  }
13854
13855  function ensureUI(){
13856    if (root) return;
13857    root = document.createElement('div');
13858    root.className = 'pd-lb';
13859    root.id = 'pd-lightbox';
13860    root.setAttribute('role','dialog');
13861    root.setAttribute('aria-modal','true');
13862    root.setAttribute('aria-hidden','true');
13863    root.setAttribute('tabindex','-1');
13864
13865    // Canvas terlebih dulu (z-index di CSS membuat tombol di atas)
13866    root.innerHTML = `
13867      <div class="pd-lb-canvas"><img class="pd-lb-img" id="pd-lb-img" alt=""></div>
13868      <button class="pd-lb-close" aria-label="Tutup" title="Tutup">&times;</button>
13869      <button class="pd-lb-prev" aria-label="Sebelumnya" title="Sebelumnya">&#10094;</button>
13870      <button class="pd-lb-next" aria-label="Berikutnya" title="Berikutnya">&#10095;</button>
13871      <div class="pd-lb-counter" id="pd-lb-counter">1 / 1</div>
13872    `;
13873    document.body.appendChild(root);
13874
13875    imgEl     = root.querySelector('#pd-lb-img');
13876    counterEl = root.querySelector('#pd-lb-counter');
13877    btnPrev   = root.querySelector('.pd-lb-prev');
13878    btnNext   = root.querySelector('.pd-lb-next');
13879    btnClose  = root.querySelector('.pd-lb-close');
13880    canvas    = root.querySelector('.pd-lb-canvas');
13881
13882    // Button clicks
13883    btnPrev.addEventListener('click', (e)=>{ e.stopPropagation(); prev(); });
13884    btnNext.addEventListener('click', (e)=>{ e.stopPropagation(); next(); });
13885    btnClose.addEventListener('click', (e)=>{ e.stopPropagation(); close(); });
13886
13887    // Klik backdrop untuk tutup
13888    root.addEventListener('click', (e)=>{ if (e.target === root) close(); });
13889
13890    // Keyboard global
13891    document.addEventListener('keydown', (e)=>{
13892      if (!state.open) return;
13893      if (e.key === 'Escape') { e.preventDefault(); close(); }
13894      if (e.key === 'ArrowRight') { e.preventDefault(); next(); }
13895      if (e.key === 'ArrowLeft')  { e.preventDefault(); prev(); }
13896    });
13897
13898    // Wheel zoom (desktop)
13899    root.addEventListener('wheel', (e)=>{
13900      if (!state.open) return;
13901      e.preventDefault();
13902      const delta = -Math.sign(e.deltaY) * 0.2;
13903      zoomAt(delta, e.clientX, e.clientY);
13904    }, { passive: false });
13905
13906    // Drag pan (mouse)
13907    canvas.addEventListener('mousedown', (e)=>{
13908      if (state.scale <= 1) return;
13909      state.isDragging = true;
13910      state.dragStartX = e.clientX;
13911      state.dragStartY = e.clientY;
13912      state.baseTx = state.tx;
13913      state.baseTy = state.ty;
13914      e.preventDefault();
13915    });
13916    window.addEventListener('mousemove', (e)=>{
13917      if (!state.isDragging) return;
13918      const dx = e.clientX - state.dragStartX;
13919      const dy = e.clientY - state.dragStartY;
13920      setTranslate(state.baseTx + dx, state.baseTy + dy);
13921    });
13922    window.addEventListener('mouseup', ()=>{ state.isDragging = false; });
13923
13924    // Touch: swipe navigate (H), swipe-down close (V), double-tap zoom, drag pan
13925    canvas.addEventListener('touchstart', (e)=>{
13926      if (!state.open) return;
13927      if (e.touches.length === 1) {
13928        const t = e.touches[0];
13929        state.touchStartX = t.clientX;
13930        state.touchStartY = t.clientY;
13931        state.totalDx = 0;
13932        state.totalDy = 0;
13933        state.swipeMode = 'none';
13934
13935        state.isDragging = state.scale > 1;
13936        state.dragStartX = t.clientX;
13937        state.dragStartY = t.clientY;
13938        state.baseTx = state.tx;
13939        state.baseTy = state.ty;
13940
13941        // Double-tap toggle zoom
13942        const now = Date.now();
13943        if (now - state.lastTap < 300) {
13944          if (state.scale > 1) resetTransform(); else zoomAt(+1, t.clientX, t.clientY, 2);
13945          state.lastTap = 0;
13946          e.preventDefault();
13947          return;
13948        }
13949        state.lastTap = now;
13950      }
13951    }, { passive: true });
13952
13953    canvas.addEventListener('touchmove', (e)=>{
13954      if (!state.open) return;
13955
13956      // Drag pan saat zoom
13957      if (state.isDragging && e.touches.length === 1 && state.scale > 1) {
13958        e.preventDefault();
13959        const t = e.touches[0];
13960        const dx = t.clientX - state.dragStartX;
13961        const dy = t.clientY - state.dragStartY;
13962        setTranslate(state.baseTx + dx, state.baseTy + dy);
13963        return;
13964      }
13965
13966      // Gesture saat scale <= 1
13967      if (state.scale <= 1 && e.touches.length === 1) {
13968        const t = e.touches[0];
13969        const dx = t.clientX - state.touchStartX;
13970        const dy = t.clientY - state.touchStartY;
13971        state.totalDx = dx; state.totalDy = dy;
13972
13973        // Tentukan mode swipe di awal gerakan
13974        if (state.swipeMode === 'none') {
13975          const absX = Math.abs(dx), absY = Math.abs(dy);
13976          if (absY > 12 && absY > absX * 1.3) {
13977            state.swipeMode = 'v'; // swipe vertical (untuk close)
13978          } else if (absX > 12 && absX > absY * 1.3) {
13979            state.swipeMode = 'h'; // swipe horizontal (ganti slide)
13980          }
13981        }
13982
13983        if (state.swipeMode === 'v') {
13984          // Ikuti jari, kurangi opacity untuk feedback
13985          e.preventDefault();
13986          setRootDrag(dy);
13987        } else if (state.swipeMode === 'h') {
13988          // Biarkan sampai touchend untuk menentukan next/prev (tidak perlu preventDefault di sini)
13989        }
13990      }
13991    }, { passive: false });
13992
13993    canvas.addEventListener('touchend', (e)=>{
13994      if (!state.open) return;
13995
13996      if (state.scale <= 1) {
13997        const dx = state.totalDx;
13998        const dy = state.totalDy;
13999
14000        if (state.swipeMode === 'v') {
14001          const TH = 80; // threshold jarak piksel untuk menutup
14002          if (Math.abs(dy) > TH) {
14003            // Animasi keluar arah swipe
14004            animateClose(dy);
14005          } else {
14006            // Snap back
14007            resetRootDrag(true);
14008          }
14009        } else if (state.swipeMode === 'h') {
14010          if (Math.abs(dx) > 50) { dx < 0 ? next() : prev(); }
14011        }
14012      }
14013
14014      state.isDragging = false;
14015      state.swipeMode = 'none';
14016      state.totalDx = 0;
14017      state.totalDy = 0;
14018    });
14019  }
14020
14021  function setImage(idx){
14022    if (!state.items.length) return;
14023    state.idx = (idx + state.items.length) % state.items.length;
14024    const it = state.items[state.idx];
14025    imgEl.src = it.src;
14026    imgEl.alt = it.alt || '';
14027    counterEl.textContent = (state.idx + 1) + ' / ' + state.items.length;
14028    resetTransform();
14029    preloadNeighbors();
14030  }
14031
14032  function resetTransform(){
14033    state.scale = 1;
14034    state.tx = 0; state.ty = 0;
14035    applyTransform();
14036  }
14037
14038  function setTranslate(tx, ty){
14039    state.tx = tx; state.ty = ty;
14040    applyTransform();
14041  }
14042
14043  function applyTransform(){
14044    imgEl.style.transform = `translate3d(${state.tx}px, ${state.ty}px, 0) scale(${state.scale})`;
14045  }
14046
14047  function zoomAt(delta, clientX, clientY, snapTo){
14048    const old = state.scale;
14049    let next = (typeof snapTo === 'number') ? snapTo : (old + delta);
14050    next = Math.max(state.minScale, Math.min(state.maxScale, next));
14051
14052    if (next !== old) {
14053      const rect = imgEl.getBoundingClientRect();
14054      const cx = clientX - rect.left - rect.width/2;
14055      const cy = clientY - rect.top  - rect.height/2;
14056      state.tx -= cx * (next/old - 1);
14057      state.ty -= cy * (next/old - 1);
14058      state.scale = next;
14059      applyTransform();
14060    }
14061  }
14062
14063  function preloadNeighbors(){
14064    if (state.items.length < 2) return;
14065    const nextIdx = (state.idx + 1) % state.items.length;
14066    const prevIdx = (state.idx - 1 + state.items.length) % state.items.length;
14067    [nextIdx, prevIdx].forEach(i=>{
14068      const img = new Image(); img.src = state.items[i].src;
14069    });
14070  }
14071
14072  // ----- Swipe-down visual helpers -----
14073  function setRootDrag(dy){
14074    const damped = dy * 0.6; // redam gerakan agar lembut
14075    const abs = Math.abs(dy);
14076    const fade = Math.max(0, Math.min(1, 1 - abs / 300)); // transparansi sesuai jarak
14077    root.style.transform = `translate3d(0, ${damped}px, 0)`;
14078    root.style.opacity = String(0.95 * fade + 0.05); // jangan jadi 0 total sebelum close
14079  }
14080  function resetRootDrag(withAnim){
14081    if (withAnim) {
14082      root.style.transition = 'transform .18s ease, opacity .18s ease';
14083      root.style.transform = 'translate3d(0,0,0)';
14084      root.style.opacity = '1';
14085      setTimeout(()=>{ root.style.transition = ''; }, 190);
14086    } else {
14087      root.style.transition = '';
14088      root.style.transform = '';
14089      root.style.opacity = '';
14090    }
14091  }
14092  function animateClose(dy){
14093    root.style.transition = 'transform .18s ease, opacity .18s ease';
14094    const off = (dy > 0 ? window.innerHeight : -window.innerHeight);
14095    root.style.transform = `translate3d(0, ${off}px, 0)`;
14096    root.style.opacity = '0';
14097    setTimeout(()=>{ root.style.transition = ''; close(); }, 180);
14098  }
14099
14100  function setSiblingsInert(makeInert){
14101    // Inert semua saudara root (elemen lain di body) agar fokus tidak lari
14102    const nodes = Array.from(document.body.children).filter(el => el !== root);
14103    nodes.forEach(el=>{
14104      if (makeInert) {
14105        if (!el.hasAttribute('inert')) el.setAttribute('inert','');
14106      } else {
14107        if (el.hasAttribute('inert')) el.removeAttribute('inert');
14108      }
14109    });
14110  }
14111
14112  function open(idx){
14113    if (!state.items.length) buildItems();
14114    if (!state.items.length) return;
14115    ensureUI();
14116
14117    // Reset efek drag visual
14118    resetRootDrag(false);
14119
14120    document.body.style.overflow = 'hidden';
14121    root.classList.add('show');
14122    root.removeAttribute('aria-hidden'); // hindari aria-hidden saat fokus child
14123    setSiblingsInert(true);
14124
14125    state.open = true;
14126    setImage(typeof idx === 'number' ? idx : 0);
14127
14128    // Fokuskan dialog agar keyboard pasti tertangkap
14129    try { root.focus(); } catch(_){}
14130  }
14131
14132  function close(){
14133    state.open = false;
14134    root.classList.remove('show');
14135    root.setAttribute('aria-hidden','true');
14136    setSiblingsInert(false);
14137    document.body.style.overflow = '';
14138
14139    // Bersihkan transform/opacity
14140    resetRootDrag(false);
14141  }
14142
14143  function next(){ setImage(state.idx + 1); }
14144  function prev(){ setImage(state.idx - 1); }
14145
14146  function findIndexByUrl(url){
14147    const target = fileName(absUrl(url));
14148    const idx = state.items.findIndex(it => fileName(it.src) === target);
14149    return idx >= 0 ? idx : 0;
14150  }
14151
14152  function initPdLightbox(){
14153    buildItems();
14154    ensureUI();
14155
14156    // Klik gambar utama → buka LB di index yang sama
14157    const mainImg = document.getElementById('pd-main-img');
14158    if (mainImg) {
14159      mainImg.style.cursor = 'zoom-in';
14160      mainImg.addEventListener('click', ()=>{
14161        open(findIndexByUrl(mainImg.src));
14162      });
14163    }
14164
14165    // Klik gambar slider mobile → buka LB di gambar yang diketuk
14166    const slider = document.getElementById('pd-mobile-slider');
14167    if (slider) {
14168      slider.addEventListener('click', (e)=>{
14169        const im = e.target && e.target.tagName === 'IMG' ? e.target : e.target.closest('img');
14170        if (!im) return;
14171        open(findIndexByUrl(im.src));
14172      });
14173    }
14174
14175    // (Opsional) double-click pada thumbnail untuk buka langsung
14176    const thumbs = document.querySelectorAll('.pd-thumb-btn');
14177    thumbs.forEach((btn)=>{
14178      let last = 0;
14179      btn.addEventListener('click', ()=>{
14180        const now = Date.now();
14181        if (now - last < 300) {
14182          const src = btn.getAttribute('data-full') || '';
14183          open(findIndexByUrl(absUrl(src)));
14184          last = 0;
14185        } else {
14186          last = now;
14187        }
14188      });
14189    });
14190
14191    // Ekspos API
14192    window.PDLightbox = { open, close, next, prev, findIndexByUrl };
14193  }
14194
14195  if (document.readyState === 'loading') {
14196    document.addEventListener('DOMContentLoaded', initPdLightbox);
14197  } else {
14198    initPdLightbox();
14199  }
14200})();
14201</script>
vendor: 2 bytes, line 14201
14201
14202<script data-inlined="cart.js">
14203(function(){
14204  function $(s,ctx=document){ return ctx.querySelector(s); }
14205  function esc(s){ return String(s||'').replace(/[&<>"']/g, m=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;' }[m])); }
14206
14207  // CSRF token helper
14208  function getCsrfToken() {
14209    const metaTag = document.querySelector('meta[name="csrf-token"]');
14210    return metaTag ? metaTag.getAttribute('content') : '';
14211  }
14212
14213  function guessPdMeta(){
14214    const name = (window.PD_NAME !== undefined) ? String(window.PD_NAME) : ($('#pd-title')?.textContent || '');
14215    const sku  = (function(){ const el=$('#pd-sku'); return el ? el.textContent.trim() : (window.PD_SKU||''); })();
14216    // SECURITY FIX: Remove price from client-side metadata (will be validated server-side)
14217    const imgEl = $('#pd-main-img');
14218    const image_url = imgEl ? (imgEl.getAttribute('src') || '') : '';
14219    return { name, sku, image_url }; // price removed for security
14220  }
14221
14222  async function addToCart(productId, qty=1, variantId=0, metaOpt){
14223    const meta = Object.assign(guessPdMeta(), metaOpt||{});
14224    const payload = {
14225      product_id: parseInt(productId||0,10) || 0,
14226      qty: parseInt(qty||1,10) || 1,
14227      variant_id: parseInt(variantId||0,10) || 0,
14228      meta
14229    };
14230
14231    // Check if user is logged in
14232    const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true' ||
14233                      document.body.classList.contains('logged-in') ||
14234                      window.userLoggedIn === true ||
14235                      document.body.dataset.userLoggedIn === 'true';
14236
14237    // Skip localStorage untuk logged user - mereka pakai database
14238    if (!isLoggedIn && window.guestCart) {
14239      try {
14240        window.guestCart.add(payload.product_id, payload.qty, payload.variant_id, meta);
14241      } catch (e) {
14242        console.warn('Failed to add to localStorage cart:', e);
14243      }
14244    }
14245
14246    const res = await fetch('/api/cart/add.php', {
14247      method: 'POST',
14248      headers: { 
14249        'Content-Type':'application/json',
14250        'X-CSRF-Token': getCsrfToken()
14251      },
14252      credentials: 'same-origin',
14253      body: JSON.stringify({
14254        ...payload,
14255        csrf_token: getCsrfToken()
14256      }),
14257    });
14258    const data = await res.json().catch(()=>({ ok:false, error:'Gagal menambahkan ke keranjang' }));
14259    if (!res.ok || !data.ok) {
14260      const msg = data && data.error ? data.error : 'Gagal menambahkan ke keranjang';
14261      throw new Error(msg);
14262    }
14263
14264    // Update badge di navbar dengan data dari response
14265    try {
14266      const el = document.querySelector('[data-role="cart-badge"]') || document.getElementById('cartCount');
14267      if (el && data.cart && typeof data.cart.items_count === 'number') {
14268        el.textContent = String(data.cart.items_count);
14269        el.classList.toggle('is-empty', data.cart.items_count <= 0);
14270      }
14271    } catch(_){}
14272
14273    // Siarkan event global (dipakai cart-badge.js dan halaman lain)
14274    try {
14275      document.dispatchEvent(new CustomEvent('cart:updated', { detail: data.cart || {} }));
14276    } catch(_){}
14277
14278    // Update CartBadge dengan data dari response (lebih reliable)
14279    try { 
14280      if (window.CartBadge && window.CartBadge.set) {
14281        window.CartBadge.set(data.cart.items_count||0);
14282      }
14283    } catch(_){}
14284
14285    // Tambahan: refresh cart counter setelah delay kecil untuk memastikan data tersinkronisasi
14286    setTimeout(() => {
14287      if (window.updateCartCounter) {
14288        window.updateCartCounter();
14289      }
14290    }, 100);
14291
14292    return data;
14293  }
14294
14295  // Function to update cart counter from server
14296  async function updateCartCounter(){
14297    try {
14298      const res = await fetch('/api/cart/get.php', { credentials:'same-origin' });
14299      const data = await res.json().catch(()=>({ok:false}));
14300      if (data.ok && typeof data.items_count === 'number') {
14301        const el = document.querySelector('[data-role="cart-badge"]') || document.getElementById('cartCount');
14302        if (el) {
14303          el.textContent = String(data.items_count);
14304          el.classList.toggle('is-empty', data.items_count <= 0);
14305        }
14306        // Dispatch event
14307        try {
14308          document.dispatchEvent(new CustomEvent('cart:updated', { detail: { items_count: data.items_count } }));
14309        } catch(_){}
14310      }
14311    } catch (err) {
14312      console.log('Could not update cart counter:', err);
14313    }
14314  }
14315
14316  // Function to manually merge guest cart (if needed)
14317  async function mergeGuestCart(){
14318    try {
14319      const res = await fetch('/api/cart/merge-guest.php', {
14320        method: 'POST',
14321        headers: { 
14322          'Content-Type': 'application/json',
14323          'X-CSRF-Token': getCsrfToken()
14324        },
14325        credentials: 'same-origin',
14326        body: JSON.stringify({ csrf_token: getCsrfToken() })
14327      });
14328      const data = await res.json().catch(()=>({ok:false}));
14329
14330      if (data.ok && data.merged_items > 0) {
14331        console.log(`Guest cart merged: ${data.merged_items} items`);
14332        // Update cart counter after merge
14333        await updateCartCounter();
14334        return data;
14335      }
14336      return data;
14337    } catch (err) {
14338      console.log('Could not merge guest cart:', err);
14339      return { ok: false, error: err.message };
14340    }
14341  }
14342
14343  window.addToCart = addToCart;
14344  window.updateCartCounter = updateCartCounter;
14345  window.mergeGuestCart = mergeGuestCart;
14346
14347  // Optional: auto-bind untuk tombol apapun yang punya data-addtocart
14348  document.addEventListener('click', async (e)=>{
14349    const btn = e.target.closest('[data-addtocart]');
14350    if (!btn) return;
14351    e.preventDefault();
14352    if (btn.disabled) return;
14353
14354    const pid = parseInt(btn.getAttribute('data-pid')||'0',10);
14355    const vid = parseInt(btn.getAttribute('data-vid')||'0',10);
14356    const qty = parseInt(btn.getAttribute('data-qty')||'1',10) || 1;
14357
14358    try {
14359      const data = await addToCart(pid, qty, vid);
14360      if (window.showToast) {
14361        const meta = guessPdMeta();
14362        window.showToast({
14363          type:'success',
14364          title:'Berhasil ditambahkan',
14365          message:`<strong>${esc(meta.name)}</strong>${meta.sku?` · <em>SKU: ${esc(meta.sku)}</em>`:''}<br>Qty: ${qty}`,
14366          duration: 3800,
14367          actions: [
14368            { label:'Lihat Keranjang', onClick: ()=> { window.location.href='/keranjang'; } },
14369            { label:'Lanjut Belanja', variant:'alt', onClick: ()=>{} }
14370          ]
14371        });
14372      }
14373    } catch (err) {
14374      if (window.showToast) {
14375        window.showToast({ type:'warn', title:'Gagal', message: String(err?.message||'Gagal menambahkan'), duration: 4200 });
14376      } else {
14377        console.error(err);
14378      }
14379    }
14380  });
14381})();
14382</script>
vendor: 2 bytes, line 14382
14382
14383<script data-inlined="stock-validator-vanilla.js">
14384/**
14385 * Stock Validation - Vanilla JS Version
14386 * 
14387 * This script provides client-side stock validation without jQuery dependency
14388 * - Works with native JavaScript and fetch API
14389 * - Graceful fallback for older browsers
14390 * - Same functionality as jQuery version
14391 */
14392
14393window.StockValidator = (function() {
14394    'use strict';
14395
14396    const config = {
14397        apiEndpoint: '/api/stock/check.php',
14398        modalSelector: '#stock-modal-overlay',  // We create our own modal
14399        loadingClass: 'loading-stock',
14400        disabledClass: 'stock-disabled',
14401        debug: true  // Enable debug logging
14402    };
14403
14404    /**
14405     * Cross-browser event listener helper
14406     */
14407    function addEventListener(element, event, handler) {
14408        if (element.addEventListener) {
14409            element.addEventListener(event, handler);
14410        } else if (element.attachEvent) {
14411            element.attachEvent('on' + event, handler);
14412        }
14413    }
14414
14415    /**
14416     * Get stock information from embedded variant data (instant, no AJAX needed)
14417     */
14418    function getStockFromEmbeddedData(variantId) {
14419        // Check if embedded variant data exists
14420        if (!window.__PD_VARIANTS_WITH_BRANCH__ || !Array.isArray(window.__PD_VARIANTS_WITH_BRANCH__)) {
14421            return null;
14422        }
14423
14424        const variants = window.__PD_VARIANTS_WITH_BRANCH__;
14425
14426        // SPECIAL CASE: Simple product (no variants) - empty array means simple product
14427        if (variants.length === 0) {
14428            // For simple products, we cannot determine stock from embedded data
14429            // Return null to force API validation
14430            return null;
14431        }
14432
14433        // If no variant specified, check base product stock (all variants)
14434        if (!variantId || variantId <= 0) {
14435            // For base product, check if ANY variant has stock
14436            const isPreorder = window.isPreorderProduct || false;
14437            let hasAnyStock = false;
14438            let totalStock = 0;
14439
14440            if (isPreorder) {
14441                // For pre-order, check preorder_remaining
14442                hasAnyStock = variants.some(v => {
14443                    if ('preorder_remaining' in v && v.preorder_remaining !== null) {
14444                        return v.preorder_remaining > 0;
14445                    }
14446                    return v.stock > 0;
14447                });
14448                totalStock = variants.reduce((sum, v) => {
14449                    if ('preorder_remaining' in v && v.preorder_remaining !== null) {
14450                        return sum + (v.preorder_remaining || 0);
14451                    }
14452                    return sum + (v.stock || 0);
14453                }, 0);
14454            } else {
14455                hasAnyStock = variants.some(v => v.stock > 0);
14456                totalStock = variants.reduce((sum, v) => sum + (v.stock || 0), 0);
14457            }
14458
14459            return {
14460                hasStock: hasAnyStock,
14461                stock: totalStock,
14462                sku: 'Base Product',
14463                variant: null
14464            };
14465        }
14466
14467        // Find specific variant
14468        const variant = variants.find(v => v.id === variantId);
14469
14470        if (!variant) {
14471            return {
14472                hasStock: false,
14473                stock: 0,
14474                sku: 'Unknown Variant',
14475                variant: null
14476            };
14477        }
14478
14479        // For pre-order products, check preorder_remaining instead of stock
14480        const isPreorder = window.isPreorderProduct || false;
14481        let hasStock = false;
14482        let availableStock = 0;
14483
14484        if (isPreorder && 'preorder_remaining' in variant && variant.preorder_remaining !== null) {
14485            availableStock = parseInt(variant.preorder_remaining, 10);
14486            if (isNaN(availableStock)) availableStock = 0;
14487            hasStock = availableStock > 0;
14488        } else {
14489            availableStock = variant.stock || 0;
14490            hasStock = availableStock > 0;
14491        }
14492
14493        return {
14494            hasStock: hasStock,
14495            stock: availableStock,
14496            sku: variant.sku,
14497            variant: variant
14498        };
14499    }
14500
14501    /**
14502     * Cross-browser AJAX helper
14503     */
14504    function makeRequest(url, options) {
14505        return new Promise((resolve, reject) => {
14506            // Try fetch first (modern browsers)
14507            if (typeof fetch !== 'undefined') {
14508                const fetchOptions = {
14509                    method: options.method || 'POST',
14510                    headers: {
14511                        'Content-Type': 'application/x-www-form-urlencoded',
14512                    },
14513                    body: options.data ? new URLSearchParams(options.data).toString() : null
14514                };
14515
14516                fetch(url, fetchOptions)
14517                    .then(response => {
14518                        if (!response.ok) {
14519                            throw new Error(`HTTP ${response.status}`);
14520                        }
14521                        return response.json();
14522                    })
14523                    .then(resolve)
14524                    .catch(reject);
14525            } else {
14526                // Fallback to XMLHttpRequest
14527                const xhr = new XMLHttpRequest();
14528                xhr.open(options.method || 'POST', url);
14529                xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
14530
14531                xhr.onload = function() {
14532                    if (xhr.status >= 200 && xhr.status < 300) {
14533                        try {
14534                            resolve(JSON.parse(xhr.responseText));
14535                        } catch (e) {
14536                            reject(new Error('Invalid JSON response'));
14537                        }
14538                    } else {
14539                        reject(new Error(`HTTP ${xhr.status}`));
14540                    }
14541                };
14542
14543                xhr.onerror = function() {
14544                    reject(new Error('Network error'));
14545                };
14546
14547                const data = options.data ? new URLSearchParams(options.data).toString() : null;
14548                xhr.send(data);
14549            }
14550        });
14551    }
14552
14553    /**
14554     * Check stock via AJAX
14555     */
14556    function checkStock(productId, variantId, qty) {
14557        return makeRequest(config.apiEndpoint, {
14558            method: 'POST',
14559            data: {
14560                product_id: productId,
14561                variant_id: variantId || 0,
14562                qty: qty || 1
14563            }
14564        });
14565    }
14566
14567    /**
14568     * Create and show stock alert modal using modal.css structure
14569     */
14570    function showStockModal(stockInfo, action = 'add to cart') {
14571        let title, message, type, iconClass;
14572
14573        // Handle both string and object formats
14574        if (typeof stockInfo === 'string') {
14575            title = 'Stok Tidak Tersedia';
14576            message = stockInfo;
14577            type = 'warning';
14578            iconClass = 'warning';
14579        } else if (stockInfo && typeof stockInfo === 'object') {
14580            if (!stockInfo.is_active) {
14581                title = 'Produk Tidak Tersedia';
14582                message = 'Maaf, produk ini sudah tidak aktif atau telah dihapus dari katalog.';
14583                type = 'danger';
14584                iconClass = 'danger';
14585            } else if (!stockInfo.is_in_stock) {
14586                title = 'Stok Habis';
14587                message = `<strong>${stockInfo.product_name || 'Produk ini'}</strong> sedang habis stok. Silakan pilih varian lain atau tunggu restock.`;
14588                if (stockInfo.sku) {
14589                    message += `<br><small style="color: #64748b;">SKU: ${stockInfo.sku}</small>`;
14590                }
14591                type = 'warning';
14592                iconClass = 'warning';
14593            } else if (!stockInfo.can_add_to_cart) {
14594                title = 'Stok Tidak Mencukupi';
14595                message = `Stok <strong>${stockInfo.product_name || 'produk'}</strong> tidak mencukupi.<br>`;
14596                message += `Tersedia: <strong>${stockInfo.available_stock}</strong>, diminta: <strong>${stockInfo.requested_qty}</strong><br>`;
14597                message += `Silakan kurangi jumlah atau pilih varian lain.`;
14598                type = 'warning';
14599                iconClass = 'warning';
14600            } else {
14601                title = 'Stok Tidak Tersedia';
14602                message = stockInfo.message || 'Produk ini tidak dapat ditambahkan ke keranjang saat ini.';
14603                type = 'warning';
14604                iconClass = 'warning';
14605            }
14606        } else {
14607            title = 'Stok Tidak Tersedia';
14608            message = 'Produk ini tidak dapat ditambahkan ke keranjang saat ini.';
14609            type = 'warning';
14610            iconClass = 'warning';
14611        }
14612
14613        // Create modal HTML using modal.css structure
14614        const modalHTML = `
14615            <div class="modal-overlay" id="stock-modal-overlay">
14616                <div class="modal-dialog">
14617                    <div class="modal-header">
14618                        <div class="modal-icon ${iconClass}">
14619                            ${getIconSVG(iconClass)}
14620                        </div>
14621                        <h3 class="modal-title">${title}</h3>
14622                    </div>
14623                    <div class="modal-body">
14624                        <p class="modal-message">${message}</p>
14625                        <div class="modal-actions">
14626                            <button type="button" class="modal-btn modal-btn-secondary" data-action="close">
14627                                Tutup
14628                            </button>
14629                            <button type="button" class="modal-btn modal-btn-primary" data-action="reload">
14630                                Muat Ulang
14631                            </button>
14632                        </div>
14633                    </div>
14634                </div>
14635            </div>
14636        `;
14637
14638        // Remove existing modal if any
14639        const existingModal = document.getElementById('stock-modal-overlay');
14640        if (existingModal) {
14641            existingModal.remove();
14642        }
14643
14644        // Add modal to body
14645        document.body.insertAdjacentHTML('beforeend', modalHTML);
14646
14647        // Show modal with animation
14648        const modal = document.getElementById('stock-modal-overlay');
14649        setTimeout(() => {
14650            modal.classList.add('show');
14651        }, 10);
14652
14653        // Add event listeners to modal buttons
14654        const closeBtn = modal.querySelector('[data-action="close"]');
14655        const reloadBtn = modal.querySelector('[data-action="reload"]');
14656
14657        if (closeBtn) {
14658            addEventListener(closeBtn, 'click', closeStockModal);
14659        }
14660
14661        if (reloadBtn) {
14662            addEventListener(reloadBtn, 'click', function() {
14663                closeStockModal();
14664                window.location.reload();
14665            });
14666        }
14667
14668        // Close modal when clicking overlay
14669        addEventListener(modal, 'click', function(event) {
14670            if (event.target === modal) {
14671                closeStockModal();
14672            }
14673        });
14674
14675        // Auto-close after 8 seconds
14676        setTimeout(() => {
14677            closeStockModal();
14678        }, 8000);
14679    }
14680
14681    /**
14682     * Get icon SVG based on type
14683     */
14684    function getIconSVG(type) {
14685        const icons = {
14686            danger: '<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>',
14687            warning: '<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
14687<path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>',
14688            success: '<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>',
14689            info: '<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>'
14690        };
14691        return icons[type] || icons.info;
14692    }
14693
14694    /**
14695     * Close stock modal
14696     */
14697    function closeStockModal() {
14698        const modal = document.getElementById('stock-modal-overlay');
14699        if (modal) {
14700            modal.classList.remove('show');
14701            setTimeout(() => {
14702                modal.remove();
14703            }, 200);
14704        }
14705    }
14706
14707    // Make closeStockModal globally available
14708    if (typeof window !== 'undefined') {
14709        window.closeStockModal = closeStockModal;
14710    }
14711
14712    /**
14713     * Get element data attribute with fallback
14714     */
14715    function getDataAttribute(element, attr) {
14716        if (element.dataset) {
14717            return element.dataset[attr];
14718        } else {
14719            return element.getAttribute('data-' + attr);
14720        }
14721    }
14722
14723    /**
14724     * Validate stock before cart action
14725     */
14726    async function validateBeforeCart(productId, variantId, qty, action = 'add') {
14727        try {
14728            // SMART VALIDATION: For simple products, check if we already have cached stock info
14729            // If buttons are enabled and cached info shows stock available, skip API validation
14730            if (window._currentStockInfo && window._currentStockInfo.can_add_to_cart) {
14731
14732                return true;
14733            }
14734
14735            // For variant products or when cached info is not available, do API validation
14736            const stockInfo = await checkStock(productId, variantId, qty);
14737
14738            if (!stockInfo.ok) {
14739                showStockModal({ 
14740                    message: stockInfo.error || 'Gagal mengecek stok',
14741                    is_active: false 
14742                }, action);
14743                return false;
14744            }
14745
14746            if (!stockInfo.can_add_to_cart) {
14747                showStockModal(stockInfo, action);
14748                return false;
14749            }
14750
14751            return true; // Stock available, proceed
14752
14753        } catch (error) {
14754            console.error('Stock validation error:', error);
14755            showStockModal({ 
14756                message: 'Terjadi kesalahan saat mengecek stok. Silakan coba lagi.',
14757                is_active: false 
14758            }, action);
14759            return false;
14760        }
14761    }
14762
14763    /**
14764     * Handle button click with stock validation
14765     */
14766    function handleButtonClick(event, action) {
14767        const button = event.target;
14768
14769
14770
14771        // FIRST CHECK: Is button marked as stock-blocked? (Anti-tampering)
14772        if (button.hasAttribute('data-stock-blocked')) {
14773            event.preventDefault();
14774            event.stopImmediatePropagation();
14775
14776            const reason = button.getAttribute('data-stock-reason') || 'Stok tidak tersedia';
14777            showStockModal(reason);
14778
14779            // Show security warning
14780            if (window.showToast) {
14781                window.showToast({
14782                    type: 'warning',
14783                    title: 'Aksi Tidak Diizinkan',
14784                    message: 'Produk ini sedang habis stok. Silakan pilih varian lain.',
14785                    duration: 3000
14786                });
14787            }
14788            return false;
14789        }
14790
14791        // SECOND CHECK: Real-time stock validation against cached stock info
14792        if (window._currentStockInfo && !window._currentStockInfo.can_add_to_cart) {
14793            event.preventDefault();
14794            event.stopImmediatePropagation();
14795
14796            showStockModal(window._currentStockInfo.message || 'Stok tidak tersedia');
14797
14798            // Re-enforce button disabling
14799            button.disabled = true;
14800            button.style.opacity = '0.5';
14801            button.style.cursor = 'not-allowed';
14802            button.setAttribute('data-stock-blocked', 'true');
14803
14804            return false;
14805        }
14806
14807        // Skip if already processing
14808        if (button.classList.contains(config.loadingClass)) {
14809            event.preventDefault();
14810            return false;
14811        }
14812
14813        // Get product data from button attributes or form
14814        let productId = parseInt(getDataAttribute(button, 'productId') || getDataAttribute(button, 'product-id') || 0);
14815        let variantId = parseInt(getDataAttribute(button, 'variantId') || getDataAttribute(button, 'variant-id') || 0);
14816        let qty = parseInt(getDataAttribute(button, 'qty') || 1);
14817
14818        // Try to get from global variables (set by product detail page)
14819        if (productId <= 0 && typeof window.PD_ID !== 'undefined') {
14820            productId = parseInt(window.PD_ID || 0);
14821        }
14822
14823        // Try to get from parent form if not in button
14824        if (productId <= 0) {
14825            const form = button.closest ? button.closest('form') : null;
14826            if (form) {
14827                const pidAttr = form.getAttribute('data-pid');
14828                const pidInput = form.querySelector('[name="product_id"]');
14829                const vidInput = form.querySelector('[name="variant_id"]');
14830                const qtyInput = form.querySelector('[name="qty"]');
14831
14832                if (pidAttr) productId = parseInt(pidAttr);
14833                if (pidInput) productId = parseInt(pidInput.value || 0);
14834                if (vidInput) variantId = parseInt(vidInput.value || 0);
14835                if (qtyInput) qty = parseInt(qtyInput.value || 1);
14836            }
14837        }
14838
14839        // CRITICAL: Always get current variant from selector (overrides button attributes)
14840        // This ensures we validate against the currently selected variant, not cached button data
14841        const variantSelector = document.getElementById('pd-variant-id');
14842        if (variantSelector) {
14843            const currentVariantId = parseInt(variantSelector.value || 0);
14844            if (currentVariantId > 0) {
14845                variantId = currentVariantId;
14846            }
14847        }
14848
14849
14850
14851        if (productId <= 0) {
14852            return true; // Let default handler deal with it
14853        }
14854
14855        // Prevent default and validate stock
14856        event.preventDefault();
14857        event.stopPropagation();
14858
14859        // Show loading state
14860        button.classList.add(config.loadingClass);
14861        const originalDisabled = button.disabled;
14862        button.disabled = true;
14863
14864
14865
14866        validateBeforeCart(productId, variantId, qty, action)
14867            .then(isValid => {
14868                if (isValid) {
14869                    // Stock OK, proceed with original action
14870                    if (action === 'cart' && typeof window.addToCart === 'function') {
14871                        window.addToCart(productId, qty, variantId);
14872                    } else if (action === 'buy' && typeof window.buyNow === 'function') {
14873                        // Call buyNow from pd-buynow.js without parameters (it gets data internally)
14874                        window.buyNow();
14875                    } else {
14876                        // Add fallback cart/buy logic here if needed
14877                    }
14878                }
14879            })
14880            .catch(error => {
14881            })
14882            .finally(() => {
14883                // Remove loading state
14884                button.classList.remove(config.loadingClass);
14885                button.disabled = originalDisabled;
14886            });
14887
14888        return false;
14889    }
14890
14891    /**
14892     * Handle mobile button click with stock validation
14893     */
14894    function handleMobileButtonClick(event, action) {
14895        const button = event.target;
14896
14897
14898
14899        // FIRST CHECK: Is button marked as stock-blocked? (Anti-tampering)
14900        if (button.hasAttribute('data-stock-blocked')) {
14901            event.preventDefault();
14902            event.stopImmediatePropagation();
14903
14904            const reason = button.getAttribute('data-stock-reason') || 'Stok tidak tersedia';
14905            showStockModal(reason);
14906
14907            // Show security warning
14908            if (window.showToast) {
14909                window.showToast({
14910                    type: 'warning',
14911                    title: 'Aksi Tidak Diizinkan',
14912                    message: 'Produk ini sedang habis stok. Silakan pilih varian lain.',
14913                    duration: 3000
14914                });
14915            }
14916            return false;
14917        }
14918
14919        // SECOND CHECK: Real-time stock validation against cached stock info
14920        if (window._currentStockInfo && !window._currentStockInfo.can_add_to_cart) {
14921            event.preventDefault();
14922            event.stopImmediatePropagation();
14923
14924            showStockModal(window._currentStockInfo.message || 'Stok tidak tersedia');
14925
14926            // Re-enforce button disabling
14927            button.disabled = true;
14928            button.style.opacity = '0.5';
14929            button.style.cursor = 'not-allowed';
14930            button.setAttribute('data-stock-blocked', 'true');
14931
14932            return false;
14933        }
14934
14935        // Skip if already processing
14936        if (button.classList.contains(config.loadingClass)) {
14937            event.preventDefault();
14938            return false;
14939        }
14940
14941        // Get product data from mobile FAB container
14942        let productId = 0;
14943        let variantId = 0;
14944        let qty = 1;
14945
14946        // Try to get from mobile FAB data-pid
14947        const mobileFab = document.getElementById('pd-mobile-fab');
14948        if (mobileFab) {
14949            const pidAttr = getDataAttribute(mobileFab, 'pid');
14950            if (pidAttr) productId = parseInt(pidAttr);
14951        }
14952
14953        // Try to get from global variables (set by product detail page)
14954        if (productId <= 0 && typeof window.PD_ID !== 'undefined') {
14955            productId = parseInt(window.PD_ID || 0);
14956        }
14957
14958        // CRITICAL: Always get current variant from selector (real-time)
14959        // This ensures we validate against the currently selected variant
14960        const variantSelector = document.getElementById('pd-variant-id');
14961        if (variantSelector) {
14962            variantId = parseInt(variantSelector.value || 0);
14963        }
14964
14965        // Try to get quantity from quantity input
14966        const qtySelector = document.querySelector('[name="qty"]');
14967        if (qtySelector) {
14968            qty = parseInt(qtySelector.value || 1);
14969        }
14970
14971        // Ensure minimum quantity is 1
14972        qty = Math.max(1, qty);
14973
14974
14975
14976        if (productId <= 0) {
14977            return true; // Let default handler deal with it
14978        }
14979
14980        // Event already prevented at interceptor level, just proceed with validation
14981
14982        // Show loading state
14983        button.classList.add(config.loadingClass);
14984        const originalDisabled = button.disabled;
14985        const originalText = button.textContent;
14986        button.disabled = true;
14987        button.textContent = action === 'cart' ? 'Validasi...' : 'Validasi...';
14988
14989
14990
14991        // PREEMPTIVE BLOCKING: Set blocking flags BEFORE validation to prevent race conditions
14992        if (window.addToCart && typeof window.addToCart === 'function') {
14993            window.addToCart._stockValidatorBlocked = true;
14994        }
14995
14996        // Also preemptively block other cart functions
14997        const originalAddToCartSubmit = window.addToCartSubmit;
14998        const originalPdAddToCartSubmit = window._pdAddToCartSubmit;
14999
15000        // Temporarily block ALL cart functions during validation
15001        window.addToCartSubmit = (...args) => {
15002            return Promise.resolve(false);
15003        };
15004
15005        if (originalPdAddToCartSubmit) {
15006            window._pdAddToCartSubmit = (...args) => {
15007                return Promise.resolve(false);
15008            };
15009        }
15010
15011        validateBeforeCart(productId, variantId, qty, action)
15012            .then(isValid => {
15013                if (isValid) {
15014                    // Stock OK, proceed with original action
15015
15016                    if (action === 'cart') {
15017                        // For mobile cart, we need to recreate the original behavior
15018                        // Strategy: Try multiple methods to ensure cart add + toast success
15019
15020
15021
15022                        let cartAddSuccess = false;
15023                        let errorMessage = '';
15024
15025                        // Method 1: Try original addToCart function
15026                        const originalAddToCart = window.addToCart && window.addToCart._original;
15027                        if (originalAddToCart && !cartAddSuccess) {
15028                            try {
15029                                const result = originalAddToCart(productId, qty, variantId);
15030
15031                                // Handle both sync and async returns
15032                                if (result && typeof result.then === 'function') {
15033                                    result.then(() => {
15034                                        cartAddSuccess = true;
15035                                    }).catch(err => {
15036                                        errorMessage = err.message || 'addToCart failed';
15037                                    });
15038                                } else {
15039                                    cartAddSuccess = true;
15040                                }
15041                            } catch (error) {
15042                                errorMessage = error.message || 'addToCart exception';
15043                            }
15044                        }
15045
15046                        // Method 2: Try pd-addtocart.js function (backup method)
15047                        if (typeof window._pdAddToCartSubmit === 'function' && !cartAddSuccess) {
15048                            try {
15049
15050                                // Create fake form for pd-addtocart.js
15051                                const fakeForm = document.createElement('form');
15052                                const qtyInput = document.createElement('input');
15053                                qtyInput.name = 'qty';
15054                                qtyInput.value = qty;
15055                                fakeForm.appendChild(qtyInput);
15056
15057                                // Set variant selector value for pd-addtocart to read
15058                                const variantSelector = document.getElementById('pd-variant-id');
15059                                if (variantSelector) {
15060                                    variantSelector.value = variantId;
15061                                }
15062
15063                                const fakeEvent = { 
15064                                    preventDefault: () => {},
15065                                    target: fakeForm
15066                                };
15067
15068                                // Call pd-addtocart.js function (this should handle toast internally)
15069                                const result = window._pdAddToCartSubmit(fakeEvent, productId);
15070                                if (result && typeof result.then === 'function') {
15071                                    result.then(() => {
15072                                        cartAddSuccess = true;
15073                                    });
15074                                } else {
15075                                    cartAddSuccess = true;
15076                                }
15077                            } catch (error) {
15078                            }
15079                        }
15080
15081                        // Method 3: Direct addToCart call (fallback)
15082                        if (typeof window.addToCart === 'function' && !cartAddSuccess) {
15083                            try {
15084                                const result = window.addToCart(productId, qty, variantId);
15085                                if (result && typeof result.then === 'function') {
15086                                    result.then(() => {
15087                                        cartAddSuccess = true;
15088                                    });
15089                                } else {
15090                                    cartAddSuccess = true;
15091                                }
15092                            } catch (error) {
15093                            }
15094                        }
15095
15096                        // Always show success toast for mobile (regardless of which method worked)
15097                        // This ensures user gets feedback for successful cart addition
15098                        setTimeout(() => {
15099                            if (window.showToast) {
15100
15101                                // Get product details for toast
15102                                const getVariantSummaryText = () => {
15103                                    const vs = document.getElementById('pd-variant-summary');
15104                                    if (!vs) return '';
15105                                    const items = Array.from(vs.querySelectorAll('.vs-item strong'))
15106                                        .map(el => el.textContent.trim())
15107                                        .filter(Boolean);
15108                                    return items.length ? items.join(' / ') : '';
15109                                };
15110
15111                                const escapeHtml = (s) => String(s||'').replace(/[&<>"']/g, m=>({
15112                                    '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'
15113                                }[m]));
15114
15115                                const variantText = getVariantSummaryText();
15116                                const skuText = (function(){ 
15117                                    const el = document.getElementById('pd-sku'); 
15118                                    return el ? el.textContent.trim() : (window.PD_SKU || 'N/A'); 
15119                                })();
15120                                const productName = window.PD_NAME || 'Product';
15121
15122                                window.showToast({
15123                                    type: 'success',
15124                                    title: 'Berhasil ditambahkan ke Keranjang',
15125                                    message: `<strong>${escapeHtml(productName)}</strong>${variantText ? ` · <em>${escapeHtml(variantText)}</em>` : ''}<br>SKU: ${escapeHtml(skuText)}
15125 · Qty: ${qty}`,
15126                                    duration: 5000,
15127                                    actions: [
15128                                        { 
15129                                            label: 'Lihat Keranjang', 
15130                                            onClick: () => { window.location.href = '/keranjang'; }
15131                                        },
15132                                        { 
15133                                            label: 'Lanjut Belanja', 
15134                                            variant: 'alt', 
15135                                            onClick: () => {} 
15136                                        }
15137                                    ]
15138                                });
15139                            } else {
15140                            }
15141                        }, 100); // Small delay to ensure cart operation completes first
15142
15143                    } else if (action === 'buy') {
15144                        // For mobile buy, call buyNow
15145                        if (typeof window.buyNow === 'function') {
15146                            window.buyNow();
15147                        }
15148                    }
15149                } else {
15150                    // Stock validation FAILED - absolutely NO cart function calls allowed
15151
15152                    // DO NOT CALL ANY CART FUNCTIONS - only show modal (already handled by validateBeforeCart)
15153                }
15154
15155                // Always restore functions after validation (success or failure)
15156                setTimeout(() => {
15157                    // Remove blocking flag
15158                    if (window.addToCart && typeof window.addToCart === 'function') {
15159                        window.addToCart._stockValidatorBlocked = false;
15160                    }
15161
15162                    // Restore other functions
15163                    if (originalAddToCartSubmit && typeof originalAddToCartSubmit === 'function') {
15164                        window.addToCartSubmit = originalAddToCartSubmit;
15165                    }
15166                    if (originalPdAddToCartSubmit && typeof originalPdAddToCartSubmit === 'function') {
15167                        window._pdAddToCartSubmit = originalPdAddToCartSubmit;
15168                    }
15169
15170                }, 1000); // Shorter timeout since we want to restore quickly after success
15171            })
15172            .catch(error => {
15173                // On error, also block the action
15174            })
15175            .finally(() => {
15176                // Remove loading state
15177                button.classList.remove(config.loadingClass);
15178                button.disabled = originalDisabled;
15179                button.textContent = originalText;
15180            });
15181
15182        return false;
15183    }
15184
15185    /**
15186     * Intercept cart buttons
15187     */
15188    function interceptCartButtons() {
15189
15190
15191
15192        // CRITICAL: Remove existing mobile cart handlers from product-detail.php
15193        setTimeout(() => {
15194            const mobileCartBtn = document.getElementById('mf-cart');
15195            if (mobileCartBtn) {
15196                // Clone the button to remove ALL existing event listeners
15197                const newBtn = mobileCartBtn.cloneNode(true);
15198                mobileCartBtn.parentNode.replaceChild(newBtn, mobileCartBtn);
15199
15200
15201
15202                // Add our own handler to the new button
15203                newBtn.addEventListener('click', function(event) {
15204                    event.preventDefault();
15205                    event.stopPropagation();
15206                    event.stopImmediatePropagation();
15207                    handleMobileButtonClick(event, 'cart');
15208                    return false;
15209                }, true);
15210            }
15211        }, 50);
15212
15213        // Note: Add to cart buttons are now handled via form submission override (addToCartSubmit)
15214        // This provides better integration with existing pd-addtocart.js toast system
15215
15216        // Buy now buttons - updated selectors (desktop + mobile)
15217        addEventListener(document, 'click', function(event) {
15218            const target = event.target;
15219
15220            // Desktop buy buttons
15221            if (target.classList.contains('btn-buy') ||           // Product detail buy button
15222                target.classList.contains('btn-buy-now') || 
15223                target.classList.contains('buy-now-btn') || 
15224                target.id === 'btn-buy' ||                        // Specific ID match
15225                getDataAttribute(target, 'action') === 'buy-now' ||
15226                getDataAttribute(target, 'action') === 'buy') {
15227
15228                handleButtonClick(event, 'buy');
15229            }
15230
15231            // Mobile buy buttons
15232            else if (target.classList.contains('mf-buy') ||       // Mobile fab buy button
15233                     target.id === 'mf-buy') {                   // Mobile buy button ID
15234
15235                handleMobileButtonClick(event, 'buy');
15236            }
15237
15238            // Mobile cart buttons - ENHANCED BLOCKING
15239            else if (target.classList.contains('mf-cart') ||      // Mobile fab cart button
15240                     target.id === 'mf-cart') {                  // Mobile cart button ID
15241
15242
15243
15244                // Immediately prevent all event propagation
15245                event.preventDefault();
15246                event.stopPropagation();
15247                event.stopImmediatePropagation();
15248
15249                handleMobileButtonClick(event, 'cart');
15250
15251                // Return false to ensure no other handlers run
15252                return false;
15253            }
15254        }, true);
15254 // Use capturing phase to ensure we intercept before other handlers
15255
15256        // Additional protection: Override addToCart function to add mobile button state checking
15257        const originalAddToCart = typeof window.addToCart === 'function' ? window.addToCart : null;
15258        if (originalAddToCart) {
15259            window.addToCart = async function(...args) {
15260                // Check if this call is coming from a mobile button that's currently being validated
15261                const mobileCartBtn = document.getElementById('mf-cart');
15262                if (mobileCartBtn && mobileCartBtn.classList.contains('loading-stock')) {
15263                    return Promise.reject(new Error('Mobile validation in progress - cart action blocked'));
15264                }
15265
15266                // Check if functions are temporarily blocked (during stock failure)
15267                if (window.addToCart._stockValidatorBlocked) {
15268                    return Promise.reject(new Error('Stock validation failed - cart action blocked'));
15269                }
15270
15271                // Allow normal calls
15272                return originalAddToCart.apply(this, args);
15273            };
15274
15275            // Store reference to original for restoration
15276            window.addToCart._original = originalAddToCart;
15277        }
15278    }
15279
15280    /**
15281     * Setup global click interceptor for security
15282     * This catches ALL clicks on cart/buy buttons and validates stock BEFORE any other handler
15283     */
15284    function setupGlobalClickInterceptor() {
15285        // Add capture-phase listener to catch clicks before other handlers
15286        document.addEventListener('click', function(event) {
15287            const target = event.target;
15288
15289            // Check if this is a cart/buy button
15290            const isCartButton = target.matches('.btn-add-cart, .mf-cart, #mf-cart, #btn-add-cart') ||
15291                                target.closest('.btn-add-cart, .mf-cart, #mf-cart, #btn-add-cart');
15292            const isBuyButton = target.matches('.btn-buy-now, .mf-buy, #mf-buy, #btn-buy') ||
15293                               target.closest('.btn-buy-now, .mf-buy, #mf-buy, #btn-buy');
15294
15295            if (!isCartButton && !isBuyButton) {
15296                return; // Not our target, allow normal processing
15297            }
15298
15299            const button = isCartButton ? 
15300                (target.matches('.btn-add-cart, .mf-cart, #mf-cart, #btn-add-cart') ? target : target.closest('.btn-add-cart, .mf-cart, #mf-cart, #btn-add-cart')) :
15301                (target.matches('.btn-buy-now, .mf-buy, #mf-buy, #btn-buy') ? target : target.closest('.btn-buy-now, .mf-buy, #mf-buy, #btn-buy'));
15302
15303            // IMMEDIATE BLOCK: Prevent any processing first
15304            event.preventDefault();
15305            event.stopImmediatePropagation();
15306
15307            // SECURITY CHECK 1: Is button marked as stock-blocked?
15308            if (button && button.hasAttribute('data-stock-blocked')) {
15309
15310                const reason = button.getAttribute('data-stock-reason') || 'Stok tidak tersedia';
15311                showStockModal(reason);
15312
15313                // Show security alert
15314                if (window.showToast) {
15315                    window.showToast({
15316                        type: 'error',
15317                        title: 'Aksi Diblokir',
15318                        message: 'Produk ini sedang habis stok dan tidak dapat ditambahkan ke keranjang.',
15319                        duration: 4000
15320                    });
15321                }
15322
15323                return false;
15324            }
15325
15326            // SECURITY CHECK 2: Real-time validation against cached stock info
15327            if (window._currentStockInfo && !window._currentStockInfo.can_add_to_cart) {
15328
15329                showStockModal(window._currentStockInfo.message || 'Stok tidak tersedia');
15330
15331                // Re-enforce button disabling
15332                if (button) {
15333                    button.disabled = true;
15334                    button.style.opacity = '0.5';
15335                    button.style.cursor = 'not-allowed';
15336                    button.setAttribute('data-stock-blocked', 'true');
15337                    button.setAttribute('data-stock-reason', window._currentStockInfo.message || 'Stok tidak tersedia');
15338                }
15339
15340                return false;
15341            }
15342
15343            // SECURITY CHECK 3: Smart client-side validation
15344            const productId = window.PD_ID ? parseInt(window.PD_ID) : 0;
15345            const variantSelector = document.getElementById('pd-variant-id');
15346            const variantId = variantSelector ? parseInt(variantSelector.value || 0
15346) : 0;
15347
15348            if (productId > 0) {
15349                // SMART VALIDATION: If we already have cached stock info showing stock is available,
15350                // and buttons are enabled, proceed immediately without re-validation
15351                if (window._currentStockInfo && window._currentStockInfo.can_add_to_cart && 
15352                    button && !button.disabled && !button.hasAttribute('data-stock-blocked')) {
15353
15354                    // Allow the action to proceed immediately with toast
15355                    if (isCartButton) {
15356                        // Trigger cart action with proper toast handling
15357                        if (typeof window._pdAddToCartSubmit === 'function') {
15358                            // Use pd-addtocart.js function for proper toast handling
15359                            const form = document.getElementById('pd-buy-form') || button.closest('form');
15360                            if (form) {
15361                                const fakeEvent = { 
15362                                    target: form,
15363                                    preventDefault: () => {}
15364                                };
15365                                window._pdAddToCartSubmit(fakeEvent, productId);
15366                            }
15367                        } else if (typeof window.addToCartSubmit === 'function') {
15368                            // Fallback to addToCartSubmit
15369                            const form = document.getElementById('pd-buy-form') || button.closest('form');
15370                            if (form) {
15371                                const fakeEvent = { 
15372                                    target: form,
15373                                    preventDefault: () => {}
15374                                };
15375                                window.addToCartSubmit(fakeEvent, productId);
15376                            }
15377                        } else if (typeof window.addToCart === 'function') {
15378                            // Direct addToCart call
15379                            window.addToCart(productId, 1, variantId);
15380                        }
15381                    } else if (isBuyButton) {
15382                        // Trigger buy action
15383                        if (typeof window.buyNow === 'function') {
15384                            window.buyNow();
15385                        }
15386                    }
15387
15388                    return false; // Action completed
15389                }
15390
15391                // Get stock info from embedded variant data (for variant products)
15392                const stockInfo = getStockFromEmbeddedData(variantId);
15393
15394                if (stockInfo && stockInfo.hasStock) {
15395
15396                    // Update global stock cache
15397                    window._currentStockInfo = {
15398                        can_add_to_cart: true,
15399                        is_in_stock: true,
15400                        available_stock: stockInfo.stock,
15401                        message: 'Stok tersedia'
15402                    };
15403
15404                    // IMPORTANT: Check if button was explicitly blocked by variant logic (e.g., pre-order exhausted)
15405                    // If data-stock-blocked is set, respect it and don't enable the button
15406                    const isExplicitlyBlocked = button.getAttribute('data-stock-blocked') === 'true';
15407
15408                    if (!isExplicitlyBlocked) {
15409                        // Remove stock blocking if it was set and enable button
15410                        button.removeAttribute('data-stock-blocked');
15411                        button.removeAttribute('data-stock-reason');
15412                        button.disabled = false;
15413                        button.style.opacity = '';
15414                        button.style.cursor = '';
15415                        button.classList.remove(config.disabledClass);
15416                    } else {
15417                        // Button is blocked by variant logic, show reason
15418                        const reason = button.getAttribute('data-stock-reason') || 'Tidak tersedia';
15419                        console.log('Button blocked by variant logic:', reason);
15420                        e.preventDefault();
15421                        e.stopPropagation();
15422                        return false;
15423                    }
15424
15425                    // Allow the action to proceed immediately with toast
15426                    if (isCartButton) {
15427                        // Trigger cart action with proper toast handling
15428                        if (typeof window._pdAddToCartSubmit === 'function') {
15429                            // Use pd-addtocart.js function for proper toast handling
15430                            const form = document.getElementById('pd-buy-form') || button.closest('form');
15431                            if (form) {
15432                                const fakeEvent = { 
15433                                    target: form,
15434                                    preventDefault: () => {}
15435                                };
15436                                window._pdAddToCartSubmit(fakeEvent, productId);
15437                            }
15438                        } else if (typeof window.addToCartSubmit === 'function') {
15439                            // Fallback to addToCartSubmit
15440                            const form = document.getElementById('pd-buy-form') || button.closest('form');
15441                            if (form) {
15442                                const fakeEvent = { 
15443                                    target: form,
15444                                    preventDefault: () => {}
15445                                };
15446                                window.addToCartSubmit(fakeEvent, productId);
15447                            }
15448                        } else if (typeof window.addToCart === 'function') {
15449                            // Direct addToCart call
15450                            window.addToCart(productId, 1, variantId);
15451                        }
15452                    } else if (isBuyButton) {
15453                        // Trigger buy action
15454                        if (typeof window.buyNow === 'function') {
15455                            window.buyNow();
15456                        }
15457                    }
15458                } else if (stockInfo && !stockInfo.hasStock) {
15459
15460                    // Update global stock cache
15461                    window._currentStockInfo = {
15462                        can_add_to_cart: false,
15463                        is_in_stock: false,
15464                        available_stock: stockInfo.stock,
15465                        message: `Stok tidak tersedia${stockInfo.sku ? ` untuk ${stockInfo.sku}` : ''}`
15466                    };
15467
15468                    // Force block button
15469                    button.disabled = true;
15470                    button.style.opacity = '0.5';
15471                    button.style.cursor = 'not-allowed';
15472                    button.setAttribute('data-stock-blocked', 'true');
15473                    button.setAttribute('data-stock-reason', window._currentStockInfo.message);
15474
15475                    // Show modal with variant info
15476                    const modalMessage = stockInfo ? 
15477                        `Varian yang dipilih (${stockInfo.sku}) sedang habis stok. Silakan pilih varian lain.` :
15478                        'Produk ini sedang habis stok. Silakan pilih varian lain.';
15479
15480                    showStockModal(modalMessage);
15481
15482                    // Show security warning
15483                    if (window.showToast) {
15484                        window.showToast({
15485                            type: 'warning',
15486                            title: 'Stok Tidak Tersedia',
15487                            message: 'Varian yang dipilih sedang habis stok. Silakan pilih varian lain.',
15488                            duration: 3000
15489                        });
15490                    }
15491                } else {
15492                    // stockInfo is null (simple product) - this means embedded data is not available
15493                    // For simple products, if buttons are enabled, we should proceed (trust the API response from updateStockDisplay)
15494                    if (button && !button.disabled && !button.hasAttribute('data-stock-blocked')) {
15495
15496                        // Allow the action to proceed immediately with toast
15497                        if (isCartButton) {
15498                            // Trigger cart action with proper toast handling
15499                            if (typeof window._pdAddToCartSubmit === 'function') {
15500                                // Use pd-addtocart.js function for proper toast handling
15501                                const form = document.getElementById('pd-buy-form') || button.closest('form');
15502                                if (form) {
15503                                    const fakeEvent = { 
15504                                        target: form,
15505                                        preventDefault: () => {}
15506                                    };
15507                                    window._pdAddToCartSubmit(fakeEvent, productId);
15508                                }
15509                            } else if (typeof window.addToCartSubmit === 'function') {
15510                                // Fallback to addToCartSubmit
15511                                const form = document.getElementById('pd-buy-form') || button.closest('form');
15512                                if (form) {
15513                                    const fakeEvent = { 
15514                                        target: form,
15515                                        preventDefault: () => {}
15516                                    };
15517                                    window.addToCartSubmit(fakeEvent, productId);
15518                                }
15519                            } else if (typeof window.addToCart === 'function') {
15520                                // Direct addToCart call
15521                                window.addToCart(productId, 1, variantId);
15522                            }
15523                        } else if (isBuyButton) {
15524                            // Trigger buy action
15525                            if (typeof window.buyNow === 'function') {
15526                                window.buyNow();
15527                            }
15528                        }
15529                    } else {
15530                        // Button is disabled, don't allow action
15531                        showStockModal('Produk ini sedang tidak tersedia.');
15532                    }
15533                }
15534            } else {
15535                // No product ID, block action
15536                showStockModal('Data produk tidak valid.');
15537            }
15538
15539            return false; // Always block at capture phase
15540
15541        }, true); // Use capture phase to run BEFORE other listeners
15542
15543
15544
15545    }
15546
15547    /**
15548     * Initialize button data attributes from current page
15549     */
15550    function initializeButtonAttributes() {
15551        // Get product data from page globals or form
15552        let productId = 0, variantId = 0;
15553
15554        // Try to get from global variables (set by PHP)
15555        if (window.PD_ID) productId = parseInt(window.PD_ID);
15556
15557        // Try to get from form
15558        const form = document.getElementById('pd-buy-form');
15559        if (form) {
15560            const pidAttr = form.getAttribute('data-pid');
15561            if (pidAttr) productId = parseInt(pidAttr);
15562
15563            const varInput = document.getElementById('pd-variant-id');
15564            if (varInput) variantId = parseInt(varInput.value || 0);
15565        }
15566
15567        // Try to get from mobile FAB
15568        const mobileFab = document.getElementById('pd-mobile-fab');
15569        if (mobileFab && productId <= 0) {
15570            const pidAttr = getDataAttribute(mobileFab, 'pid');
15571            if (pidAttr) productId = parseInt(pidAttr);
15572        }
15573
15574        // Set attributes on desktop buttons
15575        const desktopButtons = document.querySelectorAll('.btn-add-cart, .btn-buy-now, #btn-add-cart, #btn-buy, #btn-buy-voucher');
15576        desktopButtons.forEach(button => {
15577            if (productId > 0) {
15578                button.setAttribute('data-product-id', productId);
15579                if (variantId > 0) {
15580                    button.setAttribute('data-variant-id', variantId);
15581                }
15582            }
15583        });
15584
15585        // Set attributes on mobile buttons
15586        const mobileButtons = document.querySelectorAll('.mf-cart, .mf-buy, #mf-cart, #mf-buy');
15587        mobileButtons.forEach(button => {
15588            if (productId > 0) {
15589                button.setAttribute('data-product-id', productId);
15590                if (variantId > 0) {
15591                    button.setAttribute('data-variant-id', variantId);
15592                }
15593            }
15594        });
15595
15596
15597
15598
15599
15600        // Initial stock display update on page load
15601        if (productId > 0) {
15602            setTimeout(() => {
15603                updateStockDisplay(productId, variantId);
15604
15605            }, 100);
15606        }
15607    }
15608
15609    /**
15610     * Update button attributes when variant changes
15611     */
15612    function updateButtonAttributesForVariant(variantId) {
15613        const productId = window.PD_ID ? parseInt(window.PD_ID) : 0;
15614
15615        if (productId <= 0) return;
15616
15617        // Update desktop buttons
15618        const desktopButtons = document.querySelectorAll('.btn-add-cart, .btn-buy-now, #btn-add-cart, #btn-buy, #btn-buy-voucher');
15619        desktopButtons.forEach(button => {
15620            button.setAttribute('data-product-id', productId);
15621            if (variantId > 0) {
15622                button.setAttribute('data-variant-id', variantId);
15623            } else {
15624                button.removeAttribute('data-variant-id');
15625            }
15626        });
15627
15628        // Update mobile buttons
15629        const mobileButtons = document.querySelectorAll('.mf-cart, .mf-buy, #mf-cart, #mf-buy');
15630        mobileButtons.forEach(button => {
15631            button.setAttribute('data-product-id', productId);
15632            if (variantId > 0) {
15633                button.setAttribute('data-variant-id', variantId);
15634            } else {
15635                button.removeAttribute('data-variant-id');
15636            }
15637        });
15638
15639
15640
15641    }
15642
15643    /**
15644     * Setup variant change integration with existing product-detail.php handlers
15645     */
15646    function setupVariantChangeIntegration() {
15647        // Strategy: Hook into existing applyVariant function instead of adding new listener
15648        // This prevents conflicts with existing variant change functionality (gallery, pricing, etc.)
15649
15650        // Method 1: Hook into existing applyVariant function
15651        setTimeout(() => {
15652            if (typeof window.applyVariant === 'function') {
15653                const originalApplyVariant = window.applyVariant;
15654                window.applyVariant = function(v, focusImage) {
15655                    // Call original function first
15656                    const result = originalApplyVariant.call(this, v, focusImage);
15657
15658                // Then add our stock validator logic
15659                const variantId = v ? (v.id || v.ID || 0) : 0;
15660
15661                // Update button attributes for stock validator
15662                updateButtonAttributesForVariant(variantId);
15663
15664                // Update stock display using embedded data (instant)
15665                const productId = window.PD_ID ? parseInt(window.PD_ID) : 0;
15666                if (productId > 0) {
15667                    updateStockDisplay(productId, variantId);
15668                }                    return result;
15669                };
15670
15671
15672
15673            } else {
15674
15675                setupVariantObserver();
15676            }
15677        }, 500);
15678
15679        // Method 2: Direct observation of hidden input value changes (fallback)
15680        setTimeout(() => {
15681            setupVariantObserver();
15682        }, 1000);
15683    }
15684
15685    /**
15686     * Setup variant observer as fallback when applyVariant hook fail
15686s
15687     */
15688    function setupVariantObserver() {
15689        const variantInput = document.getElementById('pd-variant-id');
15690        if (variantInput) {
15691            let lastVariantId = parseInt(variantInput.value || '0');
15692
15693            // Use MutationObserver to watch for value changes
15694            if (window.MutationObserver) {
15695                const observer = new MutationObserver((mutations) => {
15696                    mutations.forEach((mutation) => {
15697                        if (mutation.type === 'attributes' && mutation.attributeName === 'value') {
15698                            const newVariantId = parseInt(variantInput.value || '0');
15699                            if (newVariantId !== lastVariantId) {
15700
15701                                updateButtonAttributesForVariant(newVariantId);
15702
15703                                // Update stock display with embedded data
15704                                const productId = window.PD_ID ? parseInt(window.PD_ID) : 0;
15705                                if (productId > 0) {
15706                                    updateStockDisplay(productId, newVariantId);
15707                                }
15708
15709                                lastVariantId = newVariantId;
15710                            }
15711                        }
15712                    });
15713                });
15714
15715                observer.observe(variantInput, { 
15716                    attributes: true, 
15717                    attributeFilter: ['value'] 
15718                });
15719
15720
15721
15722            }
15723
15724            // Additional polling as ultimate fallback
15725            setInterval(() => {
15726                const currentVariantId = parseInt(variantInput.value || '0');
15727                if (currentVariantId !== lastVariantId) {
15728
15729                    updateButtonAttributesForVariant(currentVariantId);
15730
15731                    // Update stock display with embedded data
15732                    const productId = window.PD_ID ? parseInt(window.PD_ID) : 0;
15733                    if (productId > 0) {
15734                        updateStockDisplay(productId, currentVariantId);
15735                    }
15736
15737                    lastVariantId = currentVariantId;
15738                }
15739            }, 1000);
15740
15741
15742
15743        }
15744    }
15745
15746    /**
15747     * Update stock display and enforce stock-based button blocking using embedded data
15748     */
15749    function updateStockDisplay(productId, variantId = 0) {
15750
15751
15752
15753        // Get stock info from embedded data (instant)
15754        const embeddedStockInfo = getStockFromEmbeddedData(variantId);
15755
15756        let stockInfo;
15757        if (embeddedStockInfo) {
15758            // Convert embedded data to standard format
15759            stockInfo = {
15760                can_add_to_cart: embeddedStockInfo.hasStock,
15761                is_in_stock: embeddedStockInfo.hasStock,
15762                available_stock: embeddedStockInfo.stock,
15763                message: embeddedStockInfo.hasStock ? 'Stok tersedia' : `Stok habis${embeddedStockInfo.sku ? ` untuk ${embeddedStockInfo.sku}` : ''}`,
15764                product_name: window.PD_NAME || 'Produk',
15765                sku: embeddedStockInfo.sku
15766            };
15767
15768
15769
15770        } else {
15771            // Fallback to API call if embedded data not available
15772
15773            checkStock(productId, variantId, 1)
15774                .then(apiStockInfo => {
15775                    updateStockDisplayWithInfo(apiStockInfo, productId, variantId);
15776                })
15777                .catch(error => {
15778                    console.error('Failed to update stock display via API:', error);
15779                });
15780            return;
15781        }
15782
15783        updateStockDisplayWithInfo(stockInfo, productId, variantId);
15784    }
15785
15786    /**
15787     * Update stock display with provided stock info
15788     */
15789    function updateStockDisplayWithInfo(stockInfo, productId, variantId) {
15790        // Store current stock status globally for blocking checks
15791        window._currentStockInfo = stockInfo;
15792
15793        // Update stock indicators
15794        const stockIndicators = document.querySelectorAll('.stock-indicator');
15795        stockIndicators.forEach(indicator => {
15796            if (stockInfo.is_in_stock) {
15797                indicator.classList.add('in-stock');
15798                indicator.classList.remove('out-of-stock');
15799            } else {
15800                indicator.classList.add('out-of-stock');
15801                indicator.classList.remove('in-stock');
15802            }
15803        });
15804
15805        // Update stock count
15806        const stockCounts = document.querySelectorAll('.stock-count');
15807        stockCounts.forEach(count => {
15808            count.textContent = stockInfo.available_stock || 0;
15809        });
15810
15811        // CRITICAL: For digital products, DO NOT modify button states here
15812        // PHP already handles button disable/enable based on LICENSE_POOL count
15813        // Only physical products need JavaScript stock validation
15814        const isDigitalProduct = window.IS_DIGITAL === true || window.IS_DIGITAL === 'true';
15815
15816        if (isDigitalProduct) {
15817            // For digital products: ONLY update global stock info, don't touch buttons
15818            // Let PHP-rendered disabled state remain authoritative
15819            console.log('[Stock Validator] Digital product detected - skipping button state modification');
15820            return; // Exit early, preserve PHP-set button states
15821        }
15822
15823        // ENHANCED: Force disable buttons for out-of-stock with anti-tampering (PHYSICAL PRODUCTS ONLY)
15824        const allButtons = document.querySelectorAll('.btn-add-cart, .btn-buy-now, .mf-cart, .mf-buy, #btn-add-cart, #btn-buy, #btn-buy-voucher, #mf-cart, #mf-buy');
15825        allButtons.forEach(button => {
15826            if (stockInfo.can_add_to_cart) {
15827                // STOCK AVAILABLE - ENABLE BUTTONS
15828                button.classList.remove(config.disabledClass);
15829                button.disabled = false;
15830                button.style.opacity = '';
15831                button.style.cursor = '';
15832
15833                // CRITICAL: Remove stock blocking attributes when stock becomes available
15834                button.removeAttribute('data-stock-blocked');
15835                button.removeAttribute('data-stock-reason');
15836
15837
15838
15839            } else {
15840                // OUT OF STOCK - FORCE DISABLE WITH ANTI-TAMPERING
15841                button.classList.add(config.disabledClass);
15842                button.disabled = true;
15843                button.style.opacity = '0.5';
15844                button.style.cursor = 'not-allowed';
15845
15846                // Mark as stock-blocked to prevent re-enabling
15847                button.setAttribute('data-stock-blocked', 'true');
15848                button.setAttribute('data-stock-reason', stockInfo.message || 'Stok tidak tersedia');
15849
15850
15851
15852            }
15853        });
15854
15855        // Start aggressive button monitoring for out-of-stock variants
15856        if (!stockInfo.can_add_to_cart) {
15857            enforceStockDisabling(productId, variantId);
15858        }
15859
15860
15861
15862    }
15863
15864    /**
15865     * Aggressively enforce button disabling for out-of-stock items
15866     * CRITICAL: For digital products, DO NOT modify button states (PHP is authoritative)
15867     */
15868    function enforceStockDisabling(productId, variantId) {
15869        // CRITICAL: Skip enforcement for digital products - PHP handles button states
15870        const isDigitalProduct = window.IS_DIGITAL === true || window.IS_DIGITAL === 'true';
15871        if (isDigitalProduct) {
15872            console.log('[Stock Validator] Digital product - skipping button enforcement');
15873            return; // Exit early, let PHP-rendered disabled state remain
15874        }
15875
15876        const monitorInterval = setInterval(() => {
15877            // Check if variant changed
15878            const currentVariantSelector = document.getElementById('pd-variant-id');
15879            const currentVariantId = currentVariantSelector ? parseInt(currentVariantSelector.value || 0) : 0;
15880
15881            if (currentVariantId !== variantId) {
15882                // Variant changed, stop monitoring this variant
15883                clearInterval(monitorInterval);
15884
15885                return;
15886            }
15887
15888            // Check current stock status using embedded data (real-time check)
15889            const currentStockInfo = getStockFromEmbeddedData(currentVariantId);
15890
15891            if (currentStockInfo && !currentStockInfo.hasStock) {
15892                // Still out of stock - continue monitoring for tampering
15893                const buttons = document.querySelectorAll('.btn-add-cart, .btn-buy-now, .mf-cart, .mf-buy, #btn-add-cart, #btn-buy, #btn-buy-voucher, #mf-cart, #mf-buy');
15894                buttons.forEach(button => {
15895                    // Check if button was manually enabled (inspect attack)
15896                    if (!button.disabled || button.style.opacity !== '0.5') {
15897
15898                        // Force re-disable
15899                        button.disabled = true;
15900                        button.style.opacity = '0.5';
15901                        button.style.cursor = 'not-allowed';
15902                        button.setAttribute('data-stock-blocked', 'true');
15903                        button.setAttribute('data-stock-reason', `Stok tidak tersedia${currentStockInfo.sku ? ` untuk ${currentStockInfo.sku}` : ''}`);
15904
15905                        // Show warning toast
15906                        if (window.showToast) {
15907                            window.showToast({
15908                                type: 'warning',
15909                                title: 'Aksi Tidak Diizinkan',
15910                                message: 'Produk ini sedang habis stok. Silakan pilih varian lain.',
15911                                duration: 3000
15912                            });
15913                        }
15914                    }
15915                });
15916            } else {
15917                // Stock available OR embedded data shows stock - stop monitoring
15918                clearInterval(monitorInterval);
15919
15920                // For PHYSICAL products only, enable buttons
15921                // For DIGITAL products, skip (PHP is authoritative)
15922                const isDigitalProduct = window.IS_DIGITAL === true || window.IS_DIGITAL === 'true';
15923
15924                if (!isDigitalProduct && currentStockInfo && currentStockInfo.hasStock) {
15925                    const buttons = document.querySelectorAll('.btn-add-cart, .btn-buy-now, .mf-cart, .mf-buy, #btn-add-cart, #btn-buy, #btn-buy-voucher, #mf-cart, #mf-buy');
15926                    buttons.forEach(button => {
15927                        button.disabled = false;
15928                        button.style.opacity = '';
15929                        button.style.cursor = '';
15930                        button.removeAttribute('data-stock-blocked');
15931                        button.removeAttribute('data-stock-reason');
15932                        button.classList.remove(config.disabledClass);
15933                    });
15934
15935                }
15936            }
15937        }, 500); // Check every 500ms for tampering attempts
15938
15939        // Auto-stop monitoring after 30 seconds to prevent memory leaks
15940        setTimeout(() => {
15941            clearInterval(monitorInterval);
15942
15943        }, 30000);
15944    }
15945
15946    /**
15947     * Override addToCartSubmit function to add stock validation
15948     * This function now works with pd-addtocart.js integration
15949     */
15950    function overrideAddToCartSubmitFunction() {
15951        // Check if pd-addtocart.js is already loaded
15952        const pdAddToCartExists = typeof window.addToCartSubmit === 'function' && 
15953                                  window.addToCartSubmit.toString().includes('showToast');
15954
15955        if (pdAddToCartExists) {
15956
15957
15958
15959            // Store the pd-addtocart.js function
15960            const pdAddToCartSubmit = window.addToCartSubmit;
15961            window._pdAddToCartSubmit = pdAddToCartSubmit;
15962
15963            // Override with stock validation that calls pd-addtocart.js
15964            window.addToCartSubmit = async function(e, productId) {
15965
15966                // Don't prevent default here - let pd-addtocart handle it if stock is valid
15967
15968                try {
15969                    // Extract product data from form (same way as pd-addtocart.js)
15970                    const form = e.target;
15971                    const qty = parseInt(form.querySelector('[name=qty]')?.value || '1', 10) || 1;
15972
15973                    // CRITICAL: Always get current variant from selector (real-time)
15974                    let variantId = 0;
15975                    const variantSelector = document.getElementById('pd-variant-id');
15976                    if (variantSelector) {
15977                        variantId = parseInt(variantSelector.value || '0', 10) || 0;
15978                    }
15979
15980                    if (!productId || productId <= 0) {
15981                        return pdAddToCartSubmit(e, productId);
15982                    }
15983
15984
15985
15986                    // Validate stock before proceeding
15987                    const isValid = await validateBeforeCart(productId, variantId, qty, 'cart');
15988
15989                    if (isValid) {
15990                        // Stock OK, call pd-addtocart.js function (it will handle toast)
15991                        return pdAddToCartSubmit(e, productId);
15992                    } else {
15993                        // Prevent the form submission
15994                        e.preventDefault();
15995                        // Stock validation failed, modal already shown by validateBeforeCart
15996                        return false;
15997                    }
15998
15999                } catch (error) {
16000                    e.preventDefault();
16001                    // On error, show modal and don't proceed
16002                    showStockModal({ 
16003                        message: 'Terjadi kesalahan saat memvalidasi stok. Silakan coba lagi.',
16004                        is_active: false 
16005                    }, 'cart');
16006                    return false;
16007                }
16008            };
16009        } else {
16010            // Original logic for when pd-addtocart.js is not present
16011            const originalAddToCartSubmit = typeof window.addToCartSubmit === 'function' ? window.addToCartSubmit : null;
16012
16013            if (originalAddToCartSubmit) {
16014                window._originalAddToCartSubmit = originalAddToCartSubmit;
16015            }
16016
16017            // Override with stock validation
16018            window.addToCartSubmit = async function(e, productId) {
16019
16020                e.preventDefault();
16021
16022                try {
16023                    const form = e.target;
16024                    const qty = parseInt(form.querySelector('[name=qty]')?.value || '1', 10) || 1;
16025
16026                    // CRITICAL: Always get current variant from selector (real-time)
16027                    let variantId = 0;
16028                    const variantSelector = document.getElementById('pd-variant-id');
16029                    if (variantSelector) {
16030                        variantId = parseInt(variantSelector.value || '0', 10) || 0;
16031                    }
16032
16033                    if (!productId || productId <= 0) {
16034                        if (originalAddToCartSubmit) return originalAddToCartSubmit(e, productId);
16035                        return false;
16036                    }
16037
16038
16039
16040                    const isValid = await validateBeforeCart(productId, variantId, qty, 'cart');
16041
16042                    if (isValid) {
16043                        if (originalAddToCartSubmit) {
16044                            return originalAddToCartSubmit(e, productId);
16045                        } else {
16046                            if (typeof window.addToCart === 'function') {
16047                                await window.addToCart(productId, qty, variantId);
16048                            }
16049                        }
16050                    } else {
16051                    }
16052
16053                } catch (error) {
16054                    showStockModal({ 
16055                        message: 'Terjadi kesalahan saat memvalidasi stok. Silakan coba lagi.',
16056                        is_active: false 
16057                    }, 'cart');
16058                }
16059
16060                return false;
16061            };
16062        }
16063
16064
16065
16066    }
16067
16068    /**
16069     * Override buyNow function to add stock validation
16070     */
16071    function overrideBuyNowFunction() {
16072        // Store original buyNow function if it exists
16073        const originalBuyNow = typeof window.buyNow === 'function' ? window.buyNow : null;
16074
16075        // Override with stock validation
16076        window.buyNow = async function(e) {
16077
16078            try {
16079                // Get product data (same way as pd-buynow.js)
16080                const form = document.getElementById('pd-buy-form');
16081                if (!form) {
16082                    if (originalBuyNow) return originalBuyNow(e);
16083                    return;
16084                }
16085
16086                const productId = parseInt(form.dataset.pid || '0', 10);
16087                const qty = parseInt(form.querySelector('[name=qty]')?.value || '1', 10) || 1;
16088
16089                // CRITICAL: Always get current variant from selector (real-time)
16090                let variantId = 0;
16091                const variantSelector = document.getElementById('pd-variant-id');
16092                if (variantSelector) {
16093                    variantId = parseInt(variantSelector.value || '0', 10) || 0;
16094                }
16095
16096                if (!productId || productId <= 0) {
16097                    if (originalBuyNow) return originalBuyNow(e);
16098                    return;
16099                }
16100
16101
16102
16103                // Validate stock before proceeding
16104                const isValid = await validateBeforeCart(productId, variantId, qty, 'buy');
16105
16106                if (isValid) {
16107                    // Stock OK, call original buyNow function
16108                    if (originalBuyNow) {
16109                        return originalBuyNow(e);
16110                    } else {
16111                    }
16112                } else {
16113                    // Stock validation failed, modal already shown by validateBeforeCart
16114                }
16115
16116            } catch (error) {
16117                // On error, show modal and don't proceed
16118                showStockModal({ 
16119                    message: 'Terjadi kesalahan saat memvalidasi stok. Silakan coba lagi.',
16120                    is_active: false 
16121                }, 'buy');
16122            }
16123        };
16124
16125
16126
16127    }
16128
16129    /**
16130     * Initialize stock validation
16131     */
16132    function init() {
16133        // Wait for DOM ready
16134        if (document.readyState === 'loading') {
16135            addEventListener(document, 'DOMContentLoaded', function() {
16136        initializeButtonAttributes();
16137        setupVariantChangeIntegration(); // Setup variant change integration
16138        interceptCartButtons();
16139        setupGlobalClickInterceptor(); // Setup global click security interceptor                // Override functions after other scripts load
16140                // Use multiple timeouts to catch late-loading scripts like pd-addtocart.js
16141                setTimeout(() => {
16142                    overrideAddToCartSubmitFunction();
16143                    overrideBuyNowFunction();
16144                }, 100);
16145
16146                setTimeout(() => {
16147                    // Re-check and re-override if pd-addtocart.js loaded later
16148                    overrideAddToCartSubmitFunction();
16149                }, 500);
16150
16151                setTimeout(() => {
16152                    // Final check for very late loading scripts
16153                    overrideAddToCartSubmitFunction();
16154                }, 1000);
16155
16156
16157
16158            });
16159        } else {
16160            initializeButtonAttributes();
16161            setupVariantChangeIntegration(); // Setup variant change integration
16162            interceptCartButtons();
16163            setupGlobalClickInterceptor(); // Setup global click security interceptor
16164
16165            // Override functions after other scripts load
16166            // Use multiple timeouts to catch late-loading scripts like pd-addtocart.js
16167            setTimeout(() => {
16168                overrideAddToCartSubmitFunction();
16169                overrideBuyNowFunction();
16170            }, 100);
16171
16172            setTimeout(() => {
16173                // Re-check and re-override if pd-addtocart.js loaded later
16174                overrideAddToCartSubmitFunction();
16175            }, 500);
16176
16177            setTimeout(() => {
16178                // Final check for very late loading scripts
16179                overrideAddToCartSubmitFunction();
16180            }, 1000);
16181
16182
16183
16184        }
16185    }
16186
16187    // Public API
16188    return {
16189        init: init,
16190        checkStock: checkStock,
16191        validateBeforeCart: validateBeforeCart,
16192        updateStockDisplay: updateStockDisplay,
16193        showStockModal: showStockModal,
16194        config: config
16195    };
16196})();
16197
16198// Auto-initialize
16199if (typeof window !== 'undefined') {
16200    StockValidator.init();
16201}
16202
16203</script>
vendor: 2 bytes, line 16203
16203
16204<script data-inlined="pd-addtocart.js">
16205(function(){
16206  function esc(s){ return String(s||'').replace(/[&<>"']/g, m=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;' }[m])); }
16207  function getVariantSummaryText(){
16208    const vs = document.getElementById('pd-variant-summary');
16209    if (!vs) return '';
16210    const items = Array.from(vs.querySelectorAll('.vs-item strong')).map(el => el.textContent.trim()).filter(Boolean);
16211    return items.length ? items.join(' / ') : '';
16212  }
16213  window.addToCartSubmit = async function(e, productId){
16214    e.preventDefault();
16215    try {
16216      const form = e.target;
16217      const qty = parseInt(form.querySelector('[name=qty]')?.value || '1', 10) || 1;
16218      const variantId = parseInt(document.getElementById('pd-variant-id')?.value || '0', 10) || 0;
16219      await (window.addToCart ? window.addToCart(productId, qty, variantId) : Promise.reject(new Error('addToCart tidak tersedia')));
16220
16221      if (window.PlazaAnalytics) {
16222        window.PlazaAnalytics.track('add_to_cart', { slug: String(window._pit_slug || '') });
16223      }
16224
16225      const name = (window.PD_NAME !== undefined) ? String(window.PD_NAME) : (document.getElementById('pd-title')?.textContent || '');
16226      const sku  = (function(){ const el=document.getElementById('pd-sku'); return el?el.textContent.trim(): (window.PD_SKU||''); })();
16227      const variantText = getVariantSummaryText();
16228
16229      if (window.showToast) {
16230        window.showToast({
16231          type: 'success',
16232          title: 'Berhasil ditambahkan ke Keranjang',
16233          message: `<strong>${esc(name)}</strong>${variantText?` · <em>${esc(variantText)}</em>`:''}<br>SKU: ${esc(sku)} · Qty: ${qty}`,
16234          duration: 5000,
16235          actions: [
16236            { label: 'Lihat Keranjang', onClick: ()=> { window.location.href = '/keranjang'; } },
16237            { label: 'Lanjut Belanja', variant: 'alt', onClick: ()=>{} }
16238          ]
16239        });
16240      }
16241    } catch (err) {
16242      if (window.showToast) {
16243        window.showToast({ type:'warn', title:'Gagal menambahkan', message: String(err?.message||'Terjadi kesalahan'), duration: 4500 });
16244      } else {
16245        console.error(err);
16246      }
16247    }
16248    return false;
16249  };
16250})();
16251</script>
vendor: 2 bytes, line 16251
16251
16252<script data-inlined="pd-buynow.js">
16253(function(){
16254
16255  // CSRF Token Helper
16256  function getCsrfToken() {
16257    return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
16258  }
16259
16260  // Utility functions
16261  function esc(s){ return String(s||'').replace(/[&<>"']/g, m=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;' }[m])); }
16262
16263  function getVariantSummaryText(){
16264    const vs = document.getElementById('pd-variant-summary');
16265    if (!vs) return '';
16266    const items = Array.from(vs.querySelectorAll('.vs-item strong')).map(el => el.textContent.trim()).filter(Boolean);
16267    return items.length ? items.join(' / ') : '';
16268  }
16269
16270  function getCurrentProductData(){
16271    const form = document.getElementById('pd-buy-form');
16272    if (!form) return null;
16273
16274    const productId = parseInt(form.dataset.pid || '0', 10);
16275    const qty = parseInt(form.querySelector('[name=qty]')?.value || '1', 10) || 1;
16276    const variantId = parseInt(document.getElementById('pd-variant-id')?.value || '0', 10) || 0;
16277
16278    if (!productId || productId <= 0) {
16279      throw new Error('Product ID tidak valid');
16280    }
16281
16282    if (qty <= 0) {
16283      throw new Error('Quantity harus lebih dari 0');
16284    }
16285
16286    return { productId, qty, variantId };
16287  }
16288
16289  function createBuyNowPayload(productId, qty, variantId) {
16290    // Get product metadata
16291    const name = (window.PD_NAME !== undefined) ? String(window.PD_NAME) : (document.getElementById('pd-title')?.textContent || '');
16292
16293    // Get correct price, SKU, and weight from selected variant data instead of DOM elements
16294    let price = 0;
16295    let sku = '';
16296    let weight = 0;
16297    let dimensions = null;
16298
16299    if (variantId > 0 && typeof window.variants !== 'undefined' && Array.isArray(window.variants)) {
16300      // Find the selected variant in the variants array
16301      const selectedVariant = window.variants.find(v => v.id === variantId);
16302      if (selectedVariant) {
16303        price = selectedVariant.final || 0;
16304        sku = selectedVariant.sku || '';
16305        weight = selectedVariant.weight || 0;
16306
16307        // Include dimensions if available
16308        if (selectedVariant.length_cm || selectedVariant.width_cm || selectedVariant.height_cm) {
16309          dimensions = {
16310            length_cm: selectedVariant.length_cm || 0,
16311            width_cm: selectedVariant.width_cm || 0,
16312            height_cm: selectedVariant.height_cm || 0
16313          };
16314        }
16315
16316        // Debug info to verify fix is working
16317        if (window.console && window.console.debug) {
16318          console.debug('BuyNow: Using variant data', {
16319            variantId: variantId,
16320            price: price,
16321            sku: sku,
16322            weight: weight,
16323            dimensions: dimensions,
16324            variantData: selectedVariant
16325          });
16326        }
16327      }
16328    }
16329
16330    // Fallback to DOM elements if variant data not found
16331    if (price === 0) {
16332      const priceTxt = document.getElementById('pd-price-current')?.textContent || '';
16333      price = parseInt(String(priceTxt).replace(/[^d]/g,''), 10) || 0;
16334
16335      if (window.console && window.console.debug) {
16336        console.debug('BuyNow: Using DOM price fallback', { 
16337          priceTxt: priceTxt, 
16338          price: price,
16339          variantId: variantId,
16340          reason: variantId > 0 ? 'variant_data_not_found' : 'no_variant_selected'
16341        });
16342      }
16343    }
16344
16345    if (!sku) {
16346      const skuEl = document.getElementById('pd-sku');
16347      sku = skuEl ? skuEl.textContent.trim() : (window.PD_SKU || '');
16348    }
16349
16350    const imgEl = document.getElementById('pd-main-img');
16351    const image_url = imgEl ? (imgEl.getAttribute('src') || '') : '';
16352
16353    const variantText = getVariantSummaryText();
16354
16355    // Get pre-order data if product is pre-order
16356    const preorderData = window.PD_PREORDER_DATA || null;
16357
16358    return {
16359      product_id: productId,
16360      qty: qty,
16361      variant_id: variantId,
16362      meta: {
16363        name,
16364        sku,
16365        price,
16366        image_url,
16367        variant_text: variantText,
16368        weight: weight,
16369        dimensions: dimensions
16370      },
16371      is_preorder: preorderData ? 'Y' : 'N',
16372      preorder_estimated_arrival: preorderData ? preorderData.estimated_arrival : null,
16373      preorder_note: preorderData ? preorderData.note : ''
16374    };
16375  }
16376
16377  /**
16378   * Main Buy Now function
16379   * Performs validation and redirects to checkout with buy_now mode
16380   */
16381  window.buyNow = async function(e) {
16382    if (e) e.preventDefault();
16383
16384    try {
16385      // Check user login status
16386      const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true' ||
16387                        document.body.classList.contains('logged-in') ||
16388                        window.userLoggedIn === true ||
16389                        document.body.dataset.userLoggedIn === 'true';
16390
16391      if (!isLoggedIn) {
16392        // Redirect to login with return URL to current page
16393        const currentUrl = encodeURIComponent(window.location.pathname + window.location.search);
16394        window.location.href = '/login?return=' + currentUrl;
16395        return;
16396      }
16397
16398      // Get current product data
16399      const { productId, qty, variantId } = getCurrentProductData();
16400
16401      // Create payload for buy now
16402      const payload = createBuyNowPayload(productId, qty, variantId);
16403
16404      // Store buy now data in sessionStorage for checkout page
16405      sessionStorage.setItem('buyNowData', JSON.stringify(payload));
16406
16407      // Track add_to_cart (buy now counts as purchase intent)
16408      if (window.PlazaAnalytics) {
16409        window.PlazaAnalytics.track('add_to_cart', { slug: String(window._pit_slug || '') });
16410      }
16411
16412      // Redirect to checkout with buy_now mode
16413      window.location.href = '/checkout?mode=buy_now';
16414
16415    } catch (err) {
16416      console.error('Buy Now error:', err);
16417
16418      if (window.showToast) {
16419        window.showToast({ 
16420          type: 'warn', 
16421          title: 'Gagal memproses', 
16422          message: String(err?.message || 'Terjadi kesalahan saat memproses pembelian'), 
16423          duration: 4500 
16424        });
16425      } else {
16426        alert(err.message || 'Terjadi kesalahan saat memproses pembelian');
16427      }
16428    }
16429  };
16430
16431  /**
16432   * Alternative Buy Now with confirmation dialog
16433   */
16434  window.buyNowWithConfirm = async function(e) {
16435    if (e) e.preventDefault();
16436
16437    try {
16438      const { productId, qty, variantId } = getCurrentProductData();
16439
16440      const name = (window.PD_NAME !== undefined) ? String(window.PD_NAME) : (document.getElementById('pd-title')?.textContent || '');
16441      const variantText = getVariantSummaryText();
16442      const priceTxt = document.getElementById('pd-price-current')?.textContent || 'Rp0';
16443
16444      const confirmMessage = `Lanjut ke checkout?nnProduk: ${name}${variantText ? `nVariant: ${variantText}` : ''}nQty: ${qty}nHarga: ${priceTxt}`;
16445
16446      if (!confirm(confirmMessage)) {
16447        return;
16448      }
16449
16450      // Proceed with buy now
16451      await window.buyNow(e);
16452
16453    } catch (err) {
16454      console.error('Buy Now with confirm error:', err);
16455      alert(err.message || 'Terjadi kesalahan');
16456    }
16457  };
16458
16459  /**
16460   * Attach event listener for Buy with Voucher button
16461   */
16462  function setupBuyButtons() {
16463    // Setup desktop buy button (regular buy now without voucher)
16464    const buyBtn = document.getElementById('btn-buy');
16465    if (buyBtn) {
16466      buyBtn.addEventListener('click', function(e) {
16467        e.preventDefault();
16468        if (buyBtn.disabled) return;
16469        if (window.buyNow) {
16470          window.buyNow(e);
16471        }
16472      });
16473    }
16474
16475    // Setup desktop buy with voucher button
16476    const voucherBtn = document.getElementById('btn-buy-voucher');
16477    if (voucherBtn) {
16478      voucherBtn.addEventListener('click', function(e) {
16479        e.preventDefault();
16480
16481        // Validate stock availability before proceeding
16482        if (this.disabled || this.getAttribute('data-stock-blocked') === 'true') {
16483          const stockReason = this.getAttribute('data-stock-reason') || 'Stok tidak tersedia';
16484
16485          if (window.showToast) {
16486            window.showToast({
16487              type: 'warn',
16488              title: 'Stok Tidak Tersedia',
16489              message: stockReason,
16490              duration: 3500
16491            });
16492          } else {
16493            alert(stockReason);
16494          }
16495          return;
16496        }
16497
16498        const voucherCode = this.dataset.voucherCode;
16499        if (voucherCode) {
16500          window.buyNowWithVoucher(e, voucherCode);
16501        }
16502      });
16503    }
16504
16505  }
16506
16507  // CRITICAL: Attach mobile button handler at DOCUMENT level with CAPTURE phase
16508  // This is the ONLY way to guarantee it fires regardless of other scripts
16509  document.addEventListener('click', function(e) {
16510    // Check if click is on mobile buy button or its children
16511    const target = e.target.closest('#mf-buy');
16512    if (!target) return; // Not our button
16513
16514    e.preventDefault();
16515    e.stopPropagation();
16516    e.stopImmediatePropagation();
16517
16518    if (target.disabled) return;
16519
16520    // Get voucher code from parent FAB's data attribute
16521    const mobileFab = document.getElementById('pd-mobile-fab');
16522    const voucherCode = mobileFab ? (mobileFab.getAttribute('data-voucher-code') || '') : '';
16523
16524    if (voucherCode && voucherCode.trim() !== '' && window.buyNowWithVoucher) {
16525      window.buyNowWithVoucher(e, voucherCode);
16526    } else if (window.buyNow) {
16527      window.buyNow(e);
16528    }
16529  }, true); // CAPTURE PHASE - fires BEFORE any bubble phase handlers
16530
16531  // Execute immediately if DOM already loaded, otherwise wait
16532  if (document.readyState === 'loading') {
16533    document.addEventListener('DOMContentLoaded', setupBuyButtons);
16534  } else {
16535    setupBuyButtons();
16536  }
16537
16538  /**
16539   * Buy Now with Auto-Applied Voucher
16540   * Applies voucher to session before redirecting to checkout (like cart does)
16541   * 
16542   * @param {Event} e - Click event
16543   * @param {string} voucherCode - Voucher code to apply
16544   */
16545  window.buyNowWithVoucher = async function(e, voucherCode) {
16546    if (e) e.preventDefault();
16547
16548    console.log('[buyNowWithVoucher] Called with voucher code:', voucherCode);
16549
16550    try {
16551      // CRITICAL: Check stock availability first
16552      const voucherBtn = document.getElementById('btn-buy-voucher');
16553      if (voucherBtn) {
16554        if (voucherBtn.disabled || voucherBtn.getAttribute('data-stock-blocked') === 'true') {
16555          const stockReason = voucherBtn.getAttribute('data-stock-reason') || 'Stok tidak tersedia';
16556
16557          if (window.showToast) {
16558            window.showToast({
16559              type: 'warn',
16560              title: 'Stok Tidak Tersedia',
16561              message: stockReason,
16562              duration: 3500
16563            });
16564          } else {
16565            alert(stockReason);
16566          }
16567          return;
16568        }
16569      }
16570
16571      // Check login status first
16572      const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true' ||
16573                        document.body.classList.contains('logged-in') ||
16574                        window.userLoggedIn === true ||
16575                        document.body.dataset.userLoggedIn === 'true';
16576
16577      console.log('[buyNowWithVoucher] User logged in:', isLoggedIn);
16578
16579      if (!isLoggedIn) {
16580        const currentUrl = encodeURIComponent(window.location.pathname + window.location.search);
16581        window.location.href = '/login?return=' + currentUrl;
16582        return;
16583      }
16584
16585      // Get current product data
16586      const { productId, qty, variantId } = getCurrentProductData();
16587      const payload = createBuyNowPayload(productId, qty, variantId);
16588
16589      console.log('[buyNowWithVoucher] Product data:', { productId, qty, variantId });
16590
16591      // Get category path for voucher validation
16592      const categoryPath = window.PD_CATEGORY_PATH || '';
16593
16594      // Step 1: Clear any existing voucher first (important for clean state)
16595      console.log('[buyNowWithVoucher] Step 1: Clearing existing voucher...');
16596      try {
16597        await fetch('/api/voucher/remove.php', {
16598          method: 'POST',
16599          headers: { 
16600            'X-CSRF-Token': getCsrfToken()
16601          },
16602          body: JSON.stringify({
16603            csrf_token: getCsrfToken()
16604          }),
16605          credentials: 'same-origin'
16606        });
16607      } catch (e) {
16608        // Ignore error if no voucher exists
16609        console.debug('No existing voucher to remove or removal failed');
16610      }
16611
16612      // Step 2: Apply voucher to session (like cart does)
16613      console.log('[buyNowWithVoucher] Step 2: Applying voucher to session...');
16614      const applyRes = await fetch('/api/voucher/apply.php', {
16615        method: 'POST',
16616        headers: { 
16617          'Content-Type': 'application/json',
16618          'X-CSRF-Token': getCsrfToken()
16619        },
16620        credentials: 'same-origin',
16621        body: JSON.stringify({
16622          csrf_token: getCsrfToken(),
16623          code: voucherCode,
16624          items: [{
16625            pid: productId,
16626            price: payload.meta.price,
16627            qty: qty,
16628            category_path: categoryPath
16629          }],
16630          shipping_cost: null // Will be calculated in checkout
16631        })
16632      });
16633
16634      const applyData = await applyRes.json();
16635
16636      console.log('[buyNowWithVoucher] Voucher apply response:', applyData);
16637
16638      if (!applyData.success) {
16639        // Voucher invalid - show error and fallback to regular buy now
16640        console.warn('[buyNowWithVoucher] Voucher application failed:', applyData.message);
16641        if (window.showToast) {
16642          window.showToast({
16643            type: 'error',
16644            title: 'Voucher Tidak Valid',
16645            message: applyData.message || 'Voucher tidak dapat diterapkan untuk produk ini',
16646            duration: 4000
16647          });
16648        } else {
16649          alert(applyData.message || 'Voucher tidak dapat diterapkan');
16650        }
16651
16652        // Fallback to regular buy now without voucher
16653        console.log('Voucher failed, proceeding with regular buy now');
16654        return window.buyNow(e);
16655      }
16656
16657      // Step 3: Store buy now data in sessionStorage
16658      console.log('[buyNowWithVoucher] Step 3: Storing buy now data in sessionStorage');
16659      sessionStorage.setItem('buyNowData', JSON.stringify(payload));
16660
16661      // Step 4: Redirect to checkout
16662      // Voucher sudah di session, checkout akan auto-load via loadAppliedVoucher()
16663      console.log('[buyNowWithVoucher] Step 4: Redirecting to checkout with voucher applied');
16664      window.location.href = '/checkout?mode=buy_now';
16665
16666    } catch (err) {
16667      console.error('Buy Now with Voucher error:', err);
16668
16669      if (window.showToast) {
16670        window.showToast({
16671          type: 'error',
16672          title: 'Terjadi Kesalahan',
16673          message: 'Gagal memproses pembelian dengan voucher. Silakan coba lagi.',
16674          duration: 3000
16675        });
16676      } else {
16677        alert('Gagal memproses pembelian. Silakan coba lagi.');
16678      }
16679    }
16680  };
16681})();
16682</script>
vendor: 2 bytes, line 16682
16682
16683<script data-inlined="pd-voucher-recalc.js">
16684/**
16685 * Product Detail - Voucher Discount Recalculation
16686 * 
16687 * Handles dynamic voucher discount recalculation when variant price changes
16688 * Supports both PERCENTAGE and FIXED_AMOUNT voucher types
16689 */
16690
16691(function() {
16692  'use strict';
16693
16694  /**
16695   * Calculate voucher discount based on type and price
16696   * 
16697   * @param {string} type - Voucher type: 'PERCENTAGE' or 'FIXED_AMOUNT'
16698   * @param {number} value - Voucher value (percentage or fixed amount)
16699   * @param {number} price - Current variant price
16700   * @param {number} maxDiscount - Maximum discount for percentage vouchers (0 = no limit)
16701   * @return {number} Calculated discount amount
16702   */
16703  function calculateVoucherDiscount(type, value, price, maxDiscount) {
16704    if (type === 'PERCENTAGE') {
16705      // Calculate percentage discount
16706      let discount = (price * value) / 100;
16707
16708      // Apply max discount cap if set
16709      if (maxDiscount > 0 && discount > maxDiscount) {
16710        discount = maxDiscount;
16711      }
16712
16713      return Math.round(discount);
16714    } else {
16715      // FIXED_AMOUNT - discount doesn't change with price
16716      return Math.round(value);
16717    }
16718  }
16719
16720  /**
16721   * Format number as Rupiah without 'Rp' prefix
16722   * 
16723   * @param {number} num - Number to format
16724   * @return {string} Formatted number (e.g., "20.000")
16725   */
16726  function formatNumber(num) {
16727    return Math.round(num).toString().replace(/B(?=(d{3})+(?!d))/g, '.');
16728  }
16729
16730  /**
16731   * Update voucher button discount label
16732   * 
16733   * @param {number} newPrice - New variant price
16734   */
16735  function updateVoucherDiscount(newPrice) {
16736    const voucherBtn = document.getElementById('btn-buy-voucher');
16737    if (!voucherBtn) return;
16738
16739    // Get voucher data from button attributes
16740    const voucherType = voucherBtn.dataset.voucherType;
16741    const voucherValue = parseFloat(voucherBtn.dataset.voucherValue) || 0;
16742    const voucherMax = parseFloat(voucherBtn.dataset.voucherMax) || 0;
16743
16744    if (!voucherType || voucherValue === 0) return;
16745
16746    // Calculate new discount
16747    const newDiscount = calculateVoucherDiscount(voucherType, voucherValue, newPrice, voucherMax);
16748
16749    // Update the discount label
16750    const saveLabel = voucherBtn.querySelector('.bv-save');
16751    if (saveLabel) {
16752      saveLabel.textContent = '-Rp' + formatNumber(newDiscount);
16753    }
16754
16755    // Also update mobile FAB voucher display if exists
16756    updateMobileFabVoucher(newDiscount);
16757  }
16758
16759  /**
16760   * Update mobile FAB voucher value
16761   * 
16762   * @param {number} discountAmount - New discount amount
16763   */
16764  function updateMobileFabVoucher(discountAmount) {
16765    const fab = document.getElementById('pd-mobile-fab');
16766    if (fab) {
16767      fab.dataset.voucherValue = discountAmount.toString();
16768    }
16769  }
16770
16771  /**
16772   * Listen to variant change events and recalculate discount
16773   */
16774  function initVoucherRecalculation() {
16775    window.addEventListener('pd-variant-changed', function(e) {
16776      const detail = e.detail || {};
16777      const newPrice = detail.price || 0;
16778
16779      if (newPrice > 0) {
16780        updateVoucherDiscount(newPrice);
16781      }
16782    });
16783  }
16784
16785  // Initialize when DOM is ready
16786  if (document.readyState === 'loading') {
16787    document.addEventListener('DOMContentLoaded', initVoucherRecalculation);
16788  } else {
16789    initVoucherRecalculation();
16790  }
16791
16792})();
16793
16794</script>
vendor: 2 bytes, line 16794
16794
16795<script nonce="se3iyzEHMc9FQEB4wSyd0w==">document.addEventListener('DOMContentLoaded',function(){if(typeof ProductReviews!=='undefined'){ProductReviews.init(325);}const r=document.getElementById('pd-rating-inline');if(r){r.addEventListener('click',function(e){e.preventDefault();const s=document.getElementById('reviews');if(s){s.scrollIntoView({behavior:'smooth',block:'start'});s.classList.add('highlight-flash');setTimeout(()=>{s.classList.remove('highlight-flash')},1500)}});}});</script>
16795
16796
16797<script nonce="se3iyzEHMc9FQEB4wSyd0w==">
16798(function(){function _0x9i0j(el){if(typeof gtag==='function'){gtag('event','qoala_insurance_click',{event_category:'Insurance',event_label:'Qoala Banner Click - Product Detail',link_url:'/plazait-protect',page_location:window.location.href,product_id:parseInt(el.getAttribute('data-product-id'))||0,product_name:el.getAttribute('data-product-name')||'',category_path:el.getAttribute('data-category-path')||'',custom_map:{custom_parameter_1:'qoala_banner_product_detail'}})}}
16799
16800function _0x1k2l(){var a=document.querySelector('.qoala-cta[data-ga-event="qoala_banner_click"]');if(a){a.addEventListener('click',function(e){_0x9i0j(this)})}}if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',_0x1k2l)}else{_0x1k2l()}})();
16801
16802(function(){const pwpSection=document.getElementById('pwp-deals-section');const pwpBadge=document.getElementById('pwp-trigger-badge');if(!pwpSection)return;const productId=parseInt(pwpSection.getAttribute('data-product-id'))||0;const variantId=parseInt(pwpSection.getAttribute('data-variant-id'))||0;const categoryPath=pwpSection.getAttribute('data-category-path')||'';if(!productId)return;function formatRupiah(n){return'Rp'+Math.floor(n).toLocaleString('id-ID')}function loadPWPDeals(){const url=`/api/pwp/get_by_product.php?product_id=${productId}${variantId?`&variant_id=${variantId}`:''}${categoryPath?`&category_path=${encodeURIComponent(categoryPath)}`:''}`;fetch(url).then(r=>r.json()).then(data=>{if(data.success&&data.promotions&&data.promotions.length>0){renderPWPDeals(data.promotions);pwpSection.hidden=false;if(pwpBadge)pwpBadge.hidden=false;setTimeout(function(){if(typeof window.adjustSidebarBanner==='function'){window.adjustSidebarBanner()}},150)}else{pwpSection.hidden=true;if(pwpBadge)pwpBadge.hidden=true}}).catch(err=>{console.error('PWP load error:',err);pwpSection.hidden=true;if(pwpBadge)pwpBadge.hidden=true})}function renderPWPDeals(promotions){const loading=pwpSection.querySelector('.pwp-deals-loading');const grid=document.getElementById('pwp-deals-grid');if(loading)loading.hidden=true;if(!grid)return;grid.innerHTML='';promotions.forEach(promo=>{promo.bundled_products.forEach(product=>{const card=document.createElement('div');card.className='pwp-deal-card';const discount=product.discount>0?product.discount:0;const discountPercent=product.regular_price>0?Math.round((discount/product.regular_price)*100):0;const imgUrl=product.image_url?`/uploads/products/${product.image_url}`:'/assets/img/no-image.png';card.innerHTML=`<div class="pwp-deal-thumb"><img src="${imgUrl}" alt="${escapeHtml(product.name)}" loading="lazy"></div><div class="pwp-deal-info"><h4 class="pwp-deal-name">${escapeHtml(product.name)}</h4><div class="pwp-deal-prices"><span class="pwp-deal-price">${formatRupiah(product.pwp_price)}</span>${product.regular_price>product.pwp_price?`<span class="pwp-deal-original">${formatRupiah(product.regular_price)}</span>`:''}</div></div>${discountPercent>0?`<span class="pwp-deal-badge">-${discountPercent}%</span>`:''}`;grid.appendChild(card)})})}function escapeHtml(str){const div=document.createElement('div');div.textContent=str||'';return div.innerHTML}loadPWPDeals();window.addEventListener('pd-variant-changed',function(e){const detail=e.detail||{};const newVid=detail.variant_id||0;const currentVid=parseInt(pwpSection.getAttribute('data-variant-id'))||0;if(newVid!==currentVid){pwpSection.setAttribute('data-variant-id',newVid);loadPWPDeals()}})})();
16803</script>
16803<footer class="site-footer footer-compact variant-stripes" role="contentinfo">
16804  <div class="ft-top ft-collapsible" id="ftTop">
16805    <div class="container ft-grid">
16806
16807      <div class="ft-col ft-help">
16808        <h4 class="ft-title">Butuh Bantuan?</h4>
16809        <p class="ft-desc">Hubungi kami melalui Official WhatsApp interaktif sekarang.</p>
16810        <a class="ft-wa-btn" href="https://8822ina.com" target="_blank" rel="nofollow noopener">
16811          <span class="ico" aria-hidden="true">
16812            <svg viewBox="0 0 24 24" width="18" height="18"><path fill="#25D366" d="M12.04 2c-5.52 0-10 4.43-10 9.9 0 1.75.47 3.45 1.38 4.94L2 22l5.3-1.38c1.43.78 3.05 1.19 4.7 1.19 5.52 0 10-4.43 10-9.9S17.56 2 12.04 2Zm5.86 13.9c-.25.7-1.23 1.28-1.7 1.33-.45.06-1.02.08-1.65-.1-.38-.12-.88-.29-1.52-.56-2.66-1.15-4.4-3.83-4.53-4.01-.13-.18-1.08-1.44-1.08-2.75 0-1.31.68-1.95.92-2.22.25-.27.54-.33.72-.33.18 0 .36.01.52.01.17 0 .39-.06.62.47.25.6.85 2.07.92 2.22.07.15.12.33.02.53-.1.2-.15.33-.3.5-.15.17-.32.39-.46.53-.15.15-.3.31-.13.6.18.3.8 1.31 1.73 2.12 1.19 1.03 2.17 1.36 2.48 1.5.31.14.49.12.67-.07.18-.18.77-.82.98-1.1.21-.27.42-.23.7-.14.27.09 1.72.81 2.02.95.3.14.5.22.58.35.08.13.08.72-.17 1.42Z"/></svg>
16813          </span>
16814          Kirim Pesan
16815        </a>
16816
16817        <p class="ft-desc sm-gap">Ikuti kami:</p>
16818        <div class="ft-social">
16819          <a aria-label="Facebook" href="https://facebook.com/PlazaIt/"><svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M13.5 22v-8.2h2.7l.4-3.2h-3.1V8c0-.9.3-1.5 1.6-1.5H17V3.7c-.3 0-1.2-.1-2.2-.1-2.2 0-3.8 1.3-3.8 3.9v2.2H8v3.2h3v8.1h2.5Z"/></svg></a>
16820          <a aria-label="Instagram" href="https://www.instagram.com/plazait"><svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M7.8 2h8.4A5.8 5.8 0 0 1 22 7.8v8.4A5.8 5.8 0 0 1 16.2 22H7.8A5.8 5.8 0 0 1 2 16.2V7.8A5.8 5.8 0 0 1 7.8 2Zm0 2A3.8 3.8 0 0 0 4 7.8v8.4A3.8 3.8 0 0 0 7.8 20h8.4a3.8 3.8 0 0 0 3.8-3.8
16820V7.8A3.8 3.8 0 0 0 16.2 4H7.8Zm9.9 1.7a1.1 1.1 0 1 1 0 2.3 1.1 1.1 0 0 1 0-2.3ZM12 7a5 5 0 1 1 0 10 5 5 0 0 1 0-10Zm0 2a3 3 0 1 0 0 6 3 3 0 0 0 0-6Z"/></svg></a>
16821          <a aria-label="TikTok" href="https://www.tiktok.com/@plazait"><svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M18.9 7.4a6.4 6.4 0 0 1-3.2-1.2v6.3a5.47 5.47 0 1 1-5.5-5.5c.2 0 .4 0 .6.02v2.37a2.92 2.92 0 0 0-.6-.07 2.8 2.8 0 1 0 2.8 2.8V2h2.3a4.6 4.6 0 0 0 4.5 4.1v2.3Z"/></svg></a>
16822        </div>
16823      </div>
16824
16825            <!-- Collapsible Content Wrapper (Non-Homepage Only) -->
16826      <div class="ft-collapsible-content" id="ftCollapsible">
16827
16828      <div class="ft-col">
16829        <h4 class="ft-title">Tentang BOYTOTO</h4>
16830        <ul class="ft-links">
16831          <li><a href="https://mgsco.org/">Tentang BOYTOTO</a></li>
16832          <li><a href="https://mgsco.org/">Panduan BOYTOTO</a></li>
16833          <li><a href="https://mgsco.org/">Informasi Layanan</a></li>
16834          <li><a href="https://mgsco.org/">FAQ</a></li>
16835        </ul>
16836        <div class="ft-address">
16837          <p class="company">PT. MADU MEDIA KREATIF</p>
16838                      <p>Jl. Angkasa Perdana No. 303, Kel. Srikandi,</p>
16839                      <p>Kec. Skyview, Kab. Bogor, Jawa Barat, 16915</p>
16840                  </div>
16841      </div>
16842
16843      <div class="ft-col">
16844        <h4 class="ft-title">Informasi</h4>
16845        <ul class="ft-links">
16846          <li><a href="/syarat-dan-ketentuan">Syarat &amp; Ketentuan</a></li>
16847          <li><a href="/kebijakan-privasi">Kebijakan Privasi</a></li>
16848          <li><a href="/kebijakan-cookie">Kebijakan Cookie</a></li>
16849          <li><a href="/pengiriman-dan-pengembalian">Pengiriman &amp; Pengembalian</a></li>
16850          <li><a href="/pembayaran-dan-cicilan">Pembayaran &amp; Cicilan</a></li>
16851          <li><a href="/garansi-dan-layanan">Garansi &amp; Layanan</a></li>
16852        </ul>
16853
16854        <div class="ft-payments payments-small" aria-label="Metode Pembayaran">
16855          <div class="pay-row">
16856            <img src="https://imgmahasuhu.io/images/2026/05/20/wopslot-mandiri.png" alt="Mandiri" loading="lazy">
16857            <img src="https://imgmahasuhu.io/images/2026/05/20/wopslot-bni.gif" alt="BNI" loading="lazy">
16858            <img src="https://imgmahasuhu.io/images/2026/05/20/wopslot-bri.gif" alt="BRI" loading="lazy">
16859            <img src="https://imgmahasuhu.io/images/2026/05/20/wopslot-qris.gif" alt="QRIS" loading="lazy">
16860            <img src="https://imgmahasuhu.io/images/2026/05/20/wopslot-bca.gif" alt="BCA" loading="lazy">
16861          </div>
16862        </div>
16863      </div>
16864
16865            </div>
16866      <!-- End Collapsible Content -->
16867
16868      <!-- Expand Button (Mobile Only) -->
16869      <button class="ft-expand-btn" id="ftExpandBtn" aria-label="Lihat semua informasi footer">
16870        <span class="expand-text">Selengkapnya</span>
16871        <svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
16872          <path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/>
16873        </svg>
16874      </button>
16875
16876    </div>
16877  </div>
16878  <div class="ft-bottom">
16879    <div class="container ft-bottom-inner">
16880      <div class="ft-copy">
16881        <span>ANGIN-RIBUT &copy; <span id="yearNow">2026</span></span>
16882        <span>PT ANGIN-RIBUT. All rights reserved.</span>
16883      </div>
16884    </div>
16885  </div>
16886
16887          <style nonce="se3iyzEHMc9FQEB4wSyd0w==">
16888            /* Chat Widget Styles - Lightweight & Minimal */
16889            .chat-widget {
16890                position: fixed;
16891                bottom: 20px;
16892                right: 20px;
16893                z-index: 9999;
16894                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
16895            }
16896
16897            /* Hide floating button on mobile - use bottom nav instead */
16898            @media (max-width: 640px) {
16899                .chat-toggle-btn {
16900                    display: none !important;
16901                }
16902
16903                /* Ensure popup is still visible on mobile even when button is hidden */
16904                .chat-widget {
16905                    display: block !important;
16906                }
16907
16908                .chat-popup {
16909                    display: none; /* Hidden by default, shown via JS */
16910                }
16911
16912                .chat-popup[style*="display: block"],
16913                .chat-popup[style*="display:block"] {
16914                    display: block !important;
16915                }
16916            }
16917
16918            .chat-toggle-btn {
16919                width: 60px;
16920                height: 60px;
16921                border-radius: 50%;
16922                background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
16923                border: none;
16924                color: white;
16925                cursor: pointer;
16926                box-shadow: 0 4px 12px rgba(242, 140, 26, 0.3);
16927                position: relative;
16928                transition: transform 0.2s, box-shadow 0.2s;
16929            }
16930
16931            .chat-toggle-btn:hover {
16932                transform: scale(1.05);
16933                box-shadow: 0 6px 20px rgba(242, 140, 26, 0.4);
16934            }
16935
16936            #chat-toggle-btn .chat-icon {
16937                position: absolute;
16938                top: 50%;
16939                left: 50%;
16940                transform: translate(-50%, -50%);
16941                width: 28px !important;
16942                height: 28px !important;
16943            }
16944
16945            .chat-unread-badge {
16946                position: absolute;
16947                top: -5px;
16948                right: -5px;
16949                background: #ef4444;
16950                color: white;
16951                border-radius: 12px;
16952                padding: 2px 6px;
16953                font-size: 11px;
16954                font-weight: 600;
16955                min-width: 20px;
16956                text-align: center;
16957            }
16958
16959            .chat-popup {
16960                position: absolute;
16961                bottom: 80px;
16962                right: 0;
16963                width: 380px;
16964                max-width: calc(100vw - 40px);
16965                height: 500px;
16966                max-height: calc(100vh - 120px);
16967                background: white;
16968                border-radius: 12px;
16969                box-shadow: 0 10px 40px rgba(0,0,0,0.2);
16970                display: flex;
16971                flex-direction: column;
16972                overflow: hidden;
16973            }
16974
16975            .chat-popup-header {
16976                background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
16977                color: white;
16978                padding: 16px 20px;
16979                display: flex;
16980                justify-content: space-between;
16981                align-items: center;
16982                flex-shrink: 0;
16983            }
16984
16985            .chat-header-title {
16986                display: flex;
16987                flex-direction: column;
16988                gap: 2px;
16989                min-height: 0;
16990            }
16991
16992            .chat-popup-header h3 {
16993                margin: 0;
16994                font-size: 18px;
16995                font-weight: 600;
16996                line-height: 1.2;
16997            }
16998
16999            .chat-operating-hours {
17000                font-size: 11px;
17001                opacity: 0.9;
17002                font-weight: 400;
17003                display: flex;
17004                align-items: center;
17005                gap: 4px;
17006                line-height: 1.2;
17007            }
17008
17009            .chat-operating-hours svg {
17010                width: 12px;
17011                height: 12px;
17012                opacity: 0.8;
17013                flex-shrink: 0;
17014            }
17015
17016            .chat-header-actions {
17017                display: flex;
17018                align-items: center;
17019                gap: 8px;
17020            }
17021
17022            /* Notification Toggle Button */
17023            .btn-notif-toggle {
17024                background: none;
17025                border: none;
17026                color: white;
17027                font-size: 18px;
17028                cursor: pointer;
17029                padding: 4px 8px;
17030                transition: all 0.2s;
17031                opacity: 0.8;
17032            }
17033
17034            .btn-notif-toggle:hover {
17035                opacity: 1;
17036                transform: scale(1.1);
17037            }
17038
17039            /* Icon states */
17040            .btn-notif-toggle[data-status="not-subscribed"] svg {
17041                opacity: 0.7;
17042            }
17043
17044            .btn-notif-toggle[data-status="not-subscribed"]:hover svg {
17045                opacity: 1;
17046            }
17047
17048            .btn-notif-toggle[data-status="subscribed"] svg {
17049                opacity: 1;
17050                animation: bellRing 2s ease-in-out;
17051            }
17052
17053            .btn-notif-toggle[data-status="blocked"] svg {
17054                color: #ff6b6b;
17055            }
17056
17057            .btn-notif-toggle[data-status="not-supported"] {
17058                display: none !important;
17059            }
17060
17061            @keyframes bellRing {
17062                0%, 100% { transform: rotate(0deg); }
17063                10%, 30% { transform: rotate(-10deg); }
17064                20%, 40% { transform: rotate(10deg); }
17065            }
17066
17067            /* Notification Info Bar */
17068            .chat-notif-info-bar {
17069                position: absolute;
17070                top: 64px;
17071                left: 0;
17072                right: 0;
17073                background: linear-gradient(to right, #e3f2fd, #f5f5f5);
17074                border-bottom: 1px solid #bbdefb;
17075                padding: 8px 12px;
17076                display: flex;
17077                align-items: center;
17078                gap: 8px;
17079                font-size: 12px;
17080                color: #616161;
17081                z-index: 10;
17082                animation: slideDown 0.3s ease;
17083            }
17084
17085            .chat-notif-info-bar svg {
17086                flex-shrink: 0;
17087            }
17088
17089            .chat-notif-info-bar .small {
17090                flex: 1;
17091                line-height: 1.4;
17092            }
17093
17094            .chat-notif-info-bar .btn-dismiss {
17095                background: none;
17096                border: none;
17097                color: #9e9e9e;
17098                cursor: pointer;
17099                padding: 0 4px;
17100                font-size: 16px;
17101                transition: color 0.2s;
17102            }
17103
17104            .chat-notif-info-bar .btn-dismiss:hover {
17105                color: #616161;
17106            }
17107
17108            @keyframes slideDown {
17109                from {
17110                    max-height: 0;
17111                    opacity: 0;
17112                }
17113                to {
17114                    max-height: 50px;
17115                    opacity: 1;
17116                }
17117            }
17118
17119            .chat-close-btn {
17120                background: none;
17121                border: none;
17122                color: white;
17123                font-size: 28px;
17124                line-height: 1;
17125                cursor: pointer;
17126                padding: 0;
17127                width: 30px;
17128                height: 30px;
17129            }
17130
17131            .chat-popup-body {
17132                flex: 1 1 auto;
17133                display: flex;
17134                flex-direction: column;
17135                min-height: 0;
17136                overflow: hidden;
17137                height: calc(100% - 64px);
17138                position: relative;
17139                transition: padding-top 0.3s ease;
17140            }
17141
17142            /* Add padding when info bar is visible */
17143            .chat-popup-body.has-info-bar {
17144                padding-top: 40px;
17145            }
17146
17147            .chat-view {
17148                flex: 1 1 auto;
17149                display: none;
17150                flex-direction: column;
17151                min-height: 0;
17152                overflow: hidden;
17153            }
17154
17155            #chat-list-view {
17156                display: flex;
17157            }
17158
17159            #chat-messages-view {
17160                display: none;
17161            }
17162
17163            #chat-messages-view.active {
17164                display: flex;
17165                flex-direction: column;
17166            }
17167
17168            #chat-new-view {
17169                display: none;
17170            }
17171
17172            #chat-new-view.active {
17173                display: flex;
17174            }
17175
17176            .chat-conversations-list {
17177                flex: 1;
17178                overflow-y: auto;
17179                padding: 0;
17180                background: white;
17181            }
17182
17183            /* Custom Scrollbar for Conversations List */
17184            .chat-conversations-list::-webkit-scrollbar {
17185                width: 6px;
17186            }
17187
17188            .chat-conversations-list::-webkit-scrollbar-track {
17189                background: #f1f1f1;
17190            }
17191
17192            .chat-conversations-list::-webkit-scrollbar-thumb {
17193                background: #F28C1A;
17194                border-radius: 3px;
17195            }
17196
17197            .chat-conversations-list::-webkit-scrollbar-thumb:hover {
17198                background: #D97809;
17199            }
17200
17201            /* Firefox */
17202            .chat-conversations-list {
17203                scrollbar-width: thin;
17204                scrollbar-color: #F28C1A #f1f1f1;
17205            }
17206
17207            .chat-conversation-item {
17208                padding: 12px 16px;
17209                cursor: pointer;
17210                display: flex;
17211                gap: 12px;
17212                align-items: flex-start;
17213                transition: background 0.15s ease;
17214                color: #33371f;
17215                border-bottom: 1px solid #f0f0f0;
17216            }
17217
17218            .chat-conversation-item:hover {
17219                background: #f5f5f5;
17220            }
17221
17222            .chat-conversation-item.active {
17223                background: #FFF5EB;
17224            }
17225
17226            /* WhatsApp-like Avatar */
17227            .conversation-avatar {
17228                width: 48px;
17229                height: 48px;
17230                border-radius: 50%;
17231                background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
17232                flex-shrink: 0;
17233                display: flex;
17234                align-items: center;
17235                justify-content: center;
17236                color: white;
17237                font-weight: 600;
17238                font-size: 18px;
17239            }
17240
17241            .conversation-avatar svg {
17242                width: 24px;
17243                height: 24px;
17244            }
17245
17246            /* WhatsApp-like Content */
17247            .conversation-content {
17248                flex: 1;
17249                min-width: 0;
17250                display: flex;
17251                flex-direction: column;
17252                gap: 4px;
17253            }
17254
17255            .conversation-header {
17256                display: flex;
17257                justify-content: space-between;
17258                align-items: center;
17259                gap: 8px;
17260            }
17261
17262            .conversation-name {
17263                font-weight: 600;
17264                font-size: 15px;
17265                color: #111;
17266                white-space: nowrap;
17267                overflow: hidden;
17268                text-overflow: ellipsis;
17269            }
17270
17271            .conversation-time {
17272                font-size: 12px;
17273                color: #7d8166;
17274                flex-shrink: 0;
17275            }
17276
17277            .conversation-preview {
17278                display: flex;
17279                justify-content: space-between;
17280                align-items: center;
17281                gap: 8px;
17282            }
17283
17284            .conversation-message {
17285                font-size: 14px;
17286                color: #667781;
17287                white-space: nowrap;
17288                overflow: hidden;
17289                text-overflow: ellipsis;
17290                margin: 0;
17291                flex: 1;
17292            }
17293
17294            .conversation-unread {
17295                background: #25D366;
17296                color: white;
17297                border-radius: 12px;
17298                padding: 2px 8px;
17299                font-size: 12px;
17300                font-weight: 600;
17301                min-width: 20px;
17302                text-align: center;
17303                flex-shrink: 0;
17304            }
17305
17306            /* Active item styling */
17307            .chat-conversation-item.active .conversation-name {
17308                color: #F28C1A;
17309            }
17310
17311            .chat-conversation-item.active .conversation-time {
17312                color: #F28C1A;
17313            }
17314
17315            .chat-new-btn {
17316                margin: 12px;
17317                padding: 12px;
17318                background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
17319                color: white;
17320                border: none;
17321                border-radius: 8px;
17322                cursor: pointer;
17323                font-weight: 500;
17324                font-size: 14px;
17325                display: flex;
17326                align-items: center;
17327                justify-content: center;
17328                gap: 8px;
17329                transition: all 0.2s;
17330                flex-shrink: 0;
17331            }
17332
17333            .chat-new-btn:hover {
17334                transform: translateY(-1px);
17335                box-shadow: 0 4px 8px rgba(242, 140, 26, 0.3);
17336            }
17337
17338            .chat-conversation-title {
17339                flex: 1;
17340                font-weight: 600;
17341                font-size: 16px;
17342                color: #1f2937;
17343            }
17344
17345            .chat-messages-container {
17346                flex: 1;
17347                overflow-y: auto;
17348                padding: 16px;
17349                display: flex;
17350                flex-direction: column;
17351                gap: 8px;
17352                min-height: 0;
17353                background: #E5DDD5;
17354            }
17355
17356            /* Custom Scrollbar for Messages Container */
17357            .chat-messages-container::-webkit-scrollbar {
17358                width: 6px;
17359            }
17360
17361            .chat-messages-container::-webkit-scrollbar-track {
17362                background: rgba(0, 0, 0, 0.05);
17363            }
17364
17365            .chat-messages-container::-webkit-scrollbar-thumb {
17366                background: rgba(242, 140, 26, 0.6);
17367                border-radius: 3px;
17368            }
17369
17370            .chat-messages-container::-webkit-scrollbar-thumb:hover {
17371                background: #F28C1A;
17372            }
17373
17374            /* Firefox */
17375            .chat-messages-container {
17376                scrollbar-width: thin;
17377                scrollbar-color: rgba(242, 140, 26, 0.6) rgba(0, 0, 0, 0.05);
17378            }
17379
17380            /* WhatsApp Group-like Messages */
17381            .chat-message {
17382                display: flex;
17383                margin-bottom: 4px;
17384                animation: fadeIn 0.2s ease;
17385            }
17386
17387            @keyframes fadeIn {
17388                from { opacity: 0; transform: translateY(10px); }
17389                to { opacity: 1; transform: translateY(0); }
17390            }
17391
17392            .chat-message.mine {
17393                justify-content: flex-end;
17394            }
17395
17396            .chat-message-bubble {
17397                max-width: 80%;
17398                padding: 6px 8px 8px 10px;
17399                border-radius: 8px;
17400                position: relative;
17401                box-shadow: 0 1px 0.5px rgba(0,0,0,0.13);
17402            }
17403
17404            /* Bubble from others (Admin/CS) */
17405            .chat-message:not(.mine) .chat-message-bubble {
17406                background: #FFFFFF;
17407                border-radius: 0 8px 8px 8px;
17408            }
17409
17410            /* Bubble tail for others */
17411            .chat-message:not(.mine) .chat-message-bubble::before {
17412                content: '';
17413                position: absolute;
17414                top: 0;
17415                left: -8px;
17416                width: 0;
17417                height: 0;
17418                border-style: solid;
17419                border-width: 0 8px 13px 0;
17420                border-color: transparent #FFFFFF transparent transparent;
17421            }
17422
17423            /* Bubble from user (mine) */
17424            .chat-message.mine .chat-message-bubble {
17425                background: #e4f5d0; /* Light blue for user messages */
17426                border-radius: 8px 0 8px 8px;
17427            }
17428
17429            /* Bubble tail for mine */
17430            .chat-message.mine .chat-message-bubble::before {
17431                content: '';
17432                position: absolute;
17433                top: 0;
17434                right: -8px;
17435                width: 0;
17436                height: 0;
17437                border-style: solid;
17438                border-width: 0 0 13px 8px;
17439                border-color: transparent transparent transparent #eaf5d0;
17440            }
17441
17442            .message-sender {
17443                font-size: 12.5px;
17444                font-weight: 600;
17445                color: #F28C1A !important;
17446                margin-bottom: 4px;
17447            }
17448
17449            .chat-message.mine .message-sender {
17450                display: none;
17451            }
17452
17453            /* Message text */
17454            .message-text {
17455                font-size: 14px;
17456                line-height: 19px;
17457                color: #ffffff !important;
17458                word-wrap: break-word;
17459                white-space: pre-wrap;
17460                margin-bottom: 4px;
17461            }
17462
17463            /* Message metadata (time + status) */
17464            .message-meta {
17465                display: flex;
17466                align-items: center;
17467                justify-content: flex-end;
17468                gap: 4px;
17469                margin-top: 2px;
17470            }
17471
17472            .message-time {
17473                font-size: 11px;
17474                color: #728166 !important;
17475                line-height: 15px;
17476            }
17477
17478            /* Use multiple selectors to override global styles */
17479            .chat-message-bubble .message-status,
17480            .message-meta .message-status,
17481            .chat-message .message-status {
17482                width: 16px !important;
17483                height: 16px !important;
17484                min-width: 16px !important;
17485                min-height: 16px !important;
17486                max-width: 16px !important;
17487                max-height: 16px !important;
17488                flex-shrink: 0;
17489                color: #728166 !important;
17490                fill: currentColor !important;
17491            }
17492
17493            .chat-message:not(.mine) .message-status {
17494                display: none;
17495            }
17496
17497            /* Loading dots animation */
17498            @keyframes loadingDots {
17499                0%, 20% { content: '.'; }
17500                40% { content: '..'; }
17501                60%, 100% { content: '...'; }
17502            }
17503
17504            .message-status.loading::after {
17505                content: '...';
17506                animation: loadingDots 1.5s infinite;
17507                font-size: 16px;
17508                line-height: 16px;
17509            }
17510
17511            .message-status.loading path {
17512                display: none;
17513            }
17514
17515            .message-status.failed {
17516                color: #ef4444 !important;
17517                cursor: pointer;
17518            }
17519
17520            .message-status.sent {
17521                color: #667781 !important;
17522            }
17523
17524            .chat-new-form {
17525                flex: 1;
17526                overflow-y: auto;
17527                padding: 16px;
17528                display: flex;
17529                flex-direction: column;
17530            }
17531
17532            .chat-new-form form {
17533                display: flex;
17534                flex-direction: column;
17535                gap: 16px;
17536                flex: 1;
17537            }
17538
17539            .chat-new-form .form-group {
17540                display: flex;
17541                flex-direction: column;
17542                gap: 6px;
17543            }
17544
17545            .chat-new-form label {
17546                font-size: 14px;
17547                font-weight: 500;
17548                color: #374151;
17549            }
17550
17551            .chat-new-form .form-control {
17552                width: 100%;
17553                padding: 10px 12px;
17554                border: 1px solid #d1d5db;
17555                border-radius: 8px;
17556                font-size: 14px;
17557                font-family: inherit;
17558                transition: border-color 0.2s;
17559                box-sizing: border-box;
17560            }
17561
17562            .chat-new-form .form-control:focus {
17563                outline: none;
17564                border-color: #F28C1A;
17565                box-shadow: 0 0 0 3px rgba(242, 140, 26, 0.1);
17566            }
17567
17568            .chat-new-form textarea.form-control {
17569                resize: vertical;
17570                min-height: 100px;
17571            }
17572
17573            .chat-new-form .btn {
17574                padding: 12px 20px;
17575                border: none;
17576                border-radius: 8px;
17577                font-size: 15px;
17578                font-weight: 500;
17579                cursor: pointer;
17580                transition: all 0.2s;
17581                text-align: center;
17582            }
17583
17584            .chat-new-form .btn-primary {
17585                background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
17586                color: white;
17587            }
17588
17589            .chat-new-form .btn-primary:hover {
17590                transform: translateY(-1px);
17591                box-shadow: 0 4px 8px rgba(242, 140, 26, 0.3);
17592            }
17593
17594            .chat-new-form .btn-primary:active {
17595                transform: scale(0.98);
17596            }
17597
17598            .chat-new-form .btn-block {
17599                width: 100%;
17600                margin-top: auto;
17601            }
17602
17603            .chat-message-form {
17604                padding: 12px;
17605                border-top: 1px solid #e5e7eb;
17606                display: flex;
17607                flex-wrap: wrap;
17608                gap: 8px;
17609                align-items: flex-end;
17610                flex-shrink: 0;
17611                background: white;
17612            }
17613
17614            .chat-product-card {
17615                flex: 1 1 100%;
17616                max-width: 100%;
17617                display: flex;
17618                gap: 12px;
17619                padding: 12px;
17620                background: #f3f4f6;
17621                border-radius: 8px;
17622                margin-bottom: 4px;
17623                border: 1px solid #e5e7eb;
17624            }
17625
17626            .chat-product-card img {
17627                width: 60px;
17628                height: 60px;
17629                object-fit: cover;
17630                border-radius: 6px;
17631                flex-shrink: 0;
17632            }
17633
17634            .chat-product-card-content {
17635                flex: 1;
17636                min-width: 0;
17637            }
17638
17639            .chat-product-card-name {
17640                font-weight: 500;
17641                font-size: 14px;
17642                color: #1f2937;
17643                margin-bottom: 4px;
17644                white-space: nowrap;
17645                overflow: hidden;
17646                text-overflow: ellipsis;
17647            }
17648
17649            .chat-product-card-variant {
17650                font-size: 12px;
17651                color: #74806b;
17652                margin-bottom: 4px;
17653            }
17654
17655            .chat-product-card-price {
17656                font-size: 13px;
17657                color: #8bea66;
17658                font-weight: 600;
17659            }
17660
17661            .chat-product-card-remove {
17662                align-self: flex-start;
17663                background: none;
17664                border: none;
17665                color: #9ca3af;
17666                cursor: pointer;
17667                padding: 4px;
17668                font-size: 18px;
17669                line-height: 1;
17670                transition: color 0.2s;
17671            }
17672
17673            .chat-product-card-remove:hover {
17674                color: #ef4444;
17675            }
17676
17677            /* Product card in messages (read-only display) */
17678            .message-product-card {
17679                display: flex;
17680                gap: 10px;
17681                padding: 10px;
17682                background: rgba(255, 255, 255, 0.5);
17683                border-radius: 8px;
17684                margin-bottom: 6px;
17685                border: 1px solid rgba(0, 0, 0, 0.08);
17686                align-items: center;
17687            }
17688
17689            .chat-message.mine .message-product-card {
17690                background: rgba(255, 255, 255, 0.7);
17691            }
17692
17693            .message-product-card img {
17694                width: 50px;
17695                height: 50px;
17696                object-fit: cover;
17697                border-radius: 6px;
17698                flex-shrink: 0;
17699            }
17700
17701            .message-product-info {
17702                flex: 1;
17703                min-width: 0;
17704            }
17705
17706            .message-product-name {
17707                font-weight: 500;
17708                font-size: 13px;
17709                color: #33371f;
17710                margin-bottom: 2px;
17711                overflow: hidden;
17712                text-overflow: ellipsis;
17713                display: -webkit-box;
17714                -webkit-line-clamp: 2;
17715                -webkit-box-orient: vertical;
17716                line-height: 1.3;
17717            }
17718
17719            .message-product-variant {
17720                font-size: 11px;
17721                color: #78806b;
17722                margin-bottom: 2px;
17723            }
17724
17725            .message-product-price {
17726                font-size: 12px;
17727                color: #c7ea66;
17728                font-weight: 600;
17729            }
17730
17731            .message-product-link {
17732                flex-shrink: 0;
17733                width: 32px;
17734                height: 32px;
17735                display: flex;
17736                align-items: center;
17737                justify-content: center;
17738                background: rgba(102, 126, 234, 0.1);
17739                border-radius: 6px;
17740                color: #c7ea66;
17741                text-decoration: none;
17742                transition: all 0.2s;
17743            }
17744
17745            .message-product-link:hover {
17746                background: #b7ea66;
17747                color: white;
17748            }
17749
17750            .message-product-link svg {
17751                width: 16px;
17752                height: 16px;
17753            }
17754
17755            .chat-message-input {
17756                flex: 1;
17757                border: 1px solid #d1d5db;
17758                border-radius: 20px;
17759                padding: 10px 16px;
17760                resize: none;
17761                max-height: 100px;
17762                font-family: inherit;
17763                font-size: 14px;
17764                line-height: 1.5;
17765                box-sizing: border-box;
17766                overflow-y: hidden;
17767            }
17768
17769            .chat-message-input:focus {
17770                outline: none;
17771                border-color: #F28C1A;
17772                box-shadow: 0 0 0 3px rgba(242, 140, 26, 0.1);
17773            }
17774
17775            .chat-send-btn {
17776                width: 40px;
17777                height: 40px;
17778                border-radius: 50%;
17779                background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
17780                border: none;
17781                color: white;
17782                cursor: pointer;
17783                flex-shrink: 0;
17784                display: flex;
17785                align-items: center;
17786                justify-content: center;
17787                transition: all 0.2s;
17788            }
17789
17790            .chat-send-btn:hover {
17791                transform: scale(1.05);
17792                box-shadow: 0 2px 8px rgba(242, 140, 26, 0.3);
17793            }
17794
17795            .chat-send-btn:active {
17796                transform: scale(0.95);
17797            }
17798
17799            .chat-loading {
17800                text-align: center;
17801                padding: 20px;
17802                color: #6b7280;
17803            }
17804
17805            /* Date Separator (WhatsApp-style) */
17806            .date-separator {
17807                display: flex;
17808                align-items: center;
17809                gap: 12px;
17810                margin: 24px 0 16px;
17811                opacity: 0;
17812                animation: fadeInSeparator 0.3s ease forwards;
17813            }
17814
17815            @keyframes fadeInSeparator {
17816                from {
17817                    opacity: 0;
17818                    transform: translateY(-10px);
17819                }
17820                to {
17821                    opacity: 1;
17822                    transform: translateY(0);
17823                }
17824            }
17825
17826            .date-separator-line {
17827                flex: 1;
17828                height: 1px;
17829                background: linear-gradient(90deg, transparent, rgba(0, 0, 0, 0.1) 50%, transparent);
17830            }
17831
17832            .date-separator-text {
17833                flex-shrink: 0;
17834                padding: 4px 12px;
17835                background: #e9ecef;
17836                color: #6c757d;
17837                font-size: 11px;
17838                font-weight: 600;
17839                text-transform: uppercase;
17840                letter-spacing: 0.5px;
17841                border-radius: 12px;
17842                box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
17843            }
17844
17845            /* Mobile responsive */
17846            @media (max-width: 640px) {
17847                .chat-popup {
17848                    position: fixed !important; /* Override absolute positioning */
17849                    bottom: 45px !important; /* Above bottom nav */
17850                    right: 0 !important;
17851                    left: 0 !important;
17852                    width: 100% !important;
17853                    max-width: 100% !important;
17854                    /* Use dvh (dynamic viewport height) for browser UI awareness */
17855                    height: calc(100dvh - 45px) !important;
17856                    max-height: calc(100dvh - 45px) !important;
17857                    /* Fallback for browsers that don't support dvh */
17858                    height: calc(var(--viewport-height, 100vh) - 45px) !important;
17859                    max-height: calc(var(--viewport-height, 100vh) - 45px) !important;
17860                    border-radius: 0 !important;
17861                    z-index: 9999 !important;
17862                }
17863
17864                .chat-popup-header {
17865                    border-radius: 0;
17866                }
17867
17868                .chat-messages-container,
17869                .chat-conversations-list {
17870                    /* Adjust for mobile keyboard using dynamic viewport */
17871                    max-height: calc(100dvh - 190px);
17872                    /* Fallback for browsers that don't support dvh */
17873                    max-height: calc(var(--viewport-height, 100vh) - 190px);
17874                }
17875            }
17876        </style>
17877                <!-- Chat Widget Button -->
17878        <div id="chat-widget" class="chat-widget" 
17879             data-user-id=""
17880             data-is-logged-in="false"
17881             data-csrf-token="df5d776748b1ed8c8dd0d987bae99b1ee3dea6c6dcbe0eedb1bea766c56d569a">
17882            <button id="chat-toggle-btn" class="chat-toggle-btn" aria-label="Toggle chat">
17883                <svg class="chat-icon" width="28" height="28" viewBox="0 0 640 640" fill="currentColor">
17884                    <path d="M320 128C241 128 175.3 185.3 162.3 260.7C171.6 257.7 181.6 256 192 256L208 256C234.5 256 256 277.5 256 304L256 400C256 426.5 234.5 448 208 448L192 448C139 448 96 405 96 352L96 288C96 164.3 196.3 64 320 64C443.7 64 544 164.3 544 288L544 456.1C544 522.4 490.2 576.1 423.9 576.1L336 576L304 576C277.5 576 256 554.5 256 528C256 501.5 277.5 480 304 480L336 480C362.5 480 384 501.5 384 528L384 528L424 528C463.8 528 496 495.8 496 456L496 435.1C481.9 443.3 465.5 447.9 448 447.9L432 447.9C405.5 447.9 384 426.4 384 399.9L384 303.9C384 277.4 405.5 255.9 432 255.9L448 255.9C458.4 255.9 468.3 257.5 477.7 260.6C464.7 185.3 399.1 127.9 320 127.9z"/>
17885                </svg>
17886                <span id="chat-unread-badge" class="chat-unread-badge" style="display: none;">0</span>
17887            </button>
17888
17889            <div id="chat-popup" class="chat-popup" style="display: none;">
17890                <div class="chat-popup-header">
17891                    <div class="chat-header-title">
17892                        <h3>Bantuan</h3>
17893                        <span id="chatOperatingHours" class="chat-operating-hours" style="display: none;"></span>
17894                    </div>
17895                    <div class="chat-header-actions">
17896                        <button id="chatNotifToggle" class="btn-notif-toggle" data-status="not-supported" title="Push notification" style="display: none;">
17897                            <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
17898                                <path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"/>
17899                            </svg>
17900                        </button>
17901                        <button id="chat-close-btn" class="chat-close-btn" aria-label="Close chat">
17901×</button>
17902                    </div>
17903                </div>
17904
17905                <!-- Notification Info Bar (conditional) -->
17906                <div class="chat-notif-info-bar" id="chatNotifInfoBar" style="display:none;">
17907                    <svg width="14" height="14" viewBox="0 0 24 24" fill="#ffc107">
17908                        <path d="M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6C7.8 12.16 7 10.63 7 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z"/>
17909                    </svg>
17910                    <span class="small">Aktifkan notifikasi untuk pemberitahuan balasan</span>
17911                    <button class="btn-dismiss" onclick="ChatNotification.dismissInfo()">✕</button>
17912                </div>
17913
17914                <div class="chat-popup-body">
17915                    <!-- Conversations List View -->
17916                    <div id="chat-list-view" class="chat-view">
17917                        <div id="chat-conversations-list" class="chat-conversations-list">
17918                            <div class="chat-loading">Memuat...</div>
17919                            <!-- Conversation Item Structure (rendered by JS):
17920                            <div class="chat-conversation-item">
17921                                <div class="conversation-avatar">
17922                                    <svg>...</svg>
17923                                </div>
17924                                <div class="conversation-content">
17925                                    <div class="conversation-header">
17926                                        <span class="conversation-name">Admin Support</span>
17927                                        <span class="conversation-time">10:30</span>
17928                                    </div>
17929                                    <div class="conversation-preview">
17930                                        <p class="conversation-message">Terima kasih telah menghubungi kami...</p>
17931                                        <span class="conversation-unread">2</span>
17932                                    </div>
17933                                </div>
17934                            </div>
17935                            -->
17936                        </div>
17937                        <button id="chat-new-btn" class="chat-new-btn">
17938                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
17939                                <line x1="12" y1="5" x2="12" y2="19"></line>
17940                                <line x1="5" y1="12" x2="19" y2="12"></line>
17941                            </svg>
17942                            Percakapan Baru
17943                        </button>
17944                    </div>
17945
17946                    <!-- Chat Messages View -->
17947                    <div id="chat-messages-view" class="chat-view" style="display: none;">
17948                        <div id="chat-messages-container" class="chat-messages-container">
17949                            <div class="chat-loading">Memuat pesan...</div>
17950                            <!-- Message Structure (rendered by JS):
17951                            <div class="chat-message"> (or .chat-message.mine)
17952                                <div class="chat-message-bubble">
17953                                    <div class="message-sender">Admin Support</div> (only for non-mine)
17954                                    <div class="message-text">Halo, ada yang bisa dibantu?</div>
17955                                    <div class="message-meta">
17956                                        <span class="message-time">10:30</span>
17957                                        <svg class="message-status">...</svg> (only for mine)
17958                                    </div>
17959                                </div>
17960                            </div>
17961                            -->
17962                        </div>
17963
17964                        <form id="chat-message-form" class="chat-message-form">
17965                            <input type="hidden" id="chat-current-conversation-id" value="">
17966                            <textarea 
17967                                id="chat-message-input" 
17968                                class="chat-message-input" 
17969                                placeholder="Ketik pesan..."
17970                                rows="1"
17971                                maxlength="5000"
17972                            ></textarea>
17973                            <button type="submit" class="chat-send-btn" aria-label="Send message">
17974                                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
17975                                    <line x1="22" y1="2" x2="11" y2="
1797513"></line>
17976                                    <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
17977                                </svg>
17978                            </button>
17979                        </form>
17980                    </div>
17981
17982                    <!-- New Chat View -->
17983                    <div id="chat-new-view" class="chat-view" style="display: none;">
17984                        <div class="chat-messages-header">
17985                            <button id="chat-new-back-btn" class="chat-back-btn" aria-label="Back to list">
17986                                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
17987                                    <path d="M19 12H5M12 19l-7-7 7-7"/>
17988                                </svg>
17989                            </button>
17990                            <div class="chat-conversation-title">Percakapan Baru</div>
17991                        </div>
17992
17993                        <div class="chat-new-form">
17994                            <form id="chat-start-form">
17995                                <div class="form-group">
17996                                    <label for="chat-new-subject">Subjek</label>
17997                                    <input 
17998                                        type="text" 
17999                                        id="chat-new-subject" 
18000                                        class="form-control" 
18001                                        placeholder="Bantuan Pelanggan"
18002                                        maxlength="255"
18003                                    >
18004                                </div>
18005                                <div class="form-group">
18006                                    <label for="chat-new-message">Pesan</label>
18007                                    <textarea 
18008                                        id="chat-new-message" 
18009                                        class="form-control" 
18010                                        rows="4" 
18011                                        placeholder="Tulis pertanyaan Anda..."
18012                                        required
18013                                        maxlength="5000"
18014                                    ></textarea>
18015                                </div>
18016                                <button type="submit" class="btn btn-primary btn-block">Kirim</button>
18017                            </form>
18018                        </div>
18019                    </div>
18020                </div>
18021            </div>
18022        </div>
18023
18024        <!-- Login Required Modal -->
18025        <div class="modal-overlay" id="chatLoginModal">
18026            <div class="modal-dialog">
18027                <div class="modal-header">
18028                    <h3 class="modal-title">
18029                        <div class="modal-icon info">
18030                            <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18031                                <path d="M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M11,17H13V11H11V17Z"/>
18032                            </svg>
18033                        </div>
18034                        Login Diperlukan
18035                    </h3>
18036                </div>
18037                <div class="modal-body">
18038                    <p class="modal-message">Anda harus login terlebih dahulu untuk menggunakan fitur chat bantuan pelanggan.</p>
18039                    <div class="modal-actions">
18040                        <button class="modal-btn modal-btn-secondary" id="chatLoginCancel">Batal</button>
18041                        <button class="modal-btn modal-btn-primary" id="chatLoginConfirm">Login Sekarang</button>
18042                    </div>
18043                </div>
18044            </div>
18045        </div>
18046        
18046<script src="https://plazait.co.id/assets/js/chat-widget.js" nonce="se3iyzEHMc9FQEB4wSyd0w=="></script>
18046<script data-inlined="chat-notification.js">
18047/**
18048 * Customer Chat Push Notification Manager
18049 * Handles browser push notification subscription for customers
18050 * Version: 1.0.0
18051 * Date: 2026-01-19
18052 */
18053
18054const ChatNotification = {
18055    // State
18056    status: 'not-supported', // not-supported, not-subscribed, subscribed, blocked
18057    subscription: null,
18058    vapidPublicKey: null, // Will be loaded from server
18059
18060    /**
18061     * Initialize notification manager
18062     */
18063    async init() {
18064        // Check if user is logged in (only for logged-in users)
18065        if (!this.isUserLoggedIn()) {
18066            this.updateUI('not-supported');
18067            return;
18068        }
18069
18070        // Check browser support
18071        if (!this.isSupported()) {
18072            this.updateUI('not-supported');
18073            return;
18074        }
18075
18076        // Load VAPID key from server
18077        await this.loadVapidKey();
18078
18079        if (!this.vapidPublicKey) {
18080            this.updateUI('not-supported');
18081            return;
18082        }
18083
18084        // Register service worker if needed
18085        await this.registerServiceWorker();
18086
18087        // Check current permission
18088        const permission = Notification.permission;
18089
18090        if (permission === 'denied') {
18091            this.updateUI('blocked');
18092        } else if (permission === 'granted') {
18093            // Check if already subscribed
18094            const isSubscribed = await this.checkSubscriptionStatus();
18095            this.updateUI(isSubscribed ? 'subscribed' : 'not-subscribed');
18096        } else {
18097            // Default: not subscribed yet
18098            this.updateUI('not-subscribed');
18099            this.showInfoBar();
18100        }
18101    },
18102
18103    /**
18104     * Check if user is logged in
18105     */
18106    isUserLoggedIn() {
18107        const metaTag = document.querySelector('meta[name="user-logged-in"]');
18108        return metaTag ? metaTag.getAttribute('content') === 'true' : false;
18109    },
18110
18111    /**
18112     * Check if browser supports push notifications
18113     */
18114    isSupported() {
18115        return (
18116            'serviceWorker' in navigator &&
18117            'PushManager' in window &&
18118            'Notification' in window
18119        );
18120    },
18121
18122    /**
18123     * Load VAPID public key from server
18124     */
18125    async loadVapidKey() {
18126        try {
18127            const response = await fetch('/api/chat/notifications/vapid_key', {
18128                method: 'GET',
18129                headers: {
18130                    'Accept': 'application/json'
18131                }
18132            });
18133
18134            if (!response.ok) {
18135                return false;
18136            }
18137
18138            const data = await response.json();
18139            if (data.success && data.data?.public_key) {
18140                this.vapidPublicKey = data.data.public_key;
18141                return true;
18142            }
18143
18144            return false;
18145
18146        } catch (error) {
18147            console.error('[ChatNotification] Error loading VAPID key:', error);
18148            return false;
18149        }
18150    },
18151
18152    /**
18153     * Register service worker
18154     */
18155    async registerServiceWorker() {
18156        if (!('serviceWorker' in navigator)) return null;
18157
18158        try {
18159            const registration = await navigator.serviceWorker.register('/sw-push.js', {
18160                scope: '/'
18161            });
18162            return registration;
18163        } catch (error) {
18164            return null;
18165        }
18166    },
18167
18168    /**
18169     * Check subscription status from server
18170     */
18171    async checkSubscriptionStatus() {
18172        try {
18173            const response = await fetch('/api/chat/notifications/status', {
18174                method: 'GET',
18175                credentials: 'include',
18176                headers: {
18177                    'Accept': 'application/json'
18178                }
18179            });
18180
18181            if (!response.ok) {
18182                return false;
18183            }
18184
18185            const data = await response.json();
18186            return data.data?.subscribed || false;
18187
18188        } catch (error) {
18189            console.error('[ChatNotification] Error checking subscription:', error);
18190            return false;
18191        }
18192    },
18193
18194    /**
18195     * Update UI based on status
18196     */
18197    updateUI(status) {
18198        this.status = status;
18199        const btn = document.getElementById('chatNotifToggle');
18200
18201        if (!btn) {
18202            // Retry after a short delay (button might not be in DOM yet)
18203            setTimeout(() => this.updateUI(status), 100);
18204            return;
18205        }
18206
18207        btn.setAttribute('data-status', status);
18208
18209        // Show/hide button based on support
18210        if (status === 'not-supported') {
18211            btn.style.display = 'none';
18212        } else {
18213            btn.style.display = 'block';
18214        }
18215
18216        // Update SVG icon (bell vs bell-slash)
18217        const svg = btn.querySelector('svg');
18218        if (svg) {
18219            if (status === 'subscribed') {
18220                // Bell icon (active)
18221                svg.innerHTML = '<path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"/>';
18222            } else {
18223                // Bell slash icon (inactive/blocked)
18224                svg.innerHTML = '<path d="M20 18.69L7.84 6.14 5.27 3.49 4 4.76l2.8 2.8v.01c-.52.99-.8 2.16-.8 3.42v5l-2 2v1h13.73l2 2L21 19.72l-1-1.03zM12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-7.32V11c0-3.08-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68c-.15.03-.29.08-.42.12-.1.03-.2.07-.3.11h-.01c-.01 0-.01 0-.02.01-.23.09-.46.2-.68.31 0 0-.01 0-.01.01L18 14.68z"/>';
18225            }
18226        }
18227
18228        // Update tooltip
18229        const tooltips = {
18230            'not-subscribed': 'Klik untuk aktifkan notifikasi',
18231            'subscribed': 'Notifikasi aktif',
18232            'blocked': 'Notifikasi diblokir'
18233        };
18234
18235        if (tooltips[status]) {
18236            btn.title = tooltips[status];
18237        }
18238
18239        // Hide info bar if subscribed or blocked
18240        if (status === 'subscribed' || status === 'blocked') {
18241            this.hideInfoBar();
18242        }
18243    },
18244
18245    /**
18246     * Show info bar (if not dismissed)
18247     */
18248    showInfoBar() {
18249        if (localStorage.getItem('chat_notif_info_dismissed')) {
18250            return;
18251        }
18252
18253        const infoBar = document.getElementById('chatNotifInfoBar');
18254        const popupBody = document.getElementById('chat-popup')?.querySelector('.chat-popup-body');
18255
18256        if (infoBar) {
18257            infoBar.style.display = 'flex';
18258
18259            // Bind dismiss button here so it's always attached when bar is visible
18260            const btn = infoBar.querySelector('#chatNotifDismissBtn');
18261            if (btn && !btn._dismissBound) {
18262                btn._dismissBound = true;
18263                btn.addEventListener('click', () => {
18264                    infoBar.style.display = 'none';
18265                    localStorage.setItem('chat_notif_info_dismissed', '1');
18266                    if (popupBody) popupBody.classList.remove('has-info-bar');
18267                });
18268            }
18269        }
18270
18271        if (popupBody) {
18272            popupBody.classList.add('has-info-bar');
18273        }
18274    },
18275
18276    /**
18277     * Hide info bar
18278     */
18279    hideInfoBar() {
18280        const infoBar = document.getElementById('chatNotifInfoBar');
18281        const popupBody = document.getElementById('chat-popup')?.querySelector('.chat-popup-body');
18282
18283        if (infoBar) {
18284            infoBar.style.display = 'none';
18285        }
18286
18287        if (popupBody) {
18288            popupBody.classList.remove('has-info-bar');
18289        }
18290    },
18291
18292    /**
18293     * Dismiss info bar permanently
18294     */
18295    dismissInfo() {
18296        this.hideInfoBar();
18297        localStorage.setItem('chat_notif_info_dismissed', '1');
18298    },
18299
18300    /**
18301     * Handle bell icon click
18302     */
18303    async handleToggleClick() {
18304        if (this.status === 'not-subscribed') {
18305            await this.subscribe();
18306        } else if (this.status === 'subscribed') {
18307            await this.unsubscribe();
18308        } else if (this.status === 'blocked') {
18309            this.showUnblockHelp();
18310        }
18311    },
18312
18313    /**
18314     * Subscribe to push notifications
18315     */
18316    async subscribe() {
18317        try {
18318            // Request permission
18319            const permission = await Notification.requestPermission();
18320
18321            if (permission === 'denied') {
18322                this.updateUI('blocked');
18323                this.showToast('Notifikasi diblokir', 'error');
18324                return;
18325            }
18326
18327            if (permission !== 'granted') {
18328                return;
18329            }
18330
18331            // Get service worker registration
18332            const registration = await navigator.serviceWorker.ready;
18333
18334            // Subscribe to push
18335            const subscription = await registration.pushManager.subscribe({
18336                userVisibleOnly: true,
18337                applicationServerKey: this.urlBase64ToUint8Array(this.vapidPublicKey)
18338            });
18339
18340            // Send subscription to server
18341            const response = await fetch('/api/chat/notifications/subscribe', {
18342                method: 'POST',
18343                credentials: 'include',
18344                headers: {
18345                    'Content-Type': 'application/json',
18346                    'Accept': 'application/json'
18347                },
18348                body: JSON.stringify(subscription.toJSON())
18349            });
18350
18351            if (!response.ok) {
18352                throw new Error('Failed to save subscription');
18353            }
18354
18355            const result = await response.json();
18356
18357            this.subscription = subscription;
18358            this.updateUI('subscribed');
18359            this.hideInfoBar();
18360            this.showToast('Notifikasi berhasil diaktifkan!', 'success');
18361
18362            // Send test notification
18363            setTimeout(() => this.sendTestNotification(), 1000);
18364
18365        } catch (error) {
18366            console.error('[ChatNotification] Subscribe error:', error);
18367            this.showToast('Gagal mengaktifkan notifikasi', 'error');
18368        }
18369    },
18370
18371    /**
18372     * Unsubscribe from push notifications
18373     */
18374    async unsubscribe() {
18375        try {
18376            // Confirm first
18377            if (!confirm('Nonaktifkan notifikasi chat?')) {
18378                return;
18379            }
18380
18381            // Get current subscription
18382            const registration = await navigator.serviceWorker.ready;
18383            const subscription = await registration.pushManager.getSubscription();
18384
18385            if (subscription) {
18386                // Unsubscribe from browser
18387                await subscription.unsubscribe();
18388
18389                // Tell server
18390                await fetch('/api/chat/notifications/unsubscribe', {
18391                    method: 'POST',
18392                    credentials: 'include',
18393                    headers: {
18394                        'Content-Type': 'application/json',
18395                        'Accept': 'application/json'
18396                    },
18397                    body: JSON.stringify(subscription.toJSON())
18398                });
18399            }
18400
18401            this.subscription = null;
18402            this.updateUI('not-subscribed');
18403            this.showInfoBar();
18404            this.showToast('Notifikasi dinonaktifkan', 'info');
18405
18406        } catch (error) {
18407            console.error('[ChatNotification] Unsubscribe error:', error);
18408            this.showToast('Gagal menonaktifkan notifikasi', 'error');
18409        }
18410    },
18411
18412    /**
18413     * Send test notification
18414     */
18415    async sendTestNotification() {
18416        try {
18417            const response = await fetch('/api/chat/notifications/send_test', {
18418                method: 'POST',
18419                credentials: 'include',
18420                headers: {
18421                    'Accept': 'application/json'
18422                }
18423            });
18424
18425            if (response.ok) {
18426                // Test notification sent successfully
18427            }
18428        } catch (error) {
18429            // Test notification failed silently
18430        }
18431    },
18432
18433    /**
18434     * Show unblock help modal/message
18435     */
18436    showUnblockHelp() {
18437        const message = `
18438            Notifikasi diblokir oleh browser.nn
18439            Untuk mengaktifkan:n
18440            1. Klik ikon gembok/info di address barn
18441            2. Cari "Notifications" atau "Notifikasi"n
18442            3. Ubah menjadi "Allow" atau "Izinkan"n
18443            4. Refresh halaman ini
18444        `.trim().replace(/s+/g, ' ');
18445
18446        alert(message);
18447    },
18448
18449    /**
18450     * Show toast notification
18451     */
18452    showToast(message, type = 'info') {
18453        // Simple toast implementation
18454        const toast = document.createElement('div');
18455        toast.className = `chat-toast chat-toast-${type}`;
18456        toast.textContent = message;
18457        toast.style.cssText = `
18458            position: fixed;
18459            bottom: 80px;
18460            right: 20px;
18461            background: ${type === 'success' ? '#4caf50' : type === 'error' ? '#f44336' : '#2196f3'};
18462            color: white;
18463            padding: 12px 20px;
18464            border-radius: 4px;
18465            box-shadow: 0 2px 8px rgba(0,0,0,0.2);
18466            z-index: 10000;
18467            animation: slideInUp 0.3s ease;
18468        `;
18469
18470        document.body.appendChild(toast);
18471
18472        setTimeout(() => {
18473            toast.style.animation = 'slideOutDown 0.3s ease';
18474            setTimeout(() => toast.remove(), 300);
18475        }, 3000);
18476    },
18477
18478    /**
18479     * Convert VAPID key to Uint8Array
18480     */
18481    urlBase64ToUint8Array(base64String) {
18482        const padding = '='.repeat((4 - base64String.length % 4) % 4);
18483        const base64 = (base64String + padding)
18484            .replace(/-/g, '+')
18485            .replace(/_/g, '/');
18486
18487        const rawData = window.atob(base64);
18488        const outputArray = new Uint8Array(rawData.length);
18489
18490        for (let i = 0; i < rawData.length; ++i) {
18491            outputArray[i] = rawData.charCodeAt(i);
18492        }
18493        return outputArray;
18494    },
18495
18496    /**
18497     * Check if push notification is currently active
18498     * @returns {Promise<boolean>}
18499     */
18500    async isPushActive() {
18501        try {
18502            // Check browser subscription
18503            if (!('serviceWorker' in navigator)) return false;
18504
18505            const registration = await navigator.serviceWorker.getRegistration();
18506            if (!registration) return false;
18507
18508            const subscription = await registration.pushManager.getSubscription();
18509            if (!subscription) return false;
18510
18511            // Also check server status
18512            const serverStatus = await this.checkSubscriptionStatus();
18513
18514            return serverStatus;
18515        } catch (error) {
18516            return false;
18517        }
18518    },
18519
18520    /**
18521     * Show logout confirmation if push notification is active
18522     * Call this before logout action
18523     * @returns {Promise<boolean>} true if user confirms logout, false if cancelled
18524     */
18525    async confirmLogoutWithPushNotification() {
18526        const isActive = await this.isPushActive();
18527
18528        if (!isActive) {
18529            // No active push, proceed with logout
18530            return true;
18531        }
18532
18533        // Show modal confirmation using modal.css
18534        return new Promise((resolve) => {
18535            // Create modal HTML
18536            const modalHTML = `
18537                <div class="modal-overlay" id="logoutConfirmModal">
18538                    <div class="modal-dialog">
18539                        <div class="modal-header">
18540                            <h3 class="modal-title">
18541                                <span class="modal-icon warning">⚠️</span>
18542                                Konfirmasi Logout
18543                            </h3>
18544                        </div>
18545                        <div class="modal-body">
18546                            <p class="modal-message">
18547                                Anda memiliki <strong>notifikasi push yang aktif</strong>.<br><br>
18548                                Setelah logout, Anda tidak akan menerima notifikasi chat lagi di perangkat ini.<br><br>
18549                                Apakah Anda yakin ingin melanjutkan logout?
18550                            </p>
18551                        </div>
18552                        <div class="modal-actions" style="padding: 0 20px 20px;">
18553                            <button class="modal-btn modal-btn-secondary" id="logoutCancelBtn">
18554                                Batal
18555                            </button>
18556                            <button class="modal-btn modal-btn-danger" id="logoutConfirmBtn">
18557                                Lanjutkan Logout
18558                            </button>
18559                        </div>
18560                    </div>
18561                </div>
18562            `;
18563
18564            // Append to body
18565            const modalContainer = document.createElement('div');
18566            modalContainer.innerHTML = modalHTML;
18567            document.body.appendChild(modalContainer.firstElementChild);
18568
18569            const modal = document.getElementById('logoutConfirmModal');
18570            const confirmBtn = document.getElementById('logoutConfirmBtn');
18571            const cancelBtn = document.getElementById('logoutCancelBtn');
18572
18573            // Show modal with animation
18574            requestAnimationFrame(() => {
18575                modal.classList.add('show');
18576            });
18577
18578            // Handle confirm
18579            const handleConfirm = () => {
18580                cleanup();
18581                resolve(true);
18582            };
18583
18584            // Handle cancel
18585            const handleCancel = () => {
18586                cleanup();
18587                resolve(false);
18588            };
18589
18590            // Cleanup function
18591            const cleanup = () => {
18592                modal.classList.remove('show');
18593                setTimeout(() => {
18594                    modal.remove();
18595                }, 200);
18596                confirmBtn.removeEventListener('click', handleConfirm);
18597                cancelBtn.removeEventListener('click', handleCancel);
18598                modal.removeEventListener('click', handleBackdropClick);
18599            };
18600
18601            // Handle backdrop click
18602            const handleBackdropClick = (e) => {
18603                if (e.target === modal) {
18604                    handleCancel();
18605                }
18606            };
18607
18608            // Add event listeners
18609            confirmBtn.addEventListener('click', handleConfirm);
18610            cancelBtn.addEventListener('click', handleCancel);
18611            modal.addEventListener('click', handleBackdropClick);
18612        });
18613    }
18614};
18615
18616// Add CSS animations
18617const chatNotifStyle = document.createElement('style');
18618chatNotifStyle.textContent = `
18619    @keyframes slideInUp {
18620        from {
18621            transform: translateY(100%);
18622            opacity: 0;
18623        }
18624        to {
18625            transform: translateY(0);
18626            opacity: 1;
18627        }
18628    }
18629
18630    @keyframes slideOutDown {
18631        from {
18632            transform: translateY(0);
18633            opacity: 1;
18634        }
18635        to {
18636            transform: translateY(100%);
18637            opacity: 0;
18638        }
18639    }
18640`;
18641document.head.appendChild(chatNotifStyle);
18642
18643// Auto-initialize when DOM is ready
18644if (document.readyState === 'loading') {
18645    document.addEventListener('DOMContentLoaded', () => ChatNotification.init());
18646} else {
18647    ChatNotification.init();
18648}
18649
18650// Expose to window for access from other scripts
18651window.ChatNotification = ChatNotification;
18652
18653</script>
18653  
18654    <!-- Footer Collapse Toggle Script -->
18655  
18655<script nonce="se3iyzEHMc9FQEB4wSyd0w==">
18656  document.addEventListener('DOMContentLoaded', function() {
18657    const ftTop = document.getElementById('ftTop');
18658    const expandBtn = document.getElementById('ftExpandBtn');
18659
18660    if (ftTop && expandBtn) {
18661      const expandText = expandBtn.querySelector('.expand-text');
18662
18663      expandBtn.addEventListener('click', function() {
18664        ftTop.classList.toggle('expanded');
18665        const isExpanded = ftTop.classList.contains('expanded');
18666        if (expandText) {
18667          expandText.textContent = isExpanded ? 'Sembunyikan' : 'Selengkapnya';
18668        }
18669      });
18670    }
18671  });
18672  </script>
18672
18673  </footer>
18674
18675<!-- Compare Sidebar Overlay -->
18676<div class="compare-overlay" id="compareOverlay"></div>
18677
18678<!-- Floating Compare Badge (Sticky) -->
18679<button class="compare-floating-badge" id="compareFloatingBadge" aria-label="Lihat produk yang dibandingkan" style="display:none;">
18680  <svg class="cfb-icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
18681    <path d="M9.01 14H2v2h7.01v3L13 15l-3.99-4v3zm5.98-1v-3H22V8h-7.01V5L11 9l3.99 4z"/>
18682  </svg>
18683  <span class="cfb-text">Bandingkan</span>
18684  <span class="cfb-count" id="cfbCount">0</span>
18685</button>
18686
18687<!-- AI Comparison Modal -->
18688<div class="ai-compare-modal" id="aiCompareModal">
18689  <div class="modal-backdrop"></div>
18690  <div class="modal-container">
18691    <div class="modal-header">
18692      <h3 class="modal-title">
18693        <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
18694          <path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4M11,16.5L18,9.5L16.59,8.09L11,13.67L7.91,10.59L6.5,12L11,16.5Z"/>
18695        </svg>
18696        Analisis AI: Perbandingan Panduan Shio
18697      </h3>
18698      <button class="modal-close" id="aiCompareModalClose" aria-label="Tutup">
18699        <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18700          <path d="M18.3 5.7a1 1 0 0 0-1.4 0L12 10.6 7.1 5.7a1 1 0 1 0-1.4 1.4L10.6 12l-4.9 4.9a1 1 0 0 0 1.4 1.4L12 13.4l4.9 4.9a1 1 0 0 0 1.4-1.4L13.4 12l4.9-4.9a1 1 0 0 0 0-1.4z"/>
18701        </svg>
18702      </button>
18703    </div>
18704
18705    <div class="modal-content">
18706      <!-- Loading State -->
18707      <div class="ai-loading" id="aiLoading">
18708        <div class="loading-animation">
18709          <div class="typing-indicator">
18710            <span></span>
18711            <span></span>
18712            <span></span>
18713          </div>
18714          <p>AI sedang membandingkan panduan shio...</p>
18715          <small>Proses ini membutuhkan waktu 10-30 detik</small>
18716        </div>
18717      </div>
18718
18719      <!-- Error State -->
18720      <div class="ai-error" id="aiError" hidden>
18721        <div class="error-icon">
18722          <svg width="48" height="48" viewBox="0 0 24 24" fill="#ef4444">
18723            <path d="M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z"/>
18724          </svg>
18725        </div>
18726        <h4>Analisis AI Gagal</h4>
18727        <p id="aiErrorMessage">Terjadi kesalahan saat memproses analisis.</p>
18728        <button class="btn btn-outline-primary" id="aiRetry">Coba Lagi</button>
18729      </div>
18730
18731      <!-- Analysis Results -->
18732      <div class="ai-results" id="aiResults" hidden>
18733        <!-- Summary Section -->
18734        <div class="ai-summary-card">
18735          <h4>
18736            <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18737              <path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M11,16.5L6.5,12L7.91,10.59L11,13.67L16.59,8.09L18,9.5L11,16.5Z"/>
18738            </svg>
18739            Ringkasan Analisis
18740          </h4>
18741          <p id="aiSummary" class="summary-text"></p>
18742          <div class="winner-badge" id="winnerBadge">
18743            <strong>Rekomendasi Terbaik:</strong> <span id="winnerProduct"></span>
18744            <small id="winnerReason"></small>
18745          </div>
18746        </div>
18747
18748        <!-- Comparison Table -->
18749        <div class="ai-comparison-table">
18750          <h4>
18751            <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18752              <path d="M3,3H21V5H3V3M3,7H21V9H3V7M3,11H21V13H3V11M3,15H21V17H3V15M3,19H21V21H3V19Z"/>
18753            </svg>
18754            Perbandingan Detail
18755          </h4>
18756          <div id="comparisonTable" class="comparison-grid"></div>
18757        </div>
18758
18759        <!-- Detailed Analysis -->
18760        <div class="ai-detailed-analysis">
18761          <h4>
18762            <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18763              <path d="M14,2A8,8 0 0,1 22,10A8,8 0 0,1 14,18A8,8 0 0,1 6,10A8,8 0 0,1 14,2M14,4A6,6 0 0,0 8,10A6,6 0 0,0 14,16A6,6 0 0,0 20,10A6,6 0 0,0 14,4M14,6A4,4 0 0,1 18,10A4,4 0 0,1 14,14A4,4 0 0,1 10,10A4,4 0 0,1 14,6M14,8A2,2 0 0,0 12,10A2,2 0 0,0 14,12A2,2 0 0,0 16,10A2,2 0 0,0 14,8Z"/>
18764            </svg>
18765            Analisis Mendalam
18766          </h4>
18767          <div id="detailedAnalysis" class="products-analysis"></div>
18768        </div>
18769
18770        <!-- Recommendations -->
18771        <div class="ai-recommendations">
18772          <h4>
18773            <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18774              <path d="M12,17.27L18.18,21L16.54,13.97L22,9.24L14.81,8.62L12,2L9.19,8.62L2,9.24L7.45,13.97L5.82,21L12,17.27Z"/>
18775            </svg>
18776            Rekomendasi Berdasarkan Kebutuhan
18777          </h4>
18778          <div class="recommendations-grid">
18779            <div class="recommendation-card">
18780              <h5>💰 Budget Conscious</h5>
18781              <p id="budgetRecommendation"></p>
18782            </div>
18783            <div class="recommendation-card">
18784              <h5>⚡ Performance Focused</h5>
18785              <p id="performanceRecommendation"></p>
18786            </div>
18787            <div class="recommendation-card">
18788              <h5>⚖️ Balanced Choice</h5>
18789              <p id="balancedRecommendation"></p>
18790            </div>
18791          </div>
18792        </div>
18793
18794        <!-- Buying Tips -->
18795        <div class="ai-tips">
18796          <h4>
18797            <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18798              <path d="M11,18H13V16H11V18M12,6A4,4 0 0,0 8,10H10A2,2 0 0,1 12,8A2,2 0 0,1 14,10C14,12 11,11.75 11,15H13C13,12.75 16,12.5 16,10A4,4 0 0,0 12,6
18798M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3Z"/>
18799            </svg>
18800            Tips Pembelian
18801          </h4>
18802          <ul id="buyingTips" class="tips-list"></ul>
18803        </div>
18804      </div>
18805    </div>
18806  </div>
18807</div>
18808
18809<!-- Cart Login Modal -->
18810<div class="modal-overlay" id="cartLoginModal">
18811  <div class="modal-dialog">
18812    <div class="modal-header">
18813      <h3 class="modal-title">
18814        <div class="modal-icon info">
18815          <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18816            <path d="M17,18A2,2 0 0,1 19,20A2,2 0 0,1 17,22C15.89,22 15,21.1 15,20C15,18.89 15.89,18 17,18M1,2H4.27L5.21,4H20A1,1 0 0,1 21,5C21,5.17 20.95,5.34 20.88,5.5L17.3,11.97C16.96,12.58 16.3,13 15.55,13H8.1L7.2,14.63L7.17,14.75A0.25,0.25 0 0,0 7.42,15H19V17H7C5.89,17 5,16.1 5,15C5,14.65 5.09,14.32 5.24,14.04L6.6,11.59L3,4H1V2M7,18A2,2 0 0,1 9,20A2,2 0 0,1 7,22C5.89,22 5,21.1 5,20C5,18.89 5.89,18 7,18M16,11L18.78,6H6.14L8.5,11H16Z"/>
18817          </svg>
18818        </div>
18819        Login untuk Melanjutkan
18820      </h3>
18821    </div>
18822    <div class="modal-body">
18823      <p class="modal-message" style="margin-bottom: 16px;">Silakan login atau daftar untuk menyimpan catatan dan panduan shio Anda.</p>
18824      <div style="background: #f8fafc; border-radius: 8px; padding: 12px; margin-bottom: 20px;">
18825        <p style="font-size: 14px; color: #646947; margin: 0; line-height: 1.6;">
18826          <strong style="color: #2a2a0f;">Keuntungan dengan akun:</strong><br>
18827          ✓ Simpan catatan shio Anda<br>
18828          ✓ Lacak status pesanan secara real-time<br>
18829          ✓ Akses catatan lebih cepat dengan data tersimpan<br>
18830          ✓ Dapatkan notifikasi promo & diskon eksklusif
18831        </p>
18832      </div>
18833      <div class="modal-actions">
18834        <button class="modal-btn modal-btn-secondary" id="cartLoginCancel">Batal</button>
18835        <button class="modal-btn modal-btn-primary" id="cartLoginConfirm" style="background: #ff6b35; border-color: #ff6b35;">Login</button>
18836        <button class="modal-btn modal-btn-primary" id="cartRegisterBtn" style="background: #f7931e; border-color: #f7931e;">Daftar</button>
18837      </div>
18838    </div>
18839  </div>
18840</div>
18841
18842
18843
18844<script data-inlined="app.js">
18845(function initLiveSearch(){
18846  function setup() {
18847    const searchInput = document.getElementById('searchInput');
18848    const suggest = document.getElementById('searchSuggest');
18849    const searchBtn = document.getElementById('searchBtn');
18850    const aiToggleBtn = document.getElementById('aiToggleBtn');
18851
18852    if (!searchInput || !suggest || !aiToggleBtn) {
18853      console.log('[LiveSearch] Elemen belum ada, retry...');
18854      return false;
18855    }
18856
18857    let debounceTimer = null;
18858    let lastQuery = '';
18859    let currentIndex = -1;
18860    let currentAbortController = null;
18861    let isAIModeEnabled = false; // AI mode OFF by default
18862    let isAIProcessing = false; // Flag to prevent closing suggest box during AI processing
18863
18864    // Trending cache
18865    let trendingCache = null;
18866    let trendingLoading = false;
18867
18868    // Trending products cache
18869    let trendingProductsCache = null;
18870    let trendingProductsLoading = false;
18871
18872    const MIN_LEN = 2;
18873    const DEBOUNCE = 300;
18874    const AI_TIMEOUT = 15000; // 15 seconds timeout for AI endpoint
18875
18876    // Load AI mode preference from localStorage
18877    const savedAIMode = localStorage.getItem('plazait_ai_search_mode');
18878    if (savedAIMode === 'enabled') {
18879      isAIModeEnabled = false;
18880      aiToggleBtn.classList.remove('active');
18881    }
18882
18883    // Update placeholder based on AI mode
18884    function updatePlaceholder() {
18885      if (isAIModeEnabled) {
18886        searchInput.placeholder = '🤖 Coba: "laptop HP core i3 untuk kerja" atau "tinta epson l3210"';
18887      } else {
18888        // Static placeholder for typing animation
18889        searchInput.placeholder = '';
18890      }
18891    }
18892
18893    // Typing animation for placeholder
18894    let typingTimeout = null;
18895    let currentProductIndex = 0;
18896    let currentCharIndex = 0;
18897    let isTyping = false;
18898    let isDeleting = false;
18899
18900    const popularProducts = [
18901      'Oppo A6',
18902      'Asus Ryzen 3',
18903      'Starlink V4',
18904      'HP Victus',
18905      'BOYTOTO Shio 4D',
18906      'ROG Strix',
18907      'iPhone 13',
18908      'Xpad 20',
18909      'Xiaomi 14',
18910      'Epson L3210',
18911      'Realme C85',
18912      'Brother T720',
18913      'Mouse Logitech'
18914    ];
18915
18916    function typeWriter() {
18917      if (isAIModeEnabled || document.activeElement === searchInput) {
18918        return; // Skip typing if AI mode is on or input is focused
18919      }
18920
18921      const currentProduct = popularProducts[currentProductIndex];
18922
18923      if (!isDeleting) {
18924        // Typing forward
18925        if (currentCharIndex < currentProduct.length) {
18926          searchInput.placeholder = currentProduct.substring(0, currentCharIndex + 1);
18927          currentCharIndex++;
18928          typingTimeout = setTimeout(typeWriter, 100); // Typing speed
18929        } else {
18930          // Finished typing, wait before deleting
18931          typingTimeout = setTimeout(() => {
18932            isDeleting = true;
18933            typeWriter();
18934          }, 2000); // Pause before deleting
18935        }
18936      } else {
18937        // Deleting backward
18938        if (currentCharIndex > 0) {
18939          searchInput.placeholder = currentProduct.substring(0, currentCharIndex - 1);
18940          currentCharIndex--;
18941          typingTimeout = setTimeout(typeWriter, 50); // Deleting speed (faster)
18942        } else {
18943          // Finished deleting, move to next product
18944          isDeleting = false;
18945          currentProductIndex = (currentProductIndex + 1) % popularProducts.length;
18946          typingTimeout = setTimeout(typeWriter, 500); // Pause before typing next
18947        }
18948      }
18949    }
18950
18951    function startTypingAnimation() {
18952      if (!isAIModeEnabled && !typingTimeout) {
18953        currentCharIndex = 0;
18954        isDeleting = false;
18955        typeWriter();
18956      }
18957    }
18958
18959    function stopTypingAnimation() {
18960      if (typingTimeout) {
18961        clearTimeout(typingTimeout);
18962        typingTimeout = null;
18963      }
18964    }
18965
18966    // Set initial placeholder
18967    updatePlaceholder();
18968
18969    // Start typing animation for normal mode
18970    if (!isAIModeEnabled) {
18971      startTypingAnimation();
18972    }
18973
18974    function esc(str){
18975      if (str === null || str === undefined) return '';
18976      return String(str)
18977        .replace(/&/g,'&amp;')
18978        .replace(/</g,'&lt;')
18979        .replace(/>/g,'&gt;')
18980        .replace(/"/g,'&quot;')
18981        .replace(/'/g,'&#39;');
18982    }
18983
18984    function clearSuggest(){
18985      suggest.innerHTML = '';
18986      suggest.hidden = true;
18987      suggest.classList.remove('open', 'trending-mode');
18988      currentIndex = -1;
18989    }
18990
18991    // ==================== TRENDING FUNCTIONS ====================
18992
18993    async function fetchTrending() {
18994      if (trendingCache) return trendingCache;
18995      if (trendingLoading) return null;
18996
18997      trendingLoading = true;
18998
18999      try {
19000        const res = await fetch('/api/trending_searches.php?timeframe=7d&limit=8');
19001        const data = await res.json();
19002
19003        if (data.ok && Array.isArray(data.data)) {
19004          trendingCache = data.data;
19005          console.log('[Trending] Loaded', trendingCache.length, 'items');
19006          return trendingCache;
19007        }
19008      } catch (err) {
19009        console.error('[Trending] Fetch error:', err);
19010      } finally {
19011        trendingLoading = false;
19012      }
19013
19014      return null;
19015    }
19016
19017    async function fetchTrendingProducts() {
19018      if (trendingProductsCache) return trendingProductsCache;
19019      if (trendingProductsLoading) return null;
19020
19021      trendingProductsLoading = true;
19022
19023      try {
19024        const res = await fetch('/api/trending_products.php?timeframe=7d&limit=6');
19025        const data = await res.json();
19026
19027        if (data.ok && Array.isArray(data.data)) {
19028          trendingProductsCache = data.data;
19029          console.log('[Trending Products] Loaded', trendingProductsCache.length, 'items');
19030          return trendingProductsCache;
19031        }
19032      } catch (err) {
19033        console.error('[Trending Products] Fetch error:', err);
19034      } finally {
19035        trendingProductsLoading = false;
19036      }
19037
19038      return null;
19039    }
19040
19041    function renderTrendingSuggestions(trends, products) {
19042      if ((!trends || trends.length === 0) && (!products || products.length === 0)) {
19043        clearSuggest();
19044        return;
19045      }
19046
19047      let html = `<div class="trending-container">`;
19048
19049      // Left column: Trending searches
19050      if (trends && trends.length > 0) {
19051        html += `
19052          <div class="trending-column trending-searches">
19053            <div class="trending-header">
19054              <span class="trending-icon">🔥</span>
19055              <span class="trending-title">Pencarian Trending</span>
19056            </div>
19057        `;
19058
19059        html += trends.map((t, idx) => {
19060          const categoryHint = t.category_hint ? `<span class="trending-category">${esc(t.category_hint)}</span>` : '';
19061          return `
19062            <div class="trending-item" data-query="${esc(t.query)}" data-index="${idx}">
19063              <span class="trending-rank">
19064                <img src="/assets/img/uptrend.image" alt="trending" width="16" height="16" style="vertical-align: middle;">
19065              </span>
19066              <span class="trending-query">${esc(t.query)}</span>
19067              ${categoryHint}
19068            </div>
19069          `;
19070        }).join('');
19071
19072        html += `</div>`;
19073      }
19074
19075      // Right column: Trending products
19076      if (products && products.length > 0) {
19077        html += `
19078          <div class="trending-column trending-products">
19079            <div class="trending-header">
19080              <span class="trending-icon">⭐</span>
19081              <span class="trending-title">Produk Populer</span>
19082            </div>
19083        `;
19084
19085        html += products.map((p, idx) => {
19086          return `
19087            <div class="trending-product-item" data-product-id="${esc(p.id)}" data-slug="${esc(p.slug)}" data-query="${esc(p.top_query || '')}" data-index="${idx}">
19088              <img src="${esc(p.image)}" alt="${esc(p.name)}" class="trending-product-thumb">
19089              <div class="trending-product-info">
19090                <div class="trending-product-name">${esc(p.name)}</div>
19091                <div class="trending-product-price">${esc(p.price_label)}</div>
19092              </div>
19093            </div>
19094          `;
19095        }).join('');
19096
19097        html += `</div>`;
19098      }
19099
19100      html += `</div>`;
19101
19102      suggest.innerHTML = html;
19103      suggest.hidden = false;
19104      suggest.classList.add('open', 'trending-mode');
19105      suggest.style.display = 'block';
19106
19107      console.log('[Trending] Rendered', trends?.length || 0, 'searches and', products?.length || 0, 'products');
19108    }
19109
19110    function trackSearchClick(query, productId) {
19111      fetch('/api/track_search_click.php', {
19112        method: 'POST',
19113        headers: { 'Content-Type': 'application/json' },
19114        body: JSON.stringify({
19115          query: query,
19116          product_id: productId,
19117          click_type: 'suggestion'
19118        })
19119      }).catch(() => {}); // silent fail
19120    }
19121
19122    function trackTrendingClick(query) {
19123      fetch('/api/track_search_click.php', {
19124        method: 'POST',
19125        headers: { 'Content-Type': 'application/json' },
19126        body: JSON.stringify({
19127          query: query,
19128          click_type: 'trending'
19129        })
19130      }).catch(() => {});
19131    }
19132
19133    function trackTrendingProductClick(productId, query) {
19134      fetch('/api/track_search_click.php', {
19135        method: 'POST',
19136        headers: { 'Content-Type': 'application/json' },
19137        body: JSON.stringify({
19138          query: query || 'trending_product',
19139          product_id: productId,
19140          click_type: 'trending_product'
19141        })
19142      }).catch(() => {});
19143    }
19144
19145    function trackSearchSubmit(query) {
19146      fetch('/api/track_search_click.php', {
19147        method: 'POST',
19148        headers: { 'Content-Type': 'application/json' },
19149        body: JSON.stringify({
19150          query: query,
19151          click_type: 'submit'
19152        })
19153      }).catch(() => {});
19154    }
19155
19156    function logFinalSearch(query) {
19157      // Log the final search to analytics (not during typing)
19158      fetch('/api/search_products.php?q=' + encodeURIComponent(query) + '&log=1', {
19159        headers: { 'Accept': 'application/json' }
19160      }).catch(() => {});
19161    }
19162
19163    // ==================== END TRENDING FUNCTIONS ====================
19164
19165    function clearSuggest(){
19166      suggest.innerHTML = '';
19167      suggest.hidden = true;
19168      suggest.classList.remove('open', 'trending-mode');
19169      currentIndex = -1;
19170    }
19171
19172    // AI Toggle Handler
19173    aiToggleBtn.addEventListener('click', (e) => {
19174      e.preventDefault();
19175      e.stopPropagation();
19176
19177      isAIModeEnabled = !isAIModeEnabled;
19178
19179      if (isAIModeEnabled) {
19180        aiToggleBtn.classList.add('active');
19181        localStorage.setItem('plazait_ai_search_mode', 'enabled');
19182        updatePlaceholder(); // Update placeholder text
19183        stopTypingAnimation(); // Stop typing animation in AI mode
19184
19185        // Show AI prompt if there's a query
19186        const query = searchInput.value.trim();
19187        if (query.length >= MIN_LEN) {
19188          showAISearchPrompt(query);
19189        } else {
19190          showAIActivationMessage();
19191        }
19192      } else {
19193        aiToggleBtn.classList.remove('active');
19194        localStorage.setItem('plazait_ai_search_mode', 'disabled');
19195        updatePlaceholder(); // Update placeholder text
19196        startTypingAnimation(); // Start typing animation in normal mode
19197        clearSuggest();
19198
19199        // Switch to normal search if there's a query
19200        const query = searchInput.value.trim();
19201        if (query.length >= MIN_LEN) {
19202          performSearch(query);
19203        }
19204      }
19205    });
19206
19207    function showAIActivationMessage() {
19208      suggest.innerHTML = `
19209        <div class="ai-progress-container">
19210          <div class="ai-progress-header">
19211            <div class="ai-progress-icon">✨</div>
19212            <div class="ai-progress-text">
19213              <div class="ai-progress-title">AI Search Mode Activated!</div>
19214              <div class="ai-progress-subtitle">Ketik query Anda untuk pencarian cerdas</div>
19215            </div>
19216          </div>
19217        </div>
19218      `;
19219      suggest.hidden = false;
19220      suggest.classList.add('open');
19221      suggest.style.display = 'block';
19222    }
19223
19224    function renderAIInsights(insights){
19225      if (!insights) return '';
19226
19227      // Generate AI summary via backend endpoint
19228      const summary = insights.ai_summary || generateAISummary(insights);
19229
19230      if (!summary) return '';
19231
19232      return `
19233        <div class="ai-result-banner">
19234          <div class="ai-result-icon">✨</div>
19235          <div class="ai-result-text">
19236            <div class="ai-result-title">AI Assistant</div>
19237            <div class="ai-result-description">${summary}</div>
19238          </div>
19239        </div>
19240      `;
19241    }
19242
19243    function generateAISummary(insights) {
19244      // Fallback client-side summary generation if backend doesn't provide ai_summary
19245      const productName = insights.product_name || '';
19246      const categoryPath = insights.category_path || '';
19247      const priceRange = insights.price_range || {};
19248      const intent = insights.intent || '';
19249
19250      let summary = '';
19251
19252      // Parse category for natural display
19253      let categoryDisplay = '';
19254      if (categoryPath) {
19255        const categoryParts = categoryPath.split('/');
19256        categoryDisplay = categoryParts[categoryParts.length - 1] || categoryParts[categoryParts.length - 2] || categoryPath;
19257      }
19258
19259      // Intent-based opening
19260      if (intent === 'cek_stok') {
19261        summary = `Berikut ketersediaan stok untuk <strong>${esc(productName)}</strong>. `;
19262      } else if (intent === 'rekomendasi_produk') {
19263        summary = `Berikut rekomendasi produk <strong>${esc(categoryDisplay || productName)}</strong> yang kami pilihkan untuk Anda. `;
19264      } else {
19265        // Default: cari_harga or general
19266        if (productName && categoryDisplay) {
19267          summary = `Kami menemukan <strong>${esc(productName)}</strong> di kategori ${esc(categoryDisplay)}`;
19268        } else if (productName) {
19269          summary = `Kami menemukan produk <strong>${esc(productName)}</strong>`;
19270        } else if (categoryDisplay) {
19271          summary = `Kami menemukan produk ${esc(categoryDisplay)}`;
19272        }
19273
19274        // Add price context
19275        if (priceRange.min > 0 && priceRange.max > 0) {
19276          const minPrice = new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(priceRange.min);
19277          const maxPrice = new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(priceRange.max);
19278          summary += ` dengan kisaran harga ${minPrice} - ${maxPrice}`;
19279        } else if (priceRange.min > 0) {
19280          const minPrice = new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(priceRange.min);
19281          summary += ` dengan harga mulai ${minPrice}`;
19282        } else if (priceRange.max > 0) {
19283          const maxPrice = new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(priceRange.max);
19284          summary += ` dengan harga maksimal ${maxPrice}`;
19285        }
19286
19287        summary += ' yang sesuai dengan kebutuhan Anda.';
19288      }
19289
19290      return summary || esc(insights.explanation || '');
19291    }
19292
19293    function showAIProgressSteps() {
19294      return `
19295        <div class="ai-progress-container">
19296          <div class="ai-progress-header">
19297            <div class="ai-progress-icon">🤖</div>
19298            <div class="ai-progress-text">
19299              <div class="ai-progress-title">AI sedang menganalisis...</div>
19300              <div class="ai-progress-subtitle">Memproses query Anda dengan kecerdasan buatan</div>
19301            </div>
19302          </div>
19303          <div class="ai-progress-steps">
19304            <div class="ai-progress-step active">
19305              <div class="ai-step-icon">1</div>
19306              <div class="ai-step-text">Memahami maksud pencarian...</div>
19307            </div>
19308            <div class="ai-progress-step pending">
19309              <div class="ai-step-icon">2</div>
19310              <div class="ai-step-text">Mengekstrak parameter pencarian...</div>
19311            </div>
19312            <div class="ai-progress-step pending">
19313              <div class="ai-step-icon">3</div>
19314              <div class="ai-step-text">Mencari produk yang relevan...</div>
19315            </div>
19316          </div>
19317        </div>
19318      `;
19319    }
19320
19321    function updateAIProgressStep(step) {
19322      const steps = suggest.querySelectorAll('.ai-progress-step');
19323
19324      steps.forEach((stepEl, idx) => {
19325        const icon = stepEl.querySelector('.ai-step-icon');
19326
19327        if (idx < step) {
19328          stepEl.classList.remove('active', 'pending');
19329          stepEl.classList.add('completed');
19330          icon.textContent = '✓';
19331        } else if (idx === step) {
19332          stepEl.classList.remove('pending', 'completed');
19333          stepEl.classList.add('active');
19334        }
19335      });
19336    }
19337
19338    function renderSuggest(items, aiInsights){
19339      if (!items.length){
19340        clearSuggest();
19341        return;
19342      }
19343
19344      let html = '';
19345
19346      // Show AI insights if available
19347      if (aiInsights && aiInsights.explanation) {
19348        html += renderAIInsights(aiInsights);
19349      }
19350
19351      // Section header for products with hint
19352      html += `
19353        <div class="suggest-section-header">
19354          <div class="suggest-header-main">
19355            <span class="suggest-header-icon">📦</span>
19356            <span class="suggest-header-text">Produk yang ditemukan (${items.length})</span>
19357          </div>
19358          <div class="suggest-header-hint">Tekan <kbd>Enter</kbd> untuk lihat semua hasil</div>
19359        </div>
19360      `;
19361
19362      // Render product items
19363      html += items.map((it, idx)=>{
19364        // Normalisasi & fallback
19365        const id    = it.id != null ? it.id : '';
19366        const name  = esc(it.name);
19367        const slug  = esc(it.slug != null ? it.slug : it.id);
19368        const img   = esc(it.image || '/assets/img/no-image.png');
19369        const price = esc(it.price_label || '');
19370
19371       const url = '/product/' + encodeURIComponent(slug);
19372
19373        return `
19374          <div class="suggest-item" data-index="${idx}" data-url="${url}" data-id="${esc(id)}">
19375            <img src="${img}" alt="${name}" class="suggest-thumb">
19376            <div class="suggest-meta">
19377              <div class="suggest-name">${name}</div>
19378              <div class="suggest-price">${price}</div>
19379            </div>
19380          </div>
19381        `;
19382      }).join('');
19383
19384      // Footer hint for more results - only show if 10 items (means there might be more)
19385      if (items.length >= 10) {
19386        html += `
19387          <div class="suggest-footer-hint">
19388            <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="vertical-align: -3px; margin-right: 6px;">
19389              <path d="M9.29 6.71a.996.996 0 0 0 0 1.41L13.17 12l-3.88 3.88a.996.996 0 1 0 1.41 1.41l4.59-4.59a.996.996 0 0 0 0-1.41L10.7 6.7c-.38-.39-1.02-.39-1.41.01z"/>
19390            </svg>
19391            <span>Lihat hasil lengkap dengan menekan <kbd>Enter</kbd> atau <a href="#" class="suggest-footer-link" id="footerSearchLink">klik di sini</a></span>
19392          </div>
19393        `;
19394      }
19395
19396      suggest.innerHTML = html;
19397      suggest.hidden = false;
19398      suggest.classList.add('open');
19399      suggest.style.display = 'block';
19400      suggest.style.visibility = 'visible';
19401      suggest.style.opacity = '1';
19402    }
19403
19404    function fetchRemote(q, shouldLog = false){
19405      // Cancel previous request if any
19406      if (currentAbortController) {
19407        currentAbortController.abort();
19408      }
19409      currentAbortController = new AbortController();
19410
19411      // Always use standard search for normal mode
19412      // Only add log=1 parameter when shouldLog is true (intentional search)
19413      const logParam = shouldLog ? '&log=1' : '';
19414      const url = '/api/search_products.php?q=' + encodeURIComponent(q) + logParam;
19415      const fetchOptions = {
19416        headers: {'Accept':'application/json'},
19417        signal: currentAbortController.signal
19418      };
19419
19420      return fetch(url, fetchOptions)
19421        .then(r=>r.json())
19422        .then(j=>{
19423          if (j.ok && Array.isArray(j.data)) {
19424            return j;
19425          }
19426          return { ok: false, data: [] };
19427        })
19428        .catch(err=>{
19429          if (err.name === 'AbortError') {
19430            return { ok: false, data: [] };
19431          }
19432          return { ok:false, data:[] };
19433        });
19434    }
19435
19436    function showNoResults(q, aiInsights) {
19437      suggest.innerHTML = `
19438        <div style="padding: 32px 24px; text-align: center; color: #64748b;">
19439          <div style="font-size: 56px; margin-bottom: 16px; opacity: 0.6;">🔍</div>
19440          <div style="font-size: 16px; font-weight: 600; color: #334155; margin-bottom: 8px;">
19441            Tidak ada hasil untuk "${esc(q)}"
19442          </div>
19443          ${aiInsights && aiInsights.explanation ? `
19444            <div style="font-size: 13px; margin: 16px auto; padding: 12px 16px; background: #f1f5f9; border-radius: 8px; max-width: 400px; color: #475569;">
19445              <strong style="color: #667eea;">🤖 AI memahami:</strong><br>
19446              ${esc(aiInsights.explanation)}
19447            </div>
19448          ` : ''}
19449          <div style="font-size: 13px; margin-top: 16px; opacity: 0.8;">
19450            Coba kata kunci lain atau hubungi kami untuk bantuan
19451          </div>
19452        </div>
19453      `;
19454      suggest.hidden = false;
19455      suggest.classList.add('open');
19456      suggest.style.display = 'block';
19457    }
19458
19459    function showErrorMessage() {
19460      suggest.innerHTML = `
19461        <div style="padding: 32px 24px; text-align: center; color: #dc2626;">
19462          <div style="font-size: 48px; margin-bottom: 12px;">⚠️</div>
19463          <div style="font-size: 14px; font-weight: 600; margin-bottom: 8px;">
19464            Pencarian tidak tersedia
19465          </div>
19466          <div style="font-size: 12px; opacity: 0.8;">
19467            Silakan coba lagi dalam beberapa saat
19468          </div>
19469        </div>
19470      `;
19471      suggest.hidden = false;
19472      suggest.classList.add('open');
19473      suggest.style.display = 'block';
19474    }
19475
19476    function showAIErrorState(q) {
19477      suggest.innerHTML = `
19478        <div class="ai-progress-container">
19479          <div class="ai-progress-header">
19480            <div class="ai-progress-icon" style="background: linear-gradient(135deg, #f59e0b 0%, #f97316 100%);">⚡</div>
19481            <div class="ai-progress-text">
19482              <div class="ai-progress-title">AI sedang memproses...</div>
19483              <div class="ai-progress-subtitle">Menganalisis query: "${esc(q)}"</div>
19484            </div>
19485          </div>
19486          <div style="padding: 16px; margin-top: 12px; background: #fef3c7; border-radius: 8px; border-left: 3px solid #f59e0b;">
19487            <div style="font-size: 13px; color: #92400e; line-height: 1.6;">
19488              <strong style="display: block; margin-bottom: 4px;">💡 Sedang mencari produk terbaik untuk Anda</strong>
19489              AI kami sedang menganalisis database untuk menemukan hasil yang paling relevan.
19490              Mohon tunggu sebentar...
19491            </div>
19492          </div>
19493          <div style="padding: 12px 16px; margin-top: 12px; text-align: center;">
19494            <button onclick="location.reload()" style="padding: 8px 16px; background: #fb923c; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 600;">
19495              🔄 Refresh & Coba Lagi
19496            </button>
19497          </div>
19498        </div>
19499      `;
19500      suggest.hidden = false;
19501      suggest.classList.add('open');
19502      suggest.style.display = 'block';
19503    }
19504
19505    function triggerAISearch(q) {
19506      lastQuery = q; // Update lastQuery to prevent re-triggering
19507      isAIProcessing = true; // Set flag to prevent suggest box from closing
19508
19509      // Show AI progress UI - keep suggest box visible
19510      suggest.innerHTML = showAIProgressSteps();
19511      suggest.hidden = false;
19512      suggest.classList.add('open');
19513      suggest.style.display = 'block';
19514
19515      setTimeout(() => updateAIProgressStep(1), 1000);
19516      setTimeout(() => updateAIProgressStep(2), 2500);
19517
19518      // Cancel previous request if any
19519      if (currentAbortController) {
19520        currentAbortController.abort();
19521      }
19522      currentAbortController = new AbortController();
19523
19524      const url = '/api/search_ai.php?q=' + encodeURIComponent(q);
19525      const fetchOptions = {
19526        headers: {'Accept':'application/json'},
19527        signal: currentAbortController.signal
19528      };
19529
19530      // Add timeout
19531      const timeoutId = setTimeout(() => {
19532        currentAbortController.abort();
19533
19534        // Show fallback loading state
19535        suggest.innerHTML = `
19536          <div class="ai-progress-container">
19537            <div class="ai-progress-header">
19538              <div class="ai-progress-icon">⏳</div>
19539              <div class="ai-progress-text">
19540                <div class="ai-progress-title">AI timeout - switching to standard search</div>
19541                <div class="ai-progress-subtitle">Mencari dengan metode tradisional...</div>
19542              </div>
19543            </div>
19544          </div>
19545        `;
19546
19547        // Fallback to traditional search
19548        fetch('/api/search_products.php?q=' + encodeURIComponent(q), {
19549          headers: { 'Accept': 'application/json' }
19550        })
19551          .then(r => r.json())
19552          .then(j => {
19553            isAIProcessing = false; // Reset flag
19554            if (j.ok && Array.isArray(j.data) && j.data.length) {
19555              renderSuggest(j.data, null);
19556            } else {
19557              showNoResults(q, null);
19558            }
19559          })
19560          .catch(() => {
19561            isAIProcessing = false; // Reset flag
19562            showAIErrorState(q);
19563          });
19564      }, AI_TIMEOUT);
19565
19566      fetch(url, fetchOptions)
19567        .then(r => r.json())
19568        .then(j => {
19569          clearTimeout(timeoutId);
19570          isAIProcessing = false; // Reset flag when done
19571
19572          if (j.ok && Array.isArray(j.data)) {
19573            if (j.data.length > 0) {
19574              renderSuggest(j.data, j.ai_insights || null);
19575            } else {
19576              showNoResults(q, j.ai_insights || null);
19577            }
19578          } else {
19579            showAIErrorState(q);
19580          }
19581        })
19582        .catch(err => {
19583          clearTimeout(timeoutId);
19584          isAIProcessing = false; // Reset flag on error
19585          if (err.name !== 'AbortError') {
19586            showAIErrorState(q);
19587          }
19588        });
19589    }
19590
19591    function performSearch(q){
19592      if (q.length < MIN_LEN){
19593        clearSuggest();
19594        return;
19595      }
19596      if (q === lastQuery) return;
19597      lastQuery = q;
19598
19599      // Normal mode only - AI mode uses triggerAISearch()
19600      fetchRemote(q).then(j=>{
19601        if (j.ok && Array.isArray(j.data)){
19602          if (j.data.length > 0) {
19603            renderSuggest(j.data, null);
19604          } else {
19605            showNoResults(q, null);
19606          }
19607        } else {
19608          clearSuggest();
19609        }
19610      }).catch(() => {
19611        clearSuggest();
19612      });
19613    }
19614
19615    function debounce(){
19616      const q = searchInput.value.trim();
19617      clearTimeout(debounceTimer);
19618
19619      // If empty, show trending suggestions and products
19620      if (q.length === 0) {
19621        debounceTimer = setTimeout(async () => {
19622          const trends = trendingCache || await fetchTrending();
19623          const products = trendingProductsCache || await fetchTrendingProducts();
19624          if ((trends && trends.length > 0) || (products && products.length > 0)) {
19625            renderTrendingSuggestions(trends, products);
19626          } else {
19627            clearSuggest();
19628          }
19629        }, 100);
19630        return;
19631      }
19632
19633      // For AI mode, just show search prompt (no auto search)
19634      if (isAIModeEnabled && q.length >= MIN_LEN) {
19635        showAISearchPrompt(q);
19636      } else if (!isAIModeEnabled && q.length >= MIN_LEN) {
19637        // Normal mode: auto search with debounce
19638        debounceTimer = setTimeout(()=> performSearch(q), DEBOUNCE);
19639      } else {
19640        clearSuggest();
19641      }
19642    }
19643
19644    function showAISearchPrompt(q) {
19645      suggest.innerHTML = `
19646        <div class="ai-search-prompt">
19647          <div class="ai-prompt-header">
19648            <div class="ai-prompt-icon">🤖</div>
19649            <div class="ai-prompt-text">
19650              <div class="ai-prompt-title">Pencarian AI Siap!</div>
19651              <div class="ai-prompt-subtitle">Klik tombol di bawah untuk mencari dengan AI</div>
19652            </div>
19653          </div>
19654          <div class="ai-prompt-query">
19655            <div class="ai-query-label">
19655Query Anda:</div>
19656            <div class="ai-query-text">"${esc(q)}"</div>
19657          </div>
19658          <button class="ai-search-trigger-btn" data-query="${esc(q)}">
19659            <span class="ai-btn-icon">✨</span>
19660            <span class="ai-btn-text">Cari dengan AI</span>
19661          </button>
19662          <div class="ai-prompt-footer">
19663            AI akan menganalisis query Anda untuk hasil yang lebih relevan
19664          </div>
19665        </div>
19666      `;
19667      suggest.hidden = false;
19668      suggest.classList.add('open');
19669      suggest.style.display = 'block';
19670    }
19671
19672    function moveHighlight(dir){
19673      if (suggest.hidden) return;
19674      const items = suggest.querySelectorAll('.suggest-item');
19675      if (!items.length) return;
19676      currentIndex += dir;
19677      if (currentIndex < 0) currentIndex = items.length - 1;
19678      if (currentIndex >= items.length) currentIndex = 0;
19679      items.forEach(it=> it.classList.remove('active'));
19680      const active = items[currentIndex];
19681      if (active){
19682        active.classList.add('active');
19683        active.scrollIntoView({block:'nearest'});
19684      }
19685    }
19686
19687    function activate(){
19688      const active = suggest.querySelector('.suggest-item.active');
19689      if (!active) return;
19690      window.location.href = active.getAttribute('data-url');
19691    }
19692
19693    searchInput.addEventListener('input', debounce);
19694
19695    // Stop typing animation when focused & load trending if empty
19696    searchInput.addEventListener('focus', async () => {
19697      stopTypingAnimation();
19698
19699      const q = searchInput.value.trim();
19700
19701      // If empty, show trending suggestions and products
19702      if (q.length === 0) {
19703        const trends = trendingCache || await fetchTrending();
19704        const products = trendingProductsCache || await fetchTrendingProducts();
19705        if ((trends && trends.length > 0) || (products && products.length > 0)) {
19706          renderTrendingSuggestions(trends, products);
19707        }
19708        return;
19709      }
19710
19711      // If AI mode and has query, show AI prompt
19712      if (isAIModeEnabled && q.length >= MIN_LEN && suggest.hidden) {
19713        showAISearchPrompt(q);
19714      }
19715    });
19716
19717    // Resume typing animation when blurred (if suggest is closed)
19718    searchInput.addEventListener('blur', () => {
19719      // Add delay to allow click events to fire first
19720      setTimeout(() => {
19721        if (!isAIModeEnabled && suggest.hidden && searchInput.value.trim() === '') {
19722          startTypingAnimation();
19723        }
19724      }, 200);
19725    });
19726
19727    searchInput.addEventListener('keydown', e=>{
19728      switch(e.key){
19729        case 'ArrowDown': moveHighlight(1); e.preventDefault(); break;
19730        case 'ArrowUp': moveHighlight(-1); e.preventDefault(); break;
19731        case 'Enter':
19732          if (!suggest.hidden){
19733            activate();
19734            e.preventDefault();
19735          } else {
19736            // Direct enter without suggestion - track search submit and log final search
19737            const q = searchInput.value.trim();
19738            if (q.length >= MIN_LEN) {
19739              trackSearchSubmit(q);
19740              logFinalSearch(q);
19741            }
19742          }
19743          break;
19744        case 'Escape': clearSuggest(); break;
19745      }
19746    });
19747
19748    suggest.addEventListener('click', e=>{
19749      // Check if footer link clicked
19750      const footerLink = e.target.closest('#footerSearchLink');
19751      if (footerLink) {
19752        e.preventDefault();
19753        const q = searchInput.value.trim();
19754        if (q.length >= MIN_LEN) {
19755          trackSearchSubmit(q);
19756          logFinalSearch(q);
19757          window.location.href = '/product/search/?q=' + encodeURIComponent(q);
19758        }
19759        return;
19760      }
19761
19762      // Check if AI search button clicked
19763      const aiBtn = e.target.closest('.ai-search-trigger-btn');
19764      if (aiBtn) {
19765        const query = aiBtn.getAttribute('data-query');
19766        if (query) {
19767          triggerAISearch(query);
19768        }
19769        return;
19770      }
19771
19772      // Handle trending item click
19773      const trendingItem = e.target.closest('.trending-item');
19774      if (trendingItem) {
19775        const query = trendingItem.getAttribute('data-query');
19776        if (query) {
19777          searchInput.value = query;
19778          trackTrendingClick(query);
19779          logFinalSearch(query); // Log final search when user clicks trending
19780          performSearch(query);
19781        }
19782        return;
19783      }
19784
19785      // Handle trending product click
19786      const trendingProduct = e.target.closest('.trending-product-item');
19787      if (trendingProduct) {
19788        const productId = trendingProduct.getAttribute('data-product-id');
19789        const slug = trendingProduct.getAttribute('data-slug');
19790        const query = trendingProduct.getAttribute('data-query');
19791
19792        if (productId && slug) {
19793          trackTrendingProductClick(productId, query);
19794          window.location.href = '/product/' + encodeURIComponent(slug);
19795        }
19796        return;
19797      }
19798
19799      // Handle product item click
19800      const it = e.target.closest('.suggest-item');
19801      if (!it) return;
19802
19803      const productId = it.getAttribute('data-id');
19804      trackSearchClick(lastQuery, productId);
19805      logFinalSearch(lastQuery); // Log final search when user clicks on suggestion
19806
19807      window.location.href = it.getAttribute('data-url');
19808    });
19809
19810    document.addEventListener('click', e=>{
19811      // Don't close suggest box if AI is processing
19812      if (isAIProcessing) return;
19813
19814      if (e.target === searchInput || e.target === aiToggleBtn || 
19815          suggest.contains(e.target) || aiToggleBtn.contains(e.target)) return;
19816      clearSuggest();
19817    });
19818
19819    if (searchBtn){
19820      searchBtn.addEventListener('click', ()=>{
19821        const q = searchInput.value.trim();
19822        if (q.length >= MIN_LEN){
19823          // Track search submit and log final search
19824          trackSearchSubmit(q);
19825          logFinalSearch(q);
19826          window.location.href = '/product/search/?q=' + encodeURIComponent(q);
19827        }
19828      });
19829    }
19830
19831    // Pastikan style dasar muncul
19832    if (!suggest.classList.contains('wired')) {
19833      suggest.classList.add('wired');
19834      suggest.style.position = 'absolute';
19835      suggest.style.zIndex = '9999';
19836      suggest.style.background = '#fff';
19837      suggest.style.border = '1px solid #ccc';
19838      suggest.style.boxShadow = '0 4px 12px rgba(0,0,0,0.12)';
19839      suggest.style.maxHeight = '360px';
19840      suggest.style.overflowY = 'auto';
19841    }
19842
19843    return true;
19844  }
19845
19846  if (!setup()) {
19847    if (document.readyState === 'loading') {
19848      document.addEventListener('DOMContentLoaded', setup);
19849    } else {
19850      setTimeout(setup, 80);
19851    }
19852  }
19853})();
19854</script>
vendor: 2 bytes, line 19854
19854
19855<script data-inlined="site-notice.js">
19856/**
19857 * Site Notice Banner - Interactive Behavior
19858 * Handles dismiss functionality with localStorage persistence
19859 * Supports dynamic notice IDs for different campaigns
19860 */
19861
19862(function() {
19863    'use strict';
19864
19865    const STORAGE_KEY = 'plazait_site_notice_dismissed';
19866
19867    const banner = document.getElementById('holidayNotice');
19868    const closeBtn = document.getElementById('holidayNoticeClose');
19869
19870    if (!banner || !closeBtn) return;
19871
19872    // Get notice ID from data attribute
19873    const noticeId = banner.dataset.noticeId;
19874    if (!noticeId) {
19875        console.warn('Notice ID not found. Banner will not persist dismiss state.');
19876    }
19877
19878    /**
19879     * Check if banner was previously dismissed
19880     */
19881    function wasDismissed() {
19882        if (!noticeId) return false;
19883
19884        try {
19885            const dismissed = localStorage.getItem(STORAGE_KEY);
19886            return dismissed === noticeId;
19887        } catch (e) {
19888            console.warn('localStorage not available:', e);
19889            return false;
19890        }
19891    }
19892
19893    /**
19894     * Mark banner as dismissed
19895     */
19896    function markDismissed() {
19897        if (!noticeId) return;
19898
19899        try {
19900            localStorage.setItem(STORAGE_KEY, noticeId);
19901        } catch (e) {
19902            console.warn('Could not save to localStorage:', e);
19903        }
19904    }
19905
19906    /**
19907     * Hide banner with animation
19908     */
19909    function hideBanner() {
19910        banner.classList.add('hidden');
19911        document.body.classList.remove('has-holiday-notice');
19912
19913        // Remove from DOM after animation
19914        setTimeout(() => {
19915            banner.remove();
19916        }, 300);
19917
19918        markDismissed();
19919
19920        // Analytics tracking (optional)
19921        if (window.gtag) {
19922            window.gtag('event', 'banner_dismiss', {
19923                'event_category': 'Site Notice',
19924                'event_label': noticeId || 'unknown',
19925            });
19926        }
19927    }
19928
19929    /**
19930     * Initialize banner
19931     */
19932    function init() {
19933        // Check if already dismissed
19934        if (wasDismissed()) {
19935            banner.remove();
19936            return;
19937        }
19938
19939        // Add body class for layout adjustment
19940        document.body.classList.add('has-holiday-notice');
19941
19942        // Close button click
19943        closeBtn.addEventListener('click', hideBanner);
19944
19945        // Keyboard support for close button
19946        closeBtn.addEventListener('keydown', (e) => {
19947            if (e.key === 'Enter' || e.key === ' ') {
19948                e.preventDefault();
19949                hideBanner();
19950            }
19951        });
19952
19953        // Optional: Auto-hide after certain time (disabled by default)
19954        // setTimeout(hideBanner, 30000); // 30 seconds
19955    }
19956
19957    // Initialize when DOM is ready
19958    if (document.readyState === 'loading') {
19959        document.addEventListener('DOMContentLoaded', init);
19960    } else {
19961        init();
19962    }
19963
19964})();
19965
19966</script>
vendor: 2 bytes, line 19966
19966
19967<script data-inlined="guest-cart.js">
19968/**
19969 * Guest Cart Manager - localStorage based cart for guest users
19970 * 
19971 * Manages shopping cart items in browser localStorage for users who are not logged in.
19972 * Provides seamless transition to database when user logs in.
19973 */
19974
19975class GuestCart {
19976    constructor() {
19977        this.storageKey = 'plazait_guest_cart';
19978        this.apiBase = '/api/cart';
19979
19980        // Initialize cart structure
19981        this.cart = this.loadFromStorage();
19982
19983        // Bind methods
19984        this.add = this.add.bind(this);
19985        this.remove = this.remove.bind(this);
19986        this.update = this.update.bind(this);
19987        this.clear = this.clear.bind(this);
19988        this.getItems = this.getItems.bind(this);
19989        this.mergeToDatabase = this.mergeToDatabase.bind(this);
19990    }
19991
19992    /**
19993     * Load cart from localStorage
19994     */
19995    loadFromStorage() {
19996        try {
19997            const stored = localStorage.getItem(this.storageKey);
19998            if (stored) {
19999                const parsed = JSON.parse(stored);
20000                if (parsed && typeof parsed === 'object' && parsed.items) {
20001                    return parsed;
20002                }
20003            }
20004        } catch (error) {
20005            // Silent error handling
20006        }
20007
20008        return {
20009            items: {},
20010            updated_at: Date.now(),
20011            version: '1.0'
20012        };
20013    }
20014
20015    /**
20016     * Save cart to localStorage
20017     */
20018    saveToStorage() {
20019        try {
20020            this.cart.updated_at = Date.now();
20021            localStorage.setItem(this.storageKey, JSON.stringify(this.cart));
20022
20023            // Dispatch custom event for cart updates
20024            window.dispatchEvent(new CustomEvent('guestCartUpdated', {
20025                detail: {
20026                    itemsCount: this.getItemsCount(),
20027                    linesCount: this.getLinesCount(),
20028                    items: this.cart.items
20029                }
20030            }));
20031
20032            return true;
20033        } catch (error) {
20034            return false;
20035        }
20036    }
20037
20038    /**
20039     * Generate cart item key
20040     */
20041    generateKey(productId, variantId = 0) {
20042        return `p${productId}v${variantId}`;
20043    }
20044
20045    /**
20046     * Add item to guest cart
20047     */
20048    add(productId, qty = 1, variantId = 0, meta = {}) {
20049        const key = this.generateKey(productId, variantId);
20050
20051        // Validate inputs
20052        if (!productId || productId <= 0 || !qty || qty <= 0) {
20053            throw new Error('Invalid product ID or quantity');
20054        }
20055
20056        qty = Math.max(1, Math.min(999, parseInt(qty)));
20057
20058        // Initialize item if not exists
20059        if (!this.cart.items[key]) {
20060            this.cart.items[key] = {
20061                pid: parseInt(productId),
20062                vid: parseInt(variantId) || 0,
20063                qty: 0,
20064                name: meta.name || '',
20065                sku: meta.sku || '',
20066                price: parseInt(meta.price) || 0,
20067                image_url: meta.image_url || '',
20068                product_url: meta.product_url || '/product/' + parseInt(productId),
20069                added_at: Date.now()
20070            };
20071        }
20072
20073        // Update quantity
20074        this.cart.items[key].qty = Math.max(1, Math.min(999, this.cart.items[key].qty + qty));
20075
20076        // Update metadata if provided
20077        if (meta.name) this.cart.items[key].name = meta.name;
20078        if (meta.sku) this.cart.items[key].sku = meta.sku;
20079        if (meta.price) this.cart.items[key].price = parseInt(meta.price);
20080        if (meta.image_url) this.cart.items[key].image_url = meta.image_url;
20081        if (meta.product_url) this.cart.items[key].product_url = meta.product_url;
20082
20083        this.saveToStorage();
20084
20085        return {
20086            key,
20087            item: this.cart.items[key],
20088            itemsCount: this.getItemsCount(),
20089            linesCount: this.getLinesCount()
20090        };
20091    }
20092
20093    /**
20094     * Remove item from guest cart
20095     */
20096    remove(productId, variantId = 0) {
20097        const key = this.generateKey(productId, variantId);
20098
20099        if (this.cart.items[key]) {
20100            delete this.cart.items[key];
20101            this.saveToStorage();
20102
20103            return true;
20104        }
20105
20106        return false;
20107    }
20108
20109    /**
20110     * Update item quantity
20111     */
20112    update(productId, qty, variantId = 0) {
20113        const key = this.generateKey(productId, variantId);
20114
20115        if (!this.cart.items[key]) {
20116            return false;
20117        }
20118
20119        qty = parseInt(qty);
20120        if (qty <= 0) {
20121            return this.remove(productId, variantId);
20122        }
20123
20124        this.cart.items[key].qty = Math.max(1, Math.min(999, qty));
20125        this.saveToStorage();
20126
20127        return true;
20128    }
20129
20130    /**
20131     * Clear entire cart
20132     */
20133    clear() {
20134        this.cart = {
20135            items: {},
20136            updated_at: Date.now(),
20137            version: '1.0'
20138        };
20139
20140        this.saveToStorage();
20141
20142        return true;
20143    }
20144
20145    /**
20146     * Get all cart items
20147     */
20148    getItems() {
20149        return this.cart.items;
20150    }
20151
20152    /**
20153     * Get cart items as array
20154     */
20155    getItemsArray() {
20156        return Object.values(this.cart.items);
20157    }
20158
20159    /**
20160     * Get total items count (sum of quantities)
20161     */
20162    getItemsCount() {
20163        return Object.values(this.cart.items).reduce((total, item) => total + (item.qty || 0), 0);
20164    }
20165
20166    /**
20167     * Get lines count (number of different items)
20168     */
20169    getLinesCount() {
20170        return Object.keys(this.cart.items).length;
20171    }
20172
20173    /**
20174     * Get cart summary
20175     */
20176    getSummary() {
20177        const itemsCount = this.getItemsCount();
20178        const linesCount = this.getLinesCount();
20179        const totalValue = Object.values(this.cart.items).reduce(
20180            (total, item) => total + ((item.qty || 0) * (item.price || 0)), 0
20181        );
20182
20183        return {
20184            itemsCount,
20185            linesCount,
20186            totalValue,
20187            updatedAt: this.cart.updated_at
20188        };
20189    }
20190
20191    /**
20192     * Check if cart has items
20193     */
20194    isEmpty() {
20195        return this.getLinesCount() === 0;
20196    }
20197
20198    /**
20199     * Merge guest cart to database after user login
20200     */
20201    async mergeToDatabase() {
20202        const items = this.cart.items;
20203
20204        if (Object.keys(items).length === 0) {
20205            return { ok: true, merged_count: 0, message: 'No items to merge' };
20206        }
20207
20208        try {
20209            // Use new endpoint that handles localStorage format
20210            const response = await fetch(`${this.apiBase}/merge-localStorage.php`, {
20211                method: 'POST',
20212                headers: {
20213                    'Content-Type': 'application/json',
20214                },
20215                credentials: 'same-origin',
20216                body: JSON.stringify({
20217                    items: items  // Send localStorage format directly
20218                })
20219            });
20220
20221            const result = await response.json();
20222
20223            if (result.ok) {
20224                // Clear localStorage cart after successful merge
20225                this.clear();
20226
20227                // Update cart counter
20228                if (window.updateCartCounter) {
20229                    try {
20230                        await window.updateCartCounter();
20231                    } catch (e) {
20232                        // Silent error handling
20233                    }
20234                }
20235
20236                // Dispatch merge completion event
20237                window.dispatchEvent(new CustomEvent('guestCartMerged', {
20238                    detail: result
20239                }));
20240
20241                return result;
20242            } else {
20243                throw new Error(result.error || 'Failed to merge cart');
20244            }
20245
20246        } catch (error) {
20247            throw error;
20248        }
20249    }
20250
20251    /**
20252     * Export cart data (for debugging or backup)
20253     */
20254    export() {
20255        return {
20256            ...this.cart,
20257            summary: this.getSummary()
20258        };
20259    }
20260
20261    /**
20262     * Import cart data (for restore or testing)
20263     */
20264    import(cartData) {
20265        if (cartData && typeof cartData === 'object' && cartData.items) {
20266            this.cart = {
20267                items: cartData.items,
20268                updated_at: cartData.updated_at || Date.now(),
20269                version: cartData.version || '1.0'
20270            };
20271
20272            this.saveToStorage();
20273            return true;
20274        }
20275
20276        return false;
20277    }
20278}
20279
20280// Create global instance
20281window.guestCart = new GuestCart();
20282
20283// Auto-merge on page load if user is logged in
20284document.addEventListener('DOMContentLoaded', async function() {
20285    // Multiple ways to check if user is logged in
20286    const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true' ||
20287                      document.body.classList.contains('logged-in') ||
20288                      window.userLoggedIn === true ||
20289                      document.body.dataset.userLoggedIn === 'true';
20290
20291    if (isLoggedIn && !window.guestCart.isEmpty()) {
20292        // Small delay to ensure page is fully loaded
20293        setTimeout(async () => {
20294            try {
20295                const result = await window.guestCart.mergeToDatabase();
20296            } catch (error) {
20297                // Silent error handling
20298            }
20299        }, 500);
20300    }
20301});
20302
20303// Also listen for login events (if login happens via AJAX)
20304window.addEventListener('userLoggedIn', async function() {
20305    if (!window.guestCart.isEmpty()) {
20306        try {
20307            await window.guestCart.mergeToDatabase();
20308        } catch (error) {
20309            // Silent error handling
20310        }
20311    }
20312});
20313</script>
vendor: 2 bytes, line 20313
20313
20314<script data-inlined="cart.js">
20315(function(){
20316  function $(s,ctx=document){ return ctx.querySelector(s); }
20317  function esc(s){ return String(s||'').replace(/[&<>"']/g, m=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;' }[m])); }
20318
20319  // CSRF token helper
20320  function getCsrfToken() {
20321    const metaTag = document.querySelector('meta[name="csrf-token"]');
20322    return metaTag ? metaTag.getAttribute('content') : '';
20323  }
20324
20325  function guessPdMeta(){
20326    const name = (window.PD_NAME !== undefined) ? String(window.PD_NAME) : ($('#pd-title')?.textContent || '');
20327    const sku  = (function(){ const el=$('#pd-sku'); return el ? el.textContent.trim() : (window.PD_SKU||''); })();
20328    // SECURITY FIX: Remove price from client-side metadata (will be validated server-side)
20329    const imgEl = $('#pd-main-img');
20330    const image_url = imgEl ? (imgEl.getAttribute('src') || '') : '';
20331    return { name, sku, image_url }; // price removed for security
20332  }
20333
20334  async function addToCart(productId, qty=1, variantId=0, metaOpt){
20335    const meta = Object.assign(guessPdMeta(), metaOpt||{});
20336    const payload = {
20337      product_id: parseInt(productId||0,10) || 0,
20338      qty: parseInt(qty||1,10) || 1,
20339      variant_id: parseInt(variantId||0,10) || 0,
20340      meta
20341    };
20342
20343    // Check if user is logged in
20344    const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true' ||
20345                      document.body.classList.contains('logged-in') ||
20346                      window.userLoggedIn === true ||
20347                      document.body.dataset.userLoggedIn === 'true';
20348
20349    // Skip localStorage untuk logged user - mereka pakai database
20350    if (!isLoggedIn && window.guestCart) {
20351      try {
20352        window.guestCart.add(payload.product_id, payload.qty, payload.variant_id, meta);
20353      } catch (e) {
20354        console.warn('Failed to add to localStorage cart:', e);
20355      }
20356    }
20357
20358    const res = await fetch('/api/cart/add.php', {
20359      method: 'POST',
20360      headers: { 
20361        'Content-Type':'application/json',
20362        'X-CSRF-Token': getCsrfToken()
20363      },
20364      credentials: 'same-origin',
20365      body: JSON.stringify({
20366        ...payload,
20367        csrf_token: getCsrfToken()
20368      }),
20369    });
20370    const data = await res.json().catch(()=>({ ok:false, error:'Gagal menambahkan ke keranjang' }));
20371    if (!res.ok || !data.ok) {
20372      const msg = data && data.error ? data.error : 'Gagal menambahkan ke keranjang';
20373      throw new Error(msg);
20374    }
20375
20376    // Update badge di navbar dengan data dari response
20377    try {
20378      const el = document.querySelector('[data-role="cart-badge"]') || document.getElementById('cartCount');
20379      if (el && data.cart && typeof data.cart.items_count === 'number') {
20380        el.textContent = String(data.cart.items_count);
20381        el.classList.toggle('is-empty', data.cart.items_count <= 0);
20382      }
20383    } catch(_){}
20384
20385    // Siarkan event global (dipakai cart-badge.js dan halaman lain)
20386    try {
20387      document.dispatchEvent(new CustomEvent('cart:updated', { detail: data.cart || {} }));
20388    } catch(_){}
20389
20390    // Update CartBadge dengan data dari response (lebih reliable)
20391    try { 
20392      if (window.CartBadge && window.CartBadge.set) {
20393        window.CartBadge.set(data.cart.items_count||0);
20394      }
20395    } catch(_){}
20396
20397    // Tambahan: refresh cart counter setelah delay kecil untuk memastikan data tersinkronisasi
20398    setTimeout(() => {
20399      if (window.updateCartCounter) {
20400        window.updateCartCounter();
20401      }
20402    }, 100);
20403
20404    return data;
20405  }
20406
20407  // Function to update cart counter from server
20408  async function updateCartCounter(){
20409    try {
20410      const res = await fetch('/api/cart/get.php', { credentials:'same-origin' });
20411      const data = await res.json().catch(()=>({ok:false}));
20412      if (data.ok && typeof data.items_count === 'number') {
20413        const el = document.querySelector('[data-role="cart-badge"]') || document.getElementById('cartCount');
20414        if (el) {
20415          el.textContent = String(data.items_count);
20416          el.classList.toggle('is-empty', data.items_count <= 0);
20417        }
20418        // Dispatch event
20419        try {
20420          document.dispatchEvent(new CustomEvent('cart:updated', { detail: { items_count: data.items_count } }));
20421        } catch(_){}
20422      }
20423    } catch (err) {
20424      console.log('Could not update cart counter:', err);
20425    }
20426  }
20427
20428  // Function to manually merge guest cart (if needed)
20429  async function mergeGuestCart(){
20430    try {
20431      const res = await fetch('/api/cart/merge-guest.php', {
20432        method: 'POST',
20433        headers: { 
20434          'Content-Type': 'application/json',
20435          'X-CSRF-Token': getCsrfToken()
20436        },
20437        credentials: 'same-origin',
20438        body: JSON.stringify({ csrf_token: getCsrfToken() })
20439      });
20440      const data = await res.json().catch(()=>({ok:false}));
20441
20442      if (data.ok && data.merged_items > 0) {
20443        console.log(`Guest cart merged: ${data.merged_items} items`);
20444        // Update cart counter after merge
20445        await updateCartCounter();
20446        return data;
20447      }
20448      return data;
20449    } catch (err) {
20450      console.log('Could not merge guest cart:', err);
20451      return { ok: false, error: err.message };
20452    }
20453  }
20454
20455  window.addToCart = addToCart;
20456  window.updateCartCounter = updateCartCounter;
20457  window.mergeGuestCart = mergeGuestCart;
20458
20459  // Optional: auto-bind untuk tombol apapun yang punya data-addtocart
20460  document.addEventListener('click', async (e)=>{
20461    const btn = e.target.closest('[data-addtocart]');
20462    if (!btn) return;
20463    e.preventDefault();
20464    if (btn.disabled) return;
20465
20466    const pid = parseInt(btn.getAttribute('data-pid')||'0',10);
20467    const vid = parseInt(btn.getAttribute('data-vid')||'0',10);
20468    const qty = parseInt(btn.getAttribute('data-qty')||'1',10) || 1;
20469
20470    try {
20471      const data = await addToCart(pid, qty, vid);
20472      if (window.showToast) {
20473        const meta = guessPdMeta();
20474        window.showToast({
20475          type:'success',
20476          title:'Berhasil ditambahkan',
20477          message:`<strong>${esc(meta.name)}</strong>${meta.sku?` · <em>SKU: ${esc(meta.sku)}</em>`:''}<br>Qty: ${qty}`,
20478          duration: 3800,
20479          actions: [
20480            { label:'Lihat Keranjang', onClick: ()=> { window.location.href='/keranjang'; } },
20481            { label:'Lanjut Belanja', variant:'alt', onClick: ()=>{} }
20482          ]
20483        });
20484      }
20485    } catch (err) {
20486      if (window.showToast) {
20487        window.showToast({ type:'warn', title:'Gagal', message: String(err?.message||'Gagal menambahkan'), duration: 4200 });
20488      } else {
20489        console.error(err);
20490      }
20491    }
20492  });
20493})();
20494</script>
vendor: 2 bytes, line 20494
20494
20495<script data-inlined="mega-menu-tiles.js">
20496// Tile Mega Menu — Enhanced 2025 (Stagger animations, product counts, loading states)
20497// Includes safe-area hover handling (no backdrop flicker)
20498
20499(function(){
20500  const trigger   = document.querySelector('[data-mm2="trigger"]');
20501  const panel     = document.querySelector('[data-mm2="panel"]');
20502  const topView   = document.querySelector('[data-mm2="top"]');
20503  const detailView= document.querySelector('[data-mm2="detail"]');
20504  const backdrop  = document.querySelector('[data-mm2="backdrop"]');
20505  if (!trigger || !panel || !topView || !detailView) return;
20506
20507  const apiUrl = '/api/categories_tree.php?top=16&child=18&grand=14';
20508  const isDesktop = () => window.matchMedia('(min-width: 961px)').matches;
20509
20510  let data = [];
20511  let hoverTimer, closeTimer;
20512  let isLoading = false;
20513
20514  const esc = s => (s+'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
20515
20516  // Mobile close button HTML
20517  const mobileCloseBtn = () => `
20518    <button type="button" class="mm2-mobile-close" data-mm2="close" aria-label="Tutup menu">
20519      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
20520        <line x1="18" y1="6" x2="6" y2="18"></line>
20521        <line x1="6" y1="6" x2="18" y2="18"></line>
20522      </svg>
20523    </button>
20524  `;
20525
20526  // Category-specific gradient colors (16 unique combinations)
20527  const categoryGradients = [
20528    { from: '#e0e7ff', to: '#fff', border: '#c7d2fe', color: '#4f46e5' },  // Indigo
20529    { from: '#dbeafe', to: '#fff', border: '#bfdbfe', color: '#1e40af' },  // Blue
20530    { from: '#d1fae5', to: '#fff', border: '#a7f3d0', color: '#059669' },  // Green
20531    { from: '#fef3c7', to: '#fff', border: '#fde68a', color: '#d97706' },  // Amber
20532    { from: '#fce7f3', to: '#fff', border: '#fbcfe8', color: '#db2777' },  // Pink
20533    { from: '#e9d5ff', to: '#fff', border: '#d8b4fe', color: '#9333ea' },  // Purple
20534    { from: '#ffedd5', to: '#fff', border: '#fed7aa', color: '#ea580c' },  // Orange
20535    { from: '#ccfbf1', to: '#fff', border: '#99f6e4', color: '#0d9488' },  // Teal
20536    { from: '#fef9c3', to: '#fff', border: '#fef08a', color: '#ca8a04' },  // Yellow
20537    { from: '#ffe4e6', to: '#fff', border: '#fecdd3', color: '#e11d48' },  // Rose
20538    { from: '#e0f2fe', to: '#fff', border: '#bae6fd', color: '#0284c7' },  // Sky
20539    { from: '#f3e8ff', to: '#fff', border: '#e9d5ff', color: '#a855f7' },  // Violet
20540    { from: '#dcfce7', to: '#fff', border: '#bbf7d0', color: '#16a34a' },  // Emerald
20541    { from: '#fef2f2', to: '#fff', border: '#fecaca', color: '#dc2626' },  // Red
20542    { from: '#eff6ff', to: '#fff', border: '#dbeafe', color: '#2563eb' },  // Blue-600
20543    { from: '#f5f3ff', to: '#fff', border: '#ede9fe', color: '#7c3aed' },  // Violet-600
20544  ];
20545
20546  const icon = () => `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7h5l2 3h11v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><path d="M3 7V5a2 2 0 0 1 2-2h4l2 2h6a2 2 0 0 1 2 2v3"></path></svg>`;
20547
20548  // Recent (optional future use)
20549  const REC_KEY='mm2_recent';
20550  const saveRecent=(path,label)=>{ try{
20551    let list=JSON.parse(localStorage.getItem(REC_KEY)||'[]').filter(x=>x&&x.path!==path);
20552    list.unshift({path,label,t:Date.now()}); localStorage.setItem(REC_KEY, JSON.stringify(list.slice(0,8)));
20553  }catch{} };
20554
20555  function isToSafeArea(e){
20556    const t = e && e.relatedTarget ? e.relatedTarget : null;
20557    if (!t) return false;
20558    return (trigger.contains(t) || panel.contains(t) || (backdrop && backdrop.contains(t)));
20559  }
20560
20561  function open(){ 
20562    document.body.classList.add('mm2-open'); 
20563    showTop(); 
20564    // Add entrance animation class
20565    setTimeout(() => panel.classList.add('mm2-animated'), 10);
20566  }
20567  function close(){ 
20568    document.body.classList.remove('mm2-open');
20569    panel.classList.remove('mm2-animated');
20570  }
20571  function showTop(){ 
20572    topView.classList.add('is-active'); 
20573    detailView.classList.remove('is-active'); 
20574  }
20575
20576  // Loading skeleton
20577  function showLoading() {
20578    const skeletons = Array.from({length: 12}, (_, i) => `
20579      <div class="mm2-card mm2-skeleton" style="animation-delay: ${i * 0.02}s">
20580        <span class="mm2-ico skeleton-box"></span>
20581        <span style="flex: 1;">
20582          <div class="skeleton-line" style="width: 60%; height: 16px; margin-bottom: 6px;"></div>
20583          <div class="skeleton-line" style="width: 40%; height: 12px;"></div>
20584        </span>
20585      </div>
20586    `).join('');
20587    topView.innerHTML = `<div class="mm2-top"><div class="mm2-grid">${skeletons}</div></div>`;
20588  }
20589
20590  // Count total products in a category tree
20591  function countProducts(node) {
20592    let count = node.product_count || 0;
20593    if (Array.isArray(node.children)) {
20594      node.children.forEach(child => {
20595        count += countProducts(child);
20596      });
20597    }
20598    return count;
20599  }
20600
20601  // Cache for fetched thumbnails to avoid duplicate requests
20602  const thumbnailCache = new Map();
20603
20604  // Fetch product thumbnails for a category
20605  function fetchCategoryThumbnails(categoryPath) {
20606    // Check cache first
20607    if (thumbnailCache.has(categoryPath)) {
20608      injectThumbnails(categoryPath, thumbnailCache.get(categoryPath));
20609      return;
20610    }
20611
20612    const url = `/api/category_products_preview.php?path=${encodeURIComponent(categoryPath)}&limit=2`;
20613
20614    fetch(url)
20615      .then(r => {
20616        if (!r.ok) throw new Error('Network error');
20617        return r.json();
20618      })
20619      .then(data => {
20620        if (data.success && data.products && data.products.length > 0) {
20621          thumbnailCache.set(categoryPath, data.products);
20622          injectThumbnails(categoryPath, data.products);
20623        } else {
20624          // No products, remove skeleton
20625          removeThumbSkeletons(categoryPath);
20626        }
20627      })
20628      .catch(err => {
20629        console.warn('Failed to load thumbnails for', categoryPath, err);
20630        removeThumbSkeletons(categoryPath);
20631      });
20632  }
20633
20634  // Inject thumbnails into the DOM
20635  function injectThumbnails(categoryPath, products) {
20636    const links = detailView.querySelectorAll(`[data-cat-path="${categoryPath}"]`);
20637    links.forEach(link => {
20638      const thumbsContainer = link.querySelector('.mm2-thumbs');
20639      if (!thumbsContainer) return;
20640
20641      const thumbsHtml = products.slice(0, 2).map(p => 
20642        `<img src="${esc(p.img)}" alt="${esc(p.name)}" class="mm2-thumb" title="${esc(p.name)}" loading="lazy">`
20643      ).join('');
20644
20645      thumbsContainer.innerHTML = thumbsHtml;
20646      thumbsContainer.removeAttribute('data-loading');
20647    });
20648  }
20649
20650  // Remove skeleton loaders when no products
20651  function removeThumbSkeletons(categoryPath) {
20652    const links = detailView.querySelectorAll(`[data-cat-path="${categoryPath}"]`);
20653    links.forEach(link => {
20654      const thumbsContainer = link.querySelector('.mm2-thumbs');
20655      if (thumbsContainer) {
20656        thumbsContainer.remove();
20657      }
20658    });
20659  }
20660
20661  function showDetail(node){
20662    const title = node.label.split('/')[0];
20663    const children = Array.isArray(node.children) ? node.children : [];
20664    const hasChildren = children.length > 0;
20665
20666    const subnav = hasChildren ? `
20667      <nav class="mm2-subnav" role="tablist">
20668        ${children.map((c2,i)=>{
20669          const t = c2.label.split('/').slice(-1)[0];
20670          return `<button class="mm2-subitem${i===0?' active':''}" role="tab" data-idx="${i}" aria-selected="${i===0?'true':'false'}">${esc(t)}</button>`;
20671        }).join('')}
20672      </nav>` : '';
20673
20674    const subcontent = `<div class="mm2-subcontent" data-mm2="subcontent"></div>`;
20675
20676    detailView.innerHTML = `
20677      <div class="mm2-detail">
20678        <div class="mm2-detail-header">
20679          <button class="mm2-back" type="button" data-mm2="back" aria-label="Kembali">
20680            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 18 9 12 15 6"></polyline></svg>
20681          </button>
20682          <div class="mm2-detail-title">${esc(title)}</div>
20683        </div>
20684
20685        ${hasChildren ? `
20686          <div class="mm2-detail-layout">
20687            ${subnav}
20688            ${subcontent}
20689          </div>` : `
20690          <div class="mm2-links">
20691            <a class="mm2-link mm2-seeall" href="/product/kategori/?kategori_path=${encodeURIComponent(node.path)}" data-mm2="nav" data-path="${esc(node.path)}" data-label="${esc(node.label)}">
20692              <strong>Lihat semua ${esc(title)}</strong>
20693            </a>
20694          </div>
20695        `}
20696      </div>
20697    `;
20698
20699    topView.classList.remove('is-active');
20700    detailView.classList.add('is-active');
20701
20702    if (hasChildren) {
20703      const subEl  = detailView.querySelector('.mm2-subnav');
20704      const contEl = detailView.querySelector('[data-mm2="subcontent"]');
20705      let activeIdx = 0;
20706
20707      const renderSubgrid = (idx) => {
20708        const c2 = children[idx]; if (!c2) { contEl.innerHTML=''; return; }
20709        const title2 = c2.label.split('/').slice(-1)[0];
20710        const l3 = Array.isArray(c2.children) ? c2.children : [];
20711        const l3Html = l3.map(c3 => `
20712          <a class="mm2-link" href="/product/kategori/?kategori_path=${encodeURIComponent(c3.path)}" data-mm2="nav" data-path="${esc(c3.path)}" data-label="${esc(c3.label)}" data-cat-path="${esc(c3.path)}">
20713            <span class="mm2-link-text">${esc(c3.label.split('/').slice(-1)[0])}</span>
20714            <span class="mm2-thumbs" data-loading="true">
20715              <span class="mm2-thumb-skeleton"></span>
20716              <span class="mm2-thumb-skeleton"></span>
20717            </span>
20718          </a>`).join('');
20719
20720        contEl.innerHTML = `
20721          <div class="mm2-links">
20722            <a class="mm2-link mm2-seeall" href="/product/kategori/?kategori_path=${encodeURIComponent(c2.path)}" data-mm2="nav" data-path="${esc(c2.path)}" data-label="${esc(c2.label)}">
20723              <strong>Lihat semua ${esc(title2)}</strong>
20724            </a>
20725            ${l3Html}
20726          </div>
20727        `;
20728
20729        // Fetch product thumbnails for each level-3 category (desktop only)
20730        if (isDesktop()) {
20731          setTimeout(() => {
20732            l3.forEach(c3 => {
20733              fetchCategoryThumbnails(c3.path);
20734            });
20735          }, 100);
20736        }
20737      };
20738
20739      renderSubgrid(activeIdx);
20740
20741      // Desktop: hover ganti konten; Mobile & Desktop: click pilih
20742      subEl.addEventListener('mousemove', (e)=>{
20743        if (!isDesktop()) return;
20744        const it = e.target.closest('.mm2-subitem'); if (!it) return;
20745        const idx = +it.dataset.idx; if (!Number.isInteger(idx) || idx===activeIdx) return;
20746        subEl.querySelectorAll('.mm2-subitem').forEach(el=>el.classList.remove('active'));
20747        it.classList.add('active');
20748        subEl.querySelectorAll('.mm2-subitem').forEach(el=>el.setAttribute('aria-selected', el===it ? 'true':'false'));
20749        activeIdx = idx;
20750        renderSubgrid(activeIdx);
20751      });
20752
20753      subEl.addEventListener('click', (e)=>{
20754        const it = e.target.closest('.mm2-subitem'); if (!it) return;
20755        const idx = +it.dataset.idx; if (!Number.isInteger(idx)) return;
20756        subEl.querySelectorAll('.mm2-subitem').forEach(el=>el.classList.remove('active'));
20757        it.classList.add('active');
20758        subEl.querySelectorAll('.mm2-subitem').forEach(el=>el.setAttribute('aria-selected', el===it ? 'true':'false'));
20759        activeIdx = idx;
20760        renderSubgrid(activeIdx);
20761      });
20762
20763      // Delegate link clicks to add recent
20764      detailView.addEventListener('click', (e)=>{
20765        const nav = e.target.closest('[data-mm2="nav"]');
20766        if (nav) { saveRecent(nav.dataset.path, nav.dataset.label); }
20767      });
20768    }
20769  }
20770
20771  function buildTop(){
20772    const html = data.map((n, idx) => {
20773      const title = n.label.split('/')[0];
20774      const childCount = (n.children||[]).length;
20775      const productCount = countProducts(n);
20776      const gradient = categoryGradients[idx % categoryGradients.length];
20777
20778      // Dynamic subtitle based on content - shorter for mobile
20779      let sub = '';
20780      if (productCount > 0) {
20781        sub = `${productCount} produk`;
20782        if (childCount > 0) sub += ` · ${childCount} kat`;
20783      } else if (childCount > 0) {
20784        sub = `${childCount} kategori`;
20785      } else {
20786        sub = 'Lihat semua';
20787      }
20788
20789      const iconStyle = `background: linear-gradient(135deg, ${gradient.from}, ${gradient.to}); border-color: ${gradient.border}; color: ${gradient.color};`;
20790
20791      return `<a href="/product/kategori/?kategori_path=${encodeURIComponent(n.path)}" class="mm2-card" data-mm2="card" data-path="${esc(n.path)}" data-label="${esc(n.label)}">
20792        <span class="mm2-ico" style="${iconStyle}">${icon()}</span>
20793        <span style="flex: 1;">
20794          <div class="mm2-title">${esc(title)}</div>
20795          <div class="mm2-sub">${esc(sub)}</div>
20796        </span>
20797      </a>`;
20798    }).join('');
20799    const closeBtnHtml = !isDesktop() ? mobileCloseBtn() : '';
20800    topView.innerHTML = `<div class="mm2-top">${closeBtnHtml}<div class="mm2-grid">${html}</div></div>`;
20801  }
20802
20803  // Open/close with safe-area
20804  trigger.addEventListener('click', (e)=>{ e.preventDefault(); document.body.classList.contains('mm2-open') ? close() : open(); });
20805  trigger.addEventListener('pointerenter', ()=>{ if (isDesktop()) { clearTimeout(closeTimer); hoverTimer=setTimeout(open, 80); }});
20806  trigger.addEventListener('pointerleave', (e)=>{ if (isDesktop() && !isToSafeArea(e)) { clearTimeout(hoverTimer); closeTimer=setTimeout(close, 200); }});
20807  panel.addEventListener('pointerenter', ()=>{ if (isDesktop()) clearTimeout(closeTimer); });
20808  panel.addEventListener('pointerleave', (e)=>{ if (isDesktop() && !isToSafeArea(e)) closeTimer=setTimeout(close, 200); });
20809  backdrop?.addEventListener('pointerenter', ()=>{ clearTimeout(closeTimer); });
20810  backdrop?.addEventListener('click', close);
20811  document.addEventListener('keydown', (e)=>{ if (e.key==='Escape') close(); });
20812
20813  // Card click → open detail (unless cmd/ctrl to open link)
20814  panel.addEventListener('click', (e)=>{
20815    const closeBtn = e.target.closest('[data-mm2="close"]'); if (closeBtn) { e.preventDefault(); close(); return; }
20816    const back = e.target.closest('[data-mm2="back"]'); if (back) { e.preventDefault(); showTop(); return; }
20817    const card = e.target.closest('[data-mm2="card"]');
20818    if (card && !e.metaKey && !e.ctrlKey) {
20819      e.preventDefault();
20820      const n = data.find(d => d.path === card.dataset.path);
20821      if (n) showDetail(n);
20822    }
20823    const nav = e.target.closest('[data-mm2="nav"]');
20824    if (nav) { saveRecent(nav.dataset.path, nav.dataset.label); }
20825  });
20826
20827  // Swipe-down gesture for mobile (bottom sheet close)
20828  if (!isDesktop()) {
20829    let startY = 0, currentY = 0, isDragging = false, startTime = 0;
20830    const SWIPE_THRESHOLD = 80;
20831    const VELOCITY_THRESHOLD = 0.3;
20832
20833    panel.addEventListener('touchstart', (e) => {
20834      if (!document.body.classList.contains('mm2-open')) return;
20835      const touch = e.touches[0];
20836      const scrollableContent = e.target.closest('.mm2-top, .mm2-detail');
20837
20838      // Prevent drag if content is scrolled
20839      if (scrollableContent && scrollableContent.scrollTop > 5) return;
20840
20841      // Only start drag from top area or handle
20842      const rect = panel.getBoundingClientRect();
20843      if (touch.clientY - rect.top > 60) return;
20844
20845      startY = touch.clientY;
20846      currentY = startY;
20847      startTime = Date.now();
20848      isDragging = true;
20849    }, { passive: true });
20850
20851    panel.addEventListener('touchmove', (e) => {
20852      if (!isDragging) return;
20853      const touch = e.touches[0];
20854      currentY = touch.clientY;
20855      const deltaY = currentY - startY;
20856
20857      // Only allow downward swipe
20858      if (deltaY > 0) {
20859        const opacity = Math.max(0.4, 1 - (deltaY / 300));
20860        panel.style.transform = `translateX(0) translateY(${deltaY}px)`;
20861        panel.style.opacity = opacity;
20862      }
20863    }, { passive: true });
20864
20865    const endDrag = () => {
20866      if (!isDragging) return;
20867      const deltaY = currentY - startY;
20868      const deltaTime = Date.now() - startTime;
20869      const velocity = deltaY / deltaTime;
20870
20871      // Close if swiped down enough or fast swipe
20872      if (deltaY > SWIPE_THRESHOLD || velocity > VELOCITY_THRESHOLD) {
20873        close();
20874      }
20875
20876      // Reset position
20877      panel.style.transform = '';
20878      panel.style.opacity = '';
20879      isDragging = false;
20880    };
20881
20882    panel.addEventListener('touchend', endDrag, { passive: true });
20883    panel.addEventListener('touchcancel', endDrag, { passive: true });
20884  }
20885
20886  // Fetch data with loading state
20887  if (!isLoading) {
20888    isLoading = true;
20889    showLoading();
20890
20891    fetch(apiUrl, { headers: { 'Accept': 'application/json' }})
20892      .then(r => {
20893        if (!r.ok) throw new Error('Network response was not ok');
20894        return r.json();
20895      })
20896      .then(j => { 
20897        data = Array.isArray(j.data) ? j.data : []; 
20898        if (!data.length) {
20899          topView.innerHTML = `<div class="mm2-top"><div style="text-align:center;padding:40px;color:var(--mm2-fg-muted);">
20900            <p>Tidak ada kategori tersedia</p>
20901          </div></div>`;
20902          return;
20903        }
20904        buildTop();
20905        isLoading = false;
20906      })
20907      .catch(err => {
20908        console.error('Failed to load categories:', err);
20909        topView.innerHTML = `<div class="mm2-top"><div style="text-align:center;padding:40px;color:var(--mm2-fg-muted);">
20910          <p>Gagal memuat kategori. Silakan coba lagi.</p>
20911        </div></div>`;
20912        isLoading = false;
20913      });
20914  }
20915})();
20916
20917// Add skeleton animation styles
20918const style = document.createElement('style');
20919style.textContent = `
20920.mm2-skeleton {
20921  pointer-events: none;
20922  animation: pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite;
20923}
20924.skeleton-box, .skeleton-line {
20925  background: linear-gradient(90deg, #f1f5f9 25%, #e2e8f0 50%, #f1f5f9 75%);
20926  background-size: 200% 100%;
20927  animation: shimmer 1.5s infinite;
20928  border-radius: 8px;
20929}
20930@keyframes pulse {
20931  0%, 100% { opacity: 1; }
20932  50% { opacity: 0.6; }
20933}
20934@keyframes shimmer {
20935  0% { background-position: 200% 0; }
20936  100% { background-position: -200% 0; }
20937}
20938`;
20939document.head.appendChild(style);
20940</script>
20940
20941    
20941<script data-inlined="benchmark.js">
20942/**
20943 * Benchmark Data Loader
20944 * Handles AJAX fetch and display of benchmark data
20945 */
20946
20947(function() {
20948    'use strict';
20949
20950    // Wait for DOM ready
20951    if (document.readyState === 'loading') {
20952        document.addEventListener('DOMContentLoaded', initBenchmark);
20953    } else {
20954        initBenchmark();
20955    }
20956
20957    function initBenchmark() {
20958        const section = document.querySelector('.pd-benchmark-section');
20959        if (!section) return;
20960
20961        const button = document.getElementById('btn-load-benchmark');
20962        const content = document.getElementById('benchmark-content');
20963        const productId = section.dataset.productId;
20964
20965        if (!button || !content || !productId) return;
20966
20967        let isLoading = false;
20968        let dataLoaded = false;
20969
20970        button.addEventListener('click', function() {
20971            if (isLoading || dataLoaded) return;
20972
20973            loadBenchmarkData(productId, button, content);
20974        });
20975    }
20976
20977    function loadBenchmarkData(productId, button, container) {
20978        // Update button state
20979        button.disabled = true;
20980        button.classList.add('loading');
20981        const originalText = button.querySelector('.btn-text').textContent;
20982        button.querySelector('.btn-text').textContent = 'Memuat data...';
20983
20984        // Show loading state
20985        container.style.display = 'block';
20986        container.innerHTML = `
20987            <div class="benchmark-loading">
20988                <div class="benchmark-loading-spinner"></div>
20989                <p>Mengambil data benchmark...</p>
20990            </div>
20991        `;
20992
20993        // Fetch data
20994        fetch('/api/benchmark/fetch.php?product_id=' + productId)
20995            .then(function(response) {
20996                if (!response.ok) {
20997                    throw new Error('HTTP error ' + response.status);
20998                }
20999                return response.json();
21000            })
21001            .then(function(result) {
21002                if (result.success && result.data) {
21003                    renderBenchmarkData(result.data, container);
21004                    button.style.display = 'none'; // Hide button after successful load
21005                } else {
21006                    throw new Error(result.error || 'Data tidak tersedia');
21007                }
21008            })
21009            .catch(function(error) {
21010                console.error('Benchmark fetch error:', error);
21011                container.innerHTML = `
21012                    <div class="benchmark-error">
21013                        <p><strong>⚠️ Data Benchmark Tidak Tersedia</strong></p>
21014                        <p style="font-size: 14px; margin-top: 8px;">
21015                            ${error.message || 'Terjadi kesalahan saat memuat data'}
21016                        </p>
21017                        <button type="button" onclick="location.reload()" 
21018                                style="margin-top: 16px; padding: 8px 20px; background: #667eea; color: #fff; border: none; border-radius: 6px; cursor: pointer;">
21019                            Coba Lagi
21020                        </button>
21021                    </div>
21022                `;
21023
21024                // Reset button
21025                button.disabled = false;
21026                button.classList.remove('loading');
21027                button.querySelector('.btn-text').textContent = originalText;
21028            });
21029    }
21030
21031    function renderBenchmarkData(data, container) {
21032        let html = '<div class="benchmark-data-wrapper">';
21033
21034        // Chipset info if available
21035        if (data.chipset) {
21036            html += `
21037                <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 10px 16px; border-radius: 8px; margin-bottom: 12px;">
21038                    <div style="font-size: 11px; opacity: 0.85; margin-bottom: 2px;">Chipset</div>
21039                    <div style="font-size: 15px; font-weight: 600;">${escapeHtml(data.chipset)}</div>
21040                </div>
21041            `;
21042        }
21043
21044        // Main benchmark scores
21045        if (data.antutu || data.geekbench_multi || data.overall_score) {
21046            html += '<div class="benchmark-data-grid">';
21047
21048            if (data.antutu) {
21049                html += `
21050                    <div class="benchmark-card">
21051                        <div class="benchmark-card-label">ANTUTU</div>
21052                        <div class="benchmark-card-value large">${formatNumber(data.antutu)}</div>
21053                        ${getScoreBadge(data.antutu, 'antutu')}
21054                    </div>
21055                `;
21056            }
21057
21058            if (data.geekbench_multi) {
21059                html += `
21060                    <div class="benchmark-card">
21061                        <div class="benchmark-card-label">GEEKBENCH MULTI</div>
21062                        <div class="benchmark-card-value">${formatNumber(data.geekbench_multi)}</div>
21063                        ${data.geekbench_single ? '<div class="benchmark-card-subtitle">Single: ' + formatNumber(data.geekbench_single) + '</div>' : ''}
21064                    </div>
21065                `;
21066            }
21067
21068            if (data.gaming_score) {
21069                html += `
21070                    <div class="benchmark-card">
21071                        <div class="benchmark-card-label">GAMING</div>
21072                        <div class="benchmark-card-value">${data.gaming_score}<span style="font-size: 14px; opacity: 0.6;">/100</span></div>
21073                    </div>
21074                `;
21075            }
21076
21077            if (data.battery_score) {
21078                html += `
21079                    <div class="benchmark-card">
21080                        <div class="benchmark-card-label">BATTERY</div>
21081                        <div class="benchmark-card-value">${data.battery_score}<span style="font-size: 14px; opacity: 0.6;">/100</span></div>
21082                    </div>
21083                `;
21084            }
21085
21086            if (data.overall_score) {
21087                html += `
21088                    <div class="benchmark-card">
21089                        <div class="benchmark-card-label">OVERALL</div>
21090                        <div class="benchmark-card-value">${data.overall_score}<span style="font-size: 14px; opacity: 0.6;">/100</span></div>
21091                    </div>
21092                `;
21093            }
21094
21095            html += '</div>';
21096        }
21097
21098        // AI Performance Description
21099        if (data.notes) {
21100            html += `
21101                <div style="background: #f8f9fa; border-left: 3px solid #667eea; padding: 10px 14px; border-radius: 6px; margin-top: 12px;">
21102                    <div style="font-size: 11px; font-weight: 600; color: #667eea; margin-bottom: 6px;">💡 Analisis Performa</div>
21103                    <div style="font-size: 13px; color: #4a5568; line-height: 1.5;">${escapeHtml(data.notes)}</div>
21104                </div>
21105            `;
21106        }
21107
21108        // Attribution
21109        html += `
21110            <div class="benchmark-attribution">
21111                <p style="font-size: 11px; margin: 8px 0 0 0; color: #888;">Data benchmark dianalisis menggunakan AI dari spesifikasi produk</p>
21112                ${data.fetched_at ? '<p style="font-size: 10px; margin: 4px 0 0 0; color: #aaa;">Terakhir diperbarui: ' + formatDate(data.fetched_at) + '</p>' : ''}
21113            </div>
21114        `;
21115
21116        html += '</div>';
21117
21118        container.innerHTML = html;
21119    }
21120
21121    function formatNumber(num) {
21122        if (!num) return '-';
21123        return num.toString().replace(/B(?=(d{3})+(?!d))/g, '.');
21124    }
21125
21126    function escapeHtml(text) {
21127        if (!text) return '';
21128        const div = document.createElement('div');
21129        div.textContent = text;
21130        return div.innerHTML;
21131    }
21132
21133    function formatDate(dateStr) {
21134        try {
21135            const date = new Date(dateStr);
21136            const options = { year: 'numeric', month: 'long', day: 'numeric' };
21137            return date.toLocaleDateString('id-ID', options);
21138        } catch (e) {
21139            return dateStr;
21140        }
21141    }
21142
21143    function getScoreBadge(score, type) {
21144        let category = '';
21145        let label = '';
21146
21147        if (type === 'antutu') {
21148            if (score >= 500000) {
21149                category = 'excellent';
21150                label = 'Flagship';
21151            } else if (score >= 350000) {
21152                category = 'good';
21153                label = 'High-End';
21154            } else if (score >= 200000) {
21155                category = 'average';
21156                label = 'Mid-Range';
21157            } else {
21158                category = 'average';
21159                label = 'Entry-Level';
21160            }
21161        }
21162
21163        if (label) {
21164            return '<div class="benchmark-score-badge ' + category + '">' + label + '</div>';
21165        }
21166
21167        return '';
21168    }
21169})();
21170
21171</script>
21171
21172  
21172<script data-inlined="product-compare.js">
21173(function() {
21174  'use strict';
21175
21176  // State management
21177  let compareList = [];
21178  let isAnalyzing = false;
21179  let currentAnalysis = null;
21180
21181  // DOM elements
21182  const compareBtn = document.getElementById('compareBtn');
21183  const compareSidebar = document.getElementById('compareSidebar');
21184  const compareOverlay = document.getElementById('compareOverlay');
21185  const compareClose = document.getElementById('compareClose');
21186  const compareModal = document.getElementById('aiCompareModal');
21187  const compareModalClose = document.getElementById('aiCompareModalClose');
21188
21189  // Initialize
21190  document.addEventListener('DOMContentLoaded', async function() {
21191    await initializeCompare();
21192    setupEventListeners();
21193  });
21194
21195  /**
21196   * Initialize compare functionality
21197   */
21198  async function initializeCompare() {
21199    // Load compare list first before setting up buttons
21200    await loadCompareList();
21201
21202    // Setup product detail compare button if exists
21203    const productCompareBtn = document.getElementById('pd-compare-btn');
21204
21205    if (productCompareBtn) {
21206      setupProductCompareButton(productCompareBtn);
21207    }
21208
21209    // Check for existing compare list and update UI
21210    if (compareList.length > 0) {
21211      // Only update badge and buttons, no sticky bar
21212    }
21213  }
21214
21215  /**
21216   * Setup event listeners
21217   */
21218  function setupEventListeners() {
21219    // Navigation compare button
21220    if (compareBtn) {
21221      compareBtn.addEventListener('click', toggleCompareSidebar);
21222    }
21223
21224    // Sidebar controls
21225    if (compareClose) {
21226      compareClose.addEventListener('click', closeCompareSidebar);
21227    }
21228
21229    if (compareOverlay) {
21230      compareOverlay.addEventListener('click', closeCompareSidebar);
21231    }
21232
21233    // Sidebar actions
21234    const clearAllBtn = document.getElementById('compareClearAll');
21235    if (clearAllBtn) {
21236      clearAllBtn.addEventListener('click', clearAllProducts);
21237    }
21238
21239    const analyzeBtn = document.getElementById('compareAnalyze');
21240    if (analyzeBtn) {
21241      analyzeBtn.addEventListener('click', startAIAnalysis);
21242    }
21243
21244    // Modal controls
21245    if (compareModalClose) {
21246      compareModalClose.addEventListener('click', closeModal);
21247    }
21248
21249    const modalBackdrop = compareModal?.querySelector('.modal-backdrop');
21250    if (modalBackdrop) {
21251      modalBackdrop.addEventListener('click', closeModal);
21252    }
21253
21254    // Modal actions
21255    const retryBtn = document.getElementById('aiRetry');
21256    if (retryBtn) {
21257      retryBtn.addEventListener('click', startAIAnalysis);
21258    }
21259
21260    // Removed share and add to cart buttons - replaced with AI disclaimer
21261  }
21262
21263  /**
21264   * Setup product detail compare button
21265   */
21266  function setupProductCompareButton(button) {
21267    const productId = parseInt(button.dataset.productId);
21268    if (!productId) return;
21269
21270    // Check if product is already in compare list
21271    updateProductCompareButtonState(button, productId);
21272
21273    button.addEventListener('click', async function() {
21274      // Get current variant ID (if any)
21275      const variantIdElement = document.getElementById('pd-variant-id');
21276      const variantId = variantIdElement ? parseInt(variantIdElement.value) || 0 : 0;
21277
21278      // Update button's variant data for current selection
21279      button.dataset.variantId = variantId.toString();
21280
21281      await toggleProductInCompare(productId, button, variantId);
21282    });
21283  }
21284
21285  /**
21286   * Toggle product in compare list
21287   */
21288  async function toggleProductInCompare(productId, button, variantId = 0) {
21289    // Check by product ID (variants of same product treated as same item)
21290    const isInCompare = compareList.some(p => parseInt(p.id) === parseInt(productId));
21291
21292    try {
21293      if (isInCompare) {
21294        await removeFromCompare(productId);
21295        showToast('Produk dihapus dari perbandingan', 'success');
21296      } else {
21297        await addToCompare(productId, variantId);
21298        showToast('Produk ditambahkan ke perbandingan', 'success');
21299
21300        // Trigger fly-to-compare animation
21301        triggerFlyToCompareAnimation(button);
21302      }
21303
21304      updateProductCompareButtonState(button, productId);
21305      updateCompareBadge();
21306      updateAllCompareButtons();
21307
21308    } catch (error) {
21309      showToast(error.message || 'Terjadi kesalahan', 'error');
21310    }
21311  }
21312
21313  /**
21314   * Update product compare button state
21315   */
21316  function updateProductCompareButtonState(button, productId) {
21317    // Ensure both IDs are compared as integers
21318    const isInCompare = compareList.some(p => parseInt(p.id) === parseInt(productId));
21319    button.classList.toggle('added', isInCompare);
21320
21321    // Handle visibility of text elements
21322    const compareText = button.querySelector('.compare-text');
21323    const compareRemove = button.querySelector('.compare-remove');
21324    const compareNote = document.getElementById('compare-note');
21325
21326    if (compareText) {
21327      compareText.style.display = isInCompare ? 'none' : 'inline';
21328    }
21329
21330    if (compareRemove) {
21331      if (isInCompare) {
21332        compareRemove.removeAttribute('hidden');
21333        compareRemove.style.display = 'flex';
21334      } else {
21335        compareRemove.setAttribute('hidden', '');
21336        compareRemove.style.display = 'none';
21337      }
21338    }
21339
21340    // Update info note
21341    if (compareNote) {
21342      if (isInCompare) {
21343        const currentCount = compareList.length;
21344        compareNote.textContent = `Produk sudah ada di daftar perbandingan (${currentCount}/5). Klik tombol untuk menghapus.`;
21345      } else {
21346        compareNote.textContent = 'Bandingkan hingga 5 panduan dengan bantuan AI';
21347      }
21348    }
21349
21350    // Inline compare preview removed - keeping it simple
21351  }
21352
21353  /**
21354   * Add product to compare list
21355   */
21356  async function addToCompare(productId, variantId = 0) {
21357    const requestData = { product_id: productId };
21358    if (variantId > 0) {
21359      requestData.variant_id = variantId;
21360    }
21361
21362    const response = await fetch('/api/compare/add.php', {
21363      method: 'POST',
21364      headers: {
21365        'Content-Type': 'application/json',
21366      },
21367      body: JSON.stringify(requestData),
21368      credentials: 'same-origin'
21369    });
21370
21371    const result = await response.json();
21372
21373    if (!result.success) {
21374      throw new Error(result.error || 'Gagal menambahkan produk');
21375    }
21376
21377    // Send Google Analytics event for add to compare
21378    if (typeof gtag === 'function') {
21379      gtag('event', 'add_to_compare', {
21380        event_category: 'Product Compare',
21381        event_label: 'Product Added to Compare',
21382        product_id: productId,
21383        variant_id: variantId || 0,
21384        custom_map: {
21385          custom_parameter_1: 'compare_add_product'
21386        }
21387      });
21388    }
21389
21390    await loadCompareList();
21391    return result;
21392  }
21393
21394  /**
21395   * Remove product from compare list
21396   */
21397  async function removeFromCompare(productId) {
21398    const response = await fetch('/api/compare/remove.php', {
21399      method: 'POST',
21400      headers: {
21401        'Content-Type': 'application/json',
21402      },
21403      body: JSON.stringify({ product_id: productId }),
21404      credentials: 'same-origin'
21405    });
21406
21407    const result = await response.json();
21408
21409    if (!result.success) {
21410      throw new Error(result.error || 'Gagal menghapus produk');
21411    }
21412
21413    // Send Google Analytics event for remove from compare
21414    if (typeof gtag === 'function') {
21415      gtag('event', 'remove_from_compare', {
21416        event_category: 'Product Compare',
21417        event_label: 'Product Removed from Compare',
21418        product_id: productId,
21419        custom_map: {
21420          custom_parameter_1: 'compare_remove_product'
21421        }
21422      });
21423    }
21424
21425    await loadCompareList();
21426    return result;
21427  }
21428
21429  /**
21430   * Clear all products from compare list
21431   */
21432  async function clearAllProducts() {
21433    if (compareList.length === 0) return;
21434
21435    // Show confirmation modal
21436    const confirmed = await showConfirmModal({
21437      title: 'Hapus Semua Produk',
21438      message: 'Apakah Anda yakin ingin menghapus semua produk dari daftar perbandingan?',
21439      confirmText: 'Hapus Semua',
21440      cancelText: 'Batal',
21441      type: 'danger'
21442    });
21443
21444    if (!confirmed) return;
21445
21446    try {
21447      const response = await fetch('/api/compare/clear.php', {
21448        method: 'POST',
21449        credentials: 'same-origin'
21450      });
21451
21452      const result = await response.json();
21453
21454      if (!result.success) {
21455        throw new Error(result.error || 'Gagal menghapus produk');
21456      }
21457
21458      // Send Google Analytics event for clear all products
21459      if (typeof gtag === 'function') {
21460        gtag('event', 'compare_clear_all', {
21461          event_category: 'Product Compare',
21462          event_label: 'Clear All Products',
21463          products_count: compareList.length,
21464          custom_map: {
21465            custom_parameter_1: 'compare_clear_action'
21466          }
21467        });
21468      }
21469
21470      await loadCompareList();
21471      showToast('Semua produk dihapus dari perbandingan', 'success');
21472
21473    } catch (error) {
21474      showToast(error.message || 'Terjadi kesalahan', 'error');
21475    }
21476  }
21477
21478  /**
21479   * Load compare list from server
21480   */
21481  async function loadCompareList() {
21482    try {
21483      const response = await fetch('/api/compare/get.php', {
21484        credentials: 'same-origin'
21485      });
21486
21487      const result = await response.json();
21488
21489      if (result.success) {
21490        compareList = result.products || [];
21491        updateCompareSidebar();
21492        updateCompareBadge();
21493        updateAllCompareButtons();
21494      }
21495
21496    } catch (error) {
21497      // Silently handle error - compare functionality will work with empty list
21498    }
21499  }
21500
21501  /**
21502   * Update compare sidebar content
21503   */
21504  function updateCompareSidebar() {
21505    const emptyState = document.getElementById('compareEmpty');
21506    const listContainer = document.getElementById('compareList');
21507    const actionsContainer = document.getElementById('compareActions');
21508    const countElement = document.getElementById('compareCountSidebar');
21509
21510    if (!emptyState || !listContainer || !actionsContainer || !countElement) return;
21511
21512    // Update count
21513    countElement.textContent = `(${compareList.length})`;
21514
21515    if (compareList.length === 0) {
21516      emptyState.style.display = 'block';
21517      listContainer.style.display = 'none';
21518      actionsContainer.style.display = 'none';
21519    } else {
21520      emptyState.style.display = 'none';
21521      listContainer.style.display = 'block';
21522      actionsContainer.style.display = 'flex';
21523
21524      // Render product list
21525      listContainer.innerHTML = compareList.map(product => {
21526        // Display variant name if available
21527        let displayName = product.name;
21528        if (product.combination_name) {
21529          displayName += ` (${product.combination_name})`;
21530        }
21531
21532        // Use variant price if available
21533        let displayPrice = product.price;
21534        if (product.variant_price !== undefined && product.variant_price !== null) {
21535          displayPrice = product.variant_price;
21536        }
21537
21538        return `
21539          <div class="compare-item" data-product-id="${product.id}">
21540            <div class="compare-item-image">
21541              <img src="${product.image_url}" alt="${escapeHtml(displayName)}" loading="lazy">
21542            </div>
21543            <div class="compare-item-info">
21544              <div class="compare-item-name">${escapeHtml(displayName)}</div>
21545              <div class="compare-item-price">${formatPrice(displayPrice)}</div>
21546            </div>
21547            <button class="compare-item-remove" data-product-id="${product.id}" aria-label="Hapus">
21548              <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
21549                <path d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"/>
21550              </svg>
21551            </button>
21552          </div>
21553        `;
21554      }).join('');
21555
21556      // Setup event listeners for remove buttons
21557      setupRemoveButtons();
21558
21559      // Update analyze button state
21560      const analyzeBtn = document.getElementById('compareAnalyze');
21561      if (analyzeBtn) {
21562        analyzeBtn.disabled = compareList.length < 2;
21563      }
21564    }
21565  }
21566
21567  /**
21568   * Setup remove buttons event listeners
21569   */
21570  function setupRemoveButtons() {
21571    const removeButtons = document.querySelectorAll('.compare-item-remove');
21572    removeButtons.forEach(button => {
21573      button.addEventListener('click', async (e) => {
21574        e.preventDefault();
21575        const productId = parseInt(button.dataset.productId);
21576        if (productId) {
21577          await removeProduct(productId);
21578        }
21579      });
21580    });
21581  }
21582
21583  /**
21584   * Update all compare buttons on the page
21585   */
21586  function updateAllCompareButtons() {
21587    // Update product detail compare button
21588    const productBtn = document.getElementById('pd-compare-btn');
21589    if (productBtn) {
21590      const productId = parseInt(productBtn.dataset.productId);
21591      if (productId) {
21592        updateProductCompareButtonState(productBtn, productId);
21593      }
21594    }
21595
21596    // Update any other compare buttons on the page
21597    const allCompareButtons = document.querySelectorAll('[data-product-id][class*="compare"]');
21598    allCompareButtons.forEach(button => {
21599      const productId = parseInt(button.dataset.productId);
21600      if (productId && button.id !== 'pd-compare-btn') {
21601        updateProductCompareButtonState(button, productId);
21602      }
21603    });
21604  }
21605
21606  /**
21607   * Update compare badge
21608   */
21609  function updateCompareBadge() {
21610    const badge = document.getElementById('compareCount');
21611    if (!badge) return;
21612
21613    const oldCount = parseInt(badge.textContent) || 0;
21614    const newCount = compareList.length;
21615
21616    badge.textContent = newCount;
21617    badge.classList.toggle('active', newCount > 0);
21618
21619    // Animate badge if count increased
21620    if (newCount > oldCount) {
21621      animateCompareBadge();
21622      highlightCompareButton();
21623    }
21624
21625    // Sticky bar removed - keeping only fly-to-navbar animation
21626  }
21627
21628  /**
21629   * Animate compare badge when new product added
21630   */
21631  function animateCompareBadge() {
21632    const badge = document.getElementById('compareCount');
21633    if (!badge) return;
21634
21635    badge.style.transform = 'scale(1.3)';
21636    badge.style.transition = 'transform 0.3s ease';
21637
21638    setTimeout(() => {
21639      badge.style.transform = 'scale(1)';
21640    }, 300);
21641  }
21642
21643  /**
21644   * Highlight compare button temporarily
21645   */
21646  function highlightCompareButton() {
21647    const compareBtn = document.getElementById('compareBtn');
21648    if (!compareBtn) return;
21649
21650    compareBtn.classList.add('pulse-highlight');
21651
21652    setTimeout(() => {
21653      compareBtn.classList.remove('pulse-highlight');
21654    }, 3000);
21655  }
21656
21657  /**
21658   * Trigger fly-to-compare animation
21659   */
21660  function triggerFlyToCompareAnimation(sourceButton) {
21661    if (!sourceButton) {
21662      return;
21663    }
21664
21665    // Check if mobile view (bottom nav) or desktop (top nav)
21666    const isMobile = window.innerWidth <= 768;
21667
21668    // Get source position
21669    const sourceRect = sourceButton.getBoundingClientRect();
21670
21671    // Calculate target position
21672    let targetTop, targetLeft;
21673
21674    if (isMobile) {
21675      // Mobile: fly to fixed bottom position (bottom nav area)
21676      // Use fixed coordinates regardless of bottom nav visibility
21677      targetTop = window.innerHeight - 80; // Just above bottom nav area
21678      targetLeft = window.innerWidth / 2 - 30; // Center horizontally
21679    } else {
21680      // Desktop: fly to top navbar compare button
21681      const targetElement = document.getElementById('compareBtn');
21682      if (!targetElement) {
21683        return;
21684      }
21685      const targetRect = targetElement.getBoundingClientRect();
21686      targetTop = targetRect.top + 10;
21687      targetLeft = targetRect.left + 10;
21688    }
21689
21690    // Get product image for animation - try multiple selectors
21691    let productImage = document.querySelector('#pd-main-img, .product-gallery img, .pd-image img, .product-image img, .product-main-image img, img[alt*="produk"], img[src*="product"]');
21692
21693    // Create animated element
21694    const flyingElement = document.createElement('div');
21695    flyingElement.className = 'flying-product';
21696
21697    if (productImage) {
21698      flyingElement.innerHTML = `<img src="${productImage.src}" alt="Flying product">`;
21699      flyingElement.querySelector('img').style.cssText = `
21700        width: 100%;
21701        height: 100%;
21702        object-fit: cover;
21703        border-radius: 8px;
21704        box-shadow: 0 4px 12px rgba(0,0,0,0.3);
21705      `;
21706    } else {
21707      // Fallback: colored div with icon
21708      flyingElement.innerHTML = `<div class="flying-fallback">📦</div>`;
21709      flyingElement.querySelector('.flying-fallback').style.cssText = `
21710        width: 100%;
21711        height: 100%;
21712        background: #3b82f6;
21713        border-radius: 8px;
21714        display: flex;
21715        align-items: center;
21716        justify-content: center;
21717        font-size: 24px;
21718        box-shadow: 0 4px 12px rgba(0,0,0,0.3);
21719      `;
21720    }
21721
21722    flyingElement.style.cssText = `
21723      position: fixed;
21724      top: ${sourceRect.top}px;
21725      left: ${sourceRect.left}px;
21726      width: 60px;
21727      height: 60px;
21728      z-index: 9999;
21729      pointer-events: none;
21730      opacity: 1;
21731      transform: scale(1);
21732    `;
21733
21734    document.body.appendChild(flyingElement);
21735
21736    // Use Web Animations API for more reliable animation
21737    flyingElement.animate([
21738      // Keyframe 1: Initial state
21739      {
21740        top: `${sourceRect.top}px`,
21741        left: `${sourceRect.left}px`,
21742        transform: 'scale(1)',
21743        opacity: 1
21744      },
21745      // Keyframe 2: Final state
21746      {
21747        top: `${targetTop}px`,
21748        left: `${targetLeft}px`,
21749        transform: 'scale(0.3)',
21750        opacity: 0
21751      }
21752    ], {
21753      duration: 800,
21754      easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
21755      fill: 'forwards'
21756    });
21757
21758    // Clean up after animation completes
21759    setTimeout(() => {
21760      if (flyingElement.parentNode) {
21761        flyingElement.parentNode.removeChild(flyingElement);
21762      }
21763    }, 850);
21764  }
21765
21766  // Sticky compare bar functions removed - using only fly-to-navbar animation
21767
21768  /**
21769   * Toggle compare sidebar
21770   */
21771  function toggleCompareSidebar() {
21772    if (!compareSidebar) return;
21773
21774    const isOpen = compareSidebar.classList.contains('open');
21775
21776    if (isOpen) {
21777      closeCompareSidebar();
21778    } else {
21779      openCompareSidebar();
21780    }
21781  }
21782
21783  /**
21784   * Open compare sidebar
21785   */
21786  function openCompareSidebar() {
21787    if (!compareSidebar || !compareOverlay) return;
21788
21789    compareSidebar.classList.add('open');
21790    compareOverlay.classList.add('active');
21791    document.body.style.overflow = 'hidden';
21792
21793    // Send Google Analytics event for compare sidebar open
21794    if (typeof gtag === 'function') {
21795      gtag('event', 'compare_sidebar_open', {
21796        event_category: 'Product Compare',
21797        event_label: 'Compare Sidebar Opened',
21798        products_count: compareList.length,
21799        custom_map: {
21800          custom_parameter_1: 'compare_sidebar_view'
21801        }
21802      });
21803    }
21804  }
21805
21806  /**
21807   * Close compare sidebar
21808   */
21809  function closeCompareSidebar() {
21810    if (!compareSidebar || !compareOverlay) return;
21811
21812    compareSidebar.classList.remove('open');
21813    compareOverlay.classList.remove('active');
21814    document.body.style.overflow = '';
21815  }
21816
21817  /**
21818   * Start AI analysis
21819   */
21820  async function startAIAnalysis() {
21821    if (compareList.length < 2) {
21822      showToast('Minimal 2 produk diperlukan untuk analisis AI', 'warning');
21823      return;
21824    }
21825
21826    if (isAnalyzing) return;
21827
21828    // Send Google Analytics event for AI Analysis start
21829    if (typeof gtag === 'function') {
21830      gtag('event', 'ai_analysis_start', {
21831        event_category: 'Product Compare',
21832        event_label: 'AI Analysis Button Click',
21833        products_count: compareList.length,
21834        product_ids: compareList.map(p => p.id).join(','),
21835        custom_map: {
21836          custom_parameter_1: 'compare_ai_analysis'
21837        }
21838      });
21839    }
21840
21841    // Check if products are in similar categories
21842    const mismatchInfo = await checkProductCategoryMismatch();
21843    if (mismatchInfo.shouldWarn) {
21844      const categoryList = Array.from(mismatchInfo.categories).join(', ');
21845      const confirmed = await showConfirmModal({
21846        title: 'Produk Berbeda Kategori',
21847        message: `Produk yang Anda pilih berasal dari kategori yang berbeda: ${categoryList}. AI tetap bisa menganalisis, tapi perbandingan mungkin kurang relevan. Lanjutkan analisis?`,
21848        confirmText: 'Ya, Analisis Tetap',
21849        cancelText: 'Batal',
21850        type: 'warning'
21851      });
21852
21853      if (!confirmed) return;
21854    }
21855
21856    openModal();
21857    showLoadingState();
21858    isAnalyzing = true;
21859
21860    try {
21861      const response = await fetch('/api/compare/ai-analysis.php', {
21862        method: 'POST',
21863        credentials: 'same-origin'
21864      });
21865
21866      const result = await response.json();
21867
21868      if (!result.success) {
21869        throw new Error(result.error || 'Analisis AI gagal');
21870      }
21871
21872      currentAnalysis = result;
21873      showAnalysisResults(result);
21874
21875      // Send Google Analytics event for successful AI Analysis
21876      if (typeof gtag === 'function') {
21877        gtag('event', 'ai_analysis_success', {
21878          event_category: 'Product Compare',
21879          event_label: 'AI Analysis Completed',
21880          products_count: compareList.length,
21881          analysis_type: 'product_comparison',
21882          custom_map: {
21883            custom_parameter_1: 'compare_ai_success'
21884          }
21885        });
21886      }
21887
21888    } catch (error) {
21889      showErrorState(error.message);
21890
21891      // Send Google Analytics event for failed AI Analysis
21892      if (typeof gtag === 'function') {
21893        gtag('event', 'ai_analysis_error', {
21894          event_category: 'Product Compare',
21895          event_label: 'AI Analysis Failed',
21896          products_count: compareList.length,
21897          error_message: error.message,
21898          custom_map: {
21899            custom_parameter_1: 'compare_ai_error'
21900          }
21901        });
21902      }
21903    } finally {
21904      isAnalyzing = false;
21905    }
21906  }
21907
21908  /**
21909   * Show modal
21910   */
21911  function openModal() {
21912    if (!compareModal) return;
21913    compareModal.classList.add('open');
21914    document.body.style.overflow = 'hidden';
21915  }
21916
21917  /**
21918   * Close modal
21919   */
21920  function closeModal() {
21921    if (!compareModal) return;
21922    compareModal.classList.remove('open');
21923    document.body.style.overflow = '';
21924  }
21925
21926  /**
21927   * Show loading state
21928   */
21929  function showLoadingState() {
21930    const loading = document.getElementById('aiLoading');
21931    const error = document.getElementById('aiError');
21932    const results = document.getElementById('aiResults');
21933
21934    if (loading) loading.style.display = 'block';
21935    if (error) error.hidden = true;
21936    if (results) results.hidden = true;
21937  }
21938
21939  /**
21940   * Show error state
21941   */
21942  function showErrorState(message) {
21943    const loading = document.getElementById('aiLoading');
21944    const error = document.getElementById('aiError');
21945    const results = document.getElementById('aiResults');
21946    const errorMessage = document.getElementById('aiErrorMessage');
21947
21948    if (loading) loading.style.display = 'none';
21949    if (error) error.hidden = false;
21950    if (results) results.hidden = true;
21951    if (errorMessage) errorMessage.textContent = message;
21952  }
21953
21954  /**
21955   * Show analysis results
21956   */
21957  function showAnalysisResults(analysis) {
21958    const loading = document.getElementById('aiLoading');
21959    const error = document.getElementById('aiError');
21960    const results = document.getElementById('aiResults');
21961
21962    if (loading) loading.style.display = 'none';
21963    if (error) error.hidden = true;
21964    if (results) results.hidden = false;
21965
21966    const data = analysis.data;
21967
21968    // Update summary
21969    const summaryElement = document.getElementById('aiSummary');
21970    if (summaryElement) summaryElement.textContent = data.summary || '';
21971
21972    const winnerProduct = document.getElementById('winnerProduct');
21973    const winnerReason = document.getElementById('winnerReason');
21974    if (winnerProduct && winnerReason && data.winner_overall) {
21975      const winner = compareList.find(p => parseInt(p.id) === parseInt(data.winner_overall));
21976      winnerProduct.textContent = winner ? winner.name : 'Tidak diketa
21976hui';
21977      winnerReason.textContent = data.winner_reason || '';
21978    }
21979
21980    // Update comparison table
21981    updateComparisonTable(data.comparison_table);
21982
21983    // Update detailed analysis
21984    updateDetailedAnalysis(data.detailed_analysis);
21985
21986    // Update recommendations
21987    updateRecommendations(data.recommendations);
21988
21989    // Update buying tips
21990    updateBuyingTips(data.buying_tips);
21991  }
21992
21993  /**
21994   * Update comparison table
21995   */
21996  function updateComparisonTable(comparisonData) {
21997    const container = document.getElementById('comparisonTable');
21998    if (!container || !comparisonData?.categories) return;
21999
22000    // First, create product header row with names
22001    const productHeaderHtml = `
22002      <div class="comparison-category">
22003        <div class="category-header">Produk</div>
22004        <div class="category-products">
22005          ${compareList.map(product => `
22006            <div class="product-score">
22007              <div class="product-name-header">
22008                <strong>${escapeHtml(product.name)}</strong>
22009              </div>
22010              <div class="product-price">
22011                ${formatPrice(product.price)}
22012              </div>
22013            </div>
22014          `).join('')}
22015        </div>
22016      </div>
22017    `;
22018
22019    // Then create comparison categories
22020    const categoriesHtml = comparisonData.categories.map(category => `
22021      <div class="comparison-category">
22022        <div class="category-header">${escapeHtml(category.name)}</div>
22023        <div class="category-products">
22024          ${compareList.map(product => {
22025            const productData = category.products?.[product.id];
22026            return `
22027              <div class="product-score">
22028                <div class="score-value">
22029                  <span class="score-stars">${'★'.repeat(productData?.score || 0)}${'☆'.repeat(5 - (productData?.score || 0))}</span>
22030                  <span>${productData?.score || 0}/5</span>
22031                </div>
22032                <div class="score-note">${escapeHtml(productData?.note || '')}</div>
22033              </div>
22034            `;
22035          }).join('')}
22036        </div>
22037      </div>
22038    `).join('');
22039
22040    container.innerHTML = productHeaderHtml + categoriesHtml;
22041  }
22042
22043  /**
22044   * Update detailed analysis
22045   */
22046  function updateDetailedAnalysis(analysisData) {
22047    const container = document.getElementById('detailedAnalysis');
22048    if (!container || !analysisData) return;
22049
22050    container.innerHTML = compareList.map(product => {
22051      const productAnalysis = analysisData[product.id];
22052      if (!productAnalysis) return '';
22053
22054      // Use product name from API response if available, fallback to local name
22055      const productName = productAnalysis.product_name || product.name;
22056
22057      return `
22058        <div class="product-analysis">
22059          <h5>${escapeHtml(productName)}</h5>
22060
22061          ${productAnalysis.strengths ? `
22062            <div class="analysis-section">
22063              <h6 class="strengths">✅ Kelebihan</h6>
22064              <ul>
22065                ${productAnalysis.strengths.map(strength => 
22066                  `<li>${escapeHtml(strength)}</li>`
22067                ).join('')}
22068              </ul>
22069            </div>
22070          ` : ''}
22071
22072          ${productAnalysis.weaknesses ? `
22073            <div class="analysis-section">
22074              <h6 class="weaknesses">❌ Kekurangan</h6>
22075              <ul>
22076                ${productAnalysis.weaknesses.map(weakness => 
22077                  `<li>${escapeHtml(weakness)}</li>`
22078                ).join('')}
22079              </ul>
22080            </div>
22081          ` : ''}
22082
22083          ${productAnalysis.best_for ? `
22084            <div class="analysis-section">
22085              <h6 class="best-for">🎯 Cocok Untuk</h6>
22086              <p class="best-for-text">${escapeHtml(productAnalysis.best_for)}</p>
22087            </div>
22088          ` : ''}
22089        </div>
22090      `;
22091    }).filter(Boolean).join('');
22092  }
22093
22094  /**
22095   * Update recommendations
22096   */
22097  function updateRecommendations(recommendations) {
22098    if (!recommendations) return;
22099
22100    const budgetEl = document.getElementById('budgetRecommendation');
22101    const performanceEl = document.getElementById('performanceRecommendation');
22102    const balancedEl = document.getElementById('balancedRecommendation');
22103
22104    if (budgetEl && recommendations.budget_conscious) {
22105      const product = compareList.find(p => parseInt(p.id) === parseInt(recommendations.budget_conscious));
22106      budgetEl.textContent = product ? product.name : 'Tidak tersedia';
22107    }
22108
22109    if (performanceEl && recommendations.performance_focused) {
22110      const product = compareList.find(p => parseInt(p.id) === parseInt(recommendations.performance_focused));
22111      performanceEl.textContent = product ? product.name : 'Tidak tersedia';
22112    }
22113
22114    if (balancedEl && recommendations.balanced) {
22115      const product = compareList.find(p => parseInt(p.id) === parseInt(recommendations.balanced));
22116      balancedEl.textContent = product ? product.name : 'Tidak tersedia';
22117    }
22118  }
22119
22120  /**
22121   * Check if products are from different categories
22122   */
22123  async function checkProductCategoryMismatch() {
22124    if (compareList.length < 2) {
22125      return { shouldWarn: false, categories: new Set() };
22126    }
22127
22128    // Get the root categories from each product
22129    const rootCategories = new Set();
22130    const productCategories = [];
22131
22132    for (const product of compareList) {
22133      // Extract root category from the product's category_path if available
22134      const rootCategory = await getProductRootCategory(product);
22135      if (rootCategory) {
22136        rootCategories.add(rootCategory);
22137        productCategories.push({
22138          product: product.name,
22139          category: rootCategory
22140        });
22141      }
22142    }
22143
22144    // If we have more than 1 different root categories, show warning
22145    const shouldWarn = rootCategories.size > 1 && compareList.length >= 2;
22146
22147    return {
22148      shouldWarn: shouldWarn,
22149      categories: rootCategories,
22150      productCategories: productCategories
22151    };
22152  }
22153
22154  /**
22155   * Get product root category from category_path or fallback to name detection
22156   */
22157  async function getProductRootCategory(product) {
22158    // First try to get from category_path if available
22159    if (product.category_path) {
22160      const rootCategory = product.category_path.split('/')[0];
22161      return rootCategory;
22162    }
22163
22164    // If no category_path, try to fetch from product detail or use name detection
22165    const detectedCategory = detectProductCategory(product.name);
22166    return detectedCategory;
22167  }
22168
22169  /**
22170   * Detect product category from name (fallback method)
22171   */
22172  function detectProductCategory(productName) {
22173    const name = productName.toLowerCase();
22174
22175    // Map keywords to actual root categories from the API structure
22176    const categoryMappings = {
22177      // Komputer & Laptop
22178      'Komputer & Laptop': ['laptop', 'notebook', 'macbook', 'thinkpad', 'gaming laptop', 'desktop', 'pc', 'mini pc', 'all-in-one', 'server'],
22179
22180      // Printer & Scanner  
22181      'Printer & Scanner': ['printer', 'epson printer', 'canon printer', 'hp printer', 'brother printer', 'scanner', 'laser printer', 'inkjet', 'dot matrix'],
22182
22183      // Gadget
22184      'Gadget': ['smartphone', 'iphone', 'samsung phone', 'xiaomi', 'oppo', 'vivo', 'realme', 'tablet', 'ipad', 'android tablet'],
22185
22186      // Periferal
22187      'Periferal': ['monitor', 'lcd monitor', 'led monitor', 'gaming monitor', 'keyboard', 'mouse', 'gaming keyboard', 'mechanical keyboard', 'webcam'],
22188
22189      // Audio
22190      'Audio': ['headset', 'headphone', 'earphone', 'gaming headset', 'speaker', 'mikrofon', 'sound card'],
22191
22192      // Jaringan
22193      'Jaringan': ['router', 'wifi', 'access point', 'switch', 'modem', 'network'],
22194
22195      // Penyimpanan
22196      'Penyimpanan': ['ssd', 'hdd', 'nvme', 'solid state', 'hard disk', 'flash drive', 'usb drive', 'memory card'],
22197
22198      // Komponen PC
22199      'Komponen PC': ['ram', 'memory', 'ddr4', 'ddr5', 'processor', 'cpu', 'intel', 'amd', 'motherboard', 'graphics card', 'gpu', 'psu', 'power supply', 'casing'],
22200
22201      // UPS & Power
22202      'UPS & Power': ['ups', 'uninterruptible', 'stabilizer', 'power supply'],
22203
22204      // Proyektor
22205      'Proyektor': ['proyektor', 'projector', 'layar proyektor'],
22206
22207      // Security System
22208      'Security System': ['cctv', 'security camera', 'surveillance'],
22209
22210      // Aksesoris
22211      'Aksesoris': ['charger laptop', 'adaptor', 'converter', 'dock', 'hub', 'kabel', 'cable', 'power bank', 'tas laptop'],
22212
22213      // Software
22214      'Software': ['software', 'lisensi', 'license', 'antivirus'],
22215
22216      // Perlengkapan Kantor
22217      'Perlengkapan Kantor': ['fingerprint', 'finger print', 'atk', 'alat tulis']
22218    };
22219
22220    // Check which root category matches
22221    for (const [rootCategory, keywords] of Object.entries(categoryMappings)) {
22222      for (const keyword of keywords) {
22223        if (name.includes(keyword)) {
22224          return rootCategory;
22225        }
22226      }
22227    }
22228
22229    return null;
22230  }
22231
22232  /**
22233   * Update buying tips
22234   */
22235  function updateBuyingTips(tips) {
22236    const container = document.getElementById('buyingTips');
22237    if (!container || !Array.isArray(tips)) return;
22238
22239    container.innerHTML = tips.map(tip => 
22240      `<li>${escapeHtml(tip)}</li>`
22241    ).join('');
22242  }
22243
22244  // Removed share and cart functions - replaced with AI disclaimer in UI
22245
22246  /**
22247   * Remove product from compare (public method)
22248   */
22249  async function removeProduct(productId) {
22250    try {
22251      await removeFromCompare(productId);
22252      showToast('Produk dihapus dari perbandingan', 'success');
22253
22254      // Update all compare buttons
22255      updateAllCompareButtons();
22256
22257    } catch (error) {
22258      showToast(error.message || 'Terjadi kesalahan', 'error');
22259    }
22260  }
22261
22262  /**
22263   * Utility functions
22264   */
22265  function escapeHtml(text) {
22266    const div = document.createElement('div');
22267    div.textContent = text;
22268    return div.innerHTML;
22269  }
22270
22271  function formatPrice(price) {
22272    return 'Rp ' + parseInt(price).toLocaleString('id-ID');
22273  }
22274
22275  /**
22276   * Show confirmation modal using the existing modal.css design
22277   */
22278  function showConfirmModal(options = {}) {
22279    return new Promise((resolve) => {
22280      const {
22281        title = 'Konfirmasi',
22282        message = 'Apakah Anda yakin?',
22283        confirmText = 'Ya',
22284        cancelText = 'Tidak',
22285        type = 'warning'
22286      } = options;
22287
22288      // Create modal HTML
22289      const modalHTML = `
22290        <div class="modal-overlay" id="confirmModal">
22291          <div class="modal-dialog">
22292            <div class="modal-header">
22293              <h3 class="modal-title">
22294                <div class="modal-icon ${type}">
22295                  ${getModalIcon(type)}
22296                </div>
22297                ${escapeHtml(title)}
22298              </h3>
22299            </div>
22300            <div class="modal-body">
22301              <p class="modal-message">${escapeHtml(message)}</p>
22302              <div class="modal-actions">
22303                <button type="button" class="modal-btn modal-btn-secondary" id="confirmCancel">
22304                  ${escapeHtml(cancelText)}
22305                </button>
22306                <button type="button" class="modal-btn modal-btn-${type === 'danger' ? 'danger' : 'primary'}" id="confirmOk">
22307                  ${escapeHtml(confirmText)}
22308                </button>
22309              </div>
22310            </div>
22311          </div>
22312        </div>
22313      `;
22314
22315      // Add to DOM
22316      document.body.insertAdjacentHTML('beforeend', modalHTML);
22317      const modal = document.getElementById('confirmModal');
22318      const cancelBtn = document.getElementById('confirmCancel');
22319      const okBtn = document.getElementById('confirmOk');
22320
22321      // Show modal
22322      requestAnimationFrame(() => {
22323        modal.classList.add('show');
22324      });
22325
22326      // Handle events
22327      function cleanup() {
22328        modal.remove();
22329      }
22330
22331      function handleCancel() {
22332        modal.classList.remove('show');
22333        setTimeout(() => {
22334          cleanup();
22335          resolve(false);
22336        }, 200);
22337      }
22338
22339      function handleConfirm() {
22340        modal.classList.remove('show');
22341        setTimeout(() => {
22342          cleanup();
22343          resolve(true);
22344        }, 200);
22345      }
22346
22347      cancelBtn.addEventListener('click', handleCancel);
22348      okBtn.addEventListener('click', handleConfirm);
22349
22350      // Close on overlay click
22351      modal.addEventListener('click', (e) => {
22352        if (e.target === modal) {
22353          handleCancel();
22354        }
22355      });
22356
22357      // Close on escape
22358      document.addEventListener('keydown', function escapeHandler(e) {
22359        if (e.key === 'Escape') {
22360          document.removeEventListener('keydown', escapeHandler);
22361          handleCancel();
22362        }
22363      });
22364
22365      // Focus on confirm button
22366      setTimeout(() => okBtn.focus(), 100);
22367    });
22368  }
22369
22370  /**
22371   * Get icon for modal type
22372   */
22373  function getModalIcon(type) {
22374      const icons = {
22375        danger: '🚨 ',
22376        warning: '⚠️',
22377        success: '✅',
22378        info: 'ℹ️'
22379      };
22380      return icons[type] || icons.info;
22381    }
22382
22383
22384
22385  function showToast(message, type = 'info') {
22386    // Integration with existing toast system or create simple one
22387
22388    // Simple alert fallback - replace with proper toast system
22389    if (type === 'error') {
22390      alert('Error: ' + message);
22391    }
22392  }
22393
22394  // Inline compare preview functions removed - keeping it simple and clean
22395
22396  /**
22397   * Get current product ID from page
22398   */
22399  function getCurrentProductId() {
22400    const button = document.getElementById('pd-compare-btn');
22401    return button ? parseInt(button.dataset.productId) : null;
22402  }
22403
22404  // hideStickyCompareBar function removed - no longer using sticky bar
22405
22406  // Public API
22407  window.productCompare = {
22408    removeProduct: removeProduct,
22409    addToCompare: addToCompare,
22410    loadCompareList: loadCompareList,
22411    getCompareList: function() { return compareList; },
22412    openSidebar: openCompareSidebar,
22413    startAnalysis: startAIAnalysis,
22414    // Helper function to add from external sources with variant support
22415    addProductWithVariant: async function(productId, variantId = 0) {
22416      return await addToCompare(productId, variantId);
22417    }
22418  };
22419
22420})();
22421
22422
22423
22424
22425
22426</script>
vendor: 2 bytes, line 22426
22426
22427<script data-inlined="product-card-actions.js">
22428/**
22429 * Product Card Quick Actions
22430 * Bottom sheet menu for quick add to cart & compare
22431 * Integrates with existing product-compare.js and cart.js
22432 */
22433
22434(function() {
22435  'use strict';
22436
22437  let activeSheet = null;
22438  let activeProductData = null;
22439
22440  // DOM elements (will be created dynamically)
22441  let overlay = null;
22442  let bottomSheet = null;
22443
22444  /**
22445   * Initialize on DOM ready
22446   */
22447  document.addEventListener('DOMContentLoaded', function() {
22448    createBottomSheet();
22449    attachEventListeners();
22450  });
22451
22452  /**
22453   * Create bottom sheet HTML structure
22454   */
22455  function createBottomSheet() {
22456    // Create overlay
22457    overlay = document.createElement('div');
22458    overlay.className = 'pca-overlay';
22459    overlay.addEventListener('click', closeBottomSheet);
22460
22461    // Create bottom sheet
22462    bottomSheet = document.createElement('div');
22463    bottomSheet.className = 'pca-bottom-sheet';
22464    bottomSheet.innerHTML = `
22465      <div class="pca-sheet-handle"></div>
22466      <div class="pca-sheet-product" id="pca-product-preview">
22467        <!-- Product info will be inserted here -->
22468      </div>
22469      <div class="pca-sheet-actions">
22470        <button type="button" class="pca-action-item cart" id="pca-add-cart">
22471          <svg class="pca-action-icon" viewBox="0 0 24 24" fill="currentColor">
22472            <path d="M17,18A2,2 0 0,1 19,20A2,2 0 0,1 17,22C15.89,22 15,21.1 15,20C15,18.89 15.89,18 17,18M1,2H4.27L5.21,4H20A1,1 0 0,1 21,5C21,5.17 20.95,5.34 20.88,5.5L17.3,11.97C16.96,12.58 16.3,13 15.55,13H8.1L7.2,14.63L7.17,14.75A0.25,0.25 0 0,0 7.42,15H19V17H7C5.89,17 5,16.1 5,15C5,14.65 5.09,14.32 5.24,14.04L6.6,11.59L3,4H1V2M7,18A2,2 0 0,1 9,20A2,2 0 0,1 7,22C5.89,22 5,21.1 5,20C5,18.89 5.89,18 7,18M16,11L18.78,6H6.14L8.5,11H16Z"/>
22473          </svg>
22474          <div class="pca-action-content">
22475            <div class="pca-action-label">Tambah ke Keranjang</div>
22476            <div class="pca-action-desc">Lanjut belanja produk lain</div>
22477          </div>
22478        </button>
22479
22480        <button type="button" class="pca-action-item compare" id="pca-add-compare">
22481          <svg class="pca-action-icon" viewBox="0 0 24 24" fill="currentColor">
22482            <path d="M21,9L17,5V8H10V10H17V13M7,11L3,15L7,19V16H14V14H7V11Z"/>
22483          </svg>
22484          <div class="pca-action-content">
22485            <div class="pca-action-label">Bandingkan Panduan</div>
22486            <div class="pca-action-desc">Analisis dengan AI</div>
22487          </div>
22488          <span class="pca-action-badge compare" id="pca-compare-count" style="display:none;">0</span>
22489        </button>
22490      </div>
22491    `;
22492
22493    // Append to body
22494    document.body.appendChild(overlay);
22495    document.body.appendChild(bottomSheet);
22496
22497    // Setup action buttons
22498    document.getElementById('pca-add-cart').addEventListener('click', handleAddToCart);
22499    document.getElementById('pca-add-compare').addEventListener('click', handleAddToCompare);
22500
22501    // Prevent clicks inside bottom sheet from closing it
22502    bottomSheet.addEventListener('click', function(e) {
22503      e.stopPropagation();
22504    });
22505  }
22506
22507  /**
22508   * Attach event listeners to all 3-dot buttons
22509   */
22510  function attachEventListeners() {
22511    // Use event delegation for dynamically loaded product cards
22512    document.addEventListener('click', function(e) {
22513      const btn = e.target.closest('.prod-card-actions-btn');
22514      if (btn) {
22515        e.preventDefault();
22516        e.stopPropagation();
22517        handleActionsButtonClick(btn);
22518      }
22519    });
22520
22521    // Handle ESC key
22522    document.addEventListener('keydown', function(e) {
22523      if (e.key === 'Escape' && activeSheet) {
22524        closeBottomSheet();
22525      }
22526    });
22527  }
22528
22529  /**
22530   * Handle 3-dot button click
22531   */
22532  function handleActionsButtonClick(button) {
22533    // Get product ID directly from button
22534    const productId = parseInt(button.dataset.productId) || 0;
22535    if (!productId) {
22536      console.error('Product ID not found on button:', button);
22537      showToast('Data produk tidak ditemukan', 'error');
22538      return;
22539    }
22540
22541    // Find parent card for additional data
22542    const card = button.closest('.prod-card, .pa-card');
22543    if (!card) {
22544      console.error('Product card not found');
22545      showToast('Data produk tidak ditemukan', 'error');
22546      return;
22547    }
22548
22549    // Extract product data from card
22550    const productData = extractProductData(card, productId);
22551    if (!productData) {
22552      showToast('Data produk tidak ditemukan', 'error');
22553      return;
22554    }
22555
22556    activeProductData = productData;
22557    openBottomSheet(productData);
22558  }
22559
22560  /**
22561   * Extract product data from card element
22562   */
22563  function extractProductData(card, productId) {
22564    try {
22565      // Product ID already passed from button
22566      if (!productId) {
22567        console.error('Product ID not provided');
22568        return null;
22569      }
22570      // Product ID already passed from button
22571      if (!productId) {
22572        console.error('Product ID not provided');
22573        return null;
22574      }
22575
22576      const id = productId;
22577
22578      // Get product name
22579      const nameEl = card.querySelector('.prod-name, .title');
22580      const name = nameEl ? nameEl.textContent.trim() : 'Produk';
22581
22582      // Get product image
22583      const imgEl = card.querySelector('.thumb img, img');
22584      const image = imgEl ? imgEl.src : '/assets/img/placeholder.png';
22585
22586      // Get product price and detect variants
22587      const priceEl = card.querySelector('.prod-price-final, .final, .prod-price');
22588      let price = 0;
22589      let hasVariants = false;
22590      let priceDisplay = '';
22591
22592      if (priceEl) {
22593        // Get text content and clean it
22594        let priceText = priceEl.textContent.trim();
22595        priceDisplay = priceText;
22596
22597        // Check if it's a range (indicates product has variants)
22598        if (priceText.includes('-')) {
22599          hasVariants = true;
22600          // Take the first price for calculation
22601          priceText = priceText.split('-')[0].trim();
22602        }
22603
22604        // Remove all non-numeric characters
22605        priceText = priceText.replace(/[^0-9]/g, '');
22606        price = parseInt(priceText) || 0;
22607      }
22608
22609      // Get stock status
22610      const stockDot = card.querySelector('.stock-dot');
22611      let stockStatus = 'unknown';
22612      let stockLabel = 'Cek stok';
22613
22614      if (stockDot) {
22615        if (stockDot.classList.contains('ready')) {
22616          stockStatus = 'in';
22617          stockLabel = 'Stok tersedia';
22618        } else if (stockDot.classList.contains('low')) {
22619          stockStatus = 'low';
22620          stockLabel = 'Stok terbatas';
22621        } else if (stockDot.classList.contains('out')) {
22622          stockStatus = 'out';
22623          stockLabel = 'Stok habis';
22624        }
22625      }
22626
22627      // Get category path (if available)
22628      const categoryPath = card.dataset.category || '';
22629
22630      // Get URL
22631      let url = '#';
22632      if (card.tagName === 'A') {
22633        url = card.href;
22634      } else {
22635        const link = card.querySelector('a[href*="/product/"]');
22636        if (link) url = link.href;
22637      }
22638
22639      return {
22640        id: id,
22641        name: name,
22642        image: image,
22643        price: price,
22644        priceDisplay: priceDisplay,
22645        hasVariants: hasVariants,
22646        stockStatus: stockStatus,
22647        stockLabel: stockLabel,
22648        categoryPath: categoryPath,
22649        url: url
22650      };
22651    } catch (error) {
22652      console.error('Error extracting product data:', error);
22653      return null;
22654    }
22655  }
22656
22657  /**
22658   * Open bottom sheet
22659   */
22660  function openBottomSheet(productData) {
22661    if (!overlay || !bottomSheet) return;
22662
22663    // Update product preview
22664    updateProductPreview(productData);
22665
22666    // Update cart button based on variant status
22667    updateCartButtonForVariant(productData);
22668
22669    // Update compare button state
22670    updateCompareButtonState(productData.id);
22671
22672    // Show overlay and sheet
22673    overlay.classList.add('active');
22674    bottomSheet.classList.add('active');
22675    document.body.style.overflow = 'hidden';
22676
22677    activeSheet = true;
22678
22679    // Google Analytics
22680    if (typeof gtag === 'function') {
22681      gtag('event', 'quick_actions_open', {
22682        event_category: 'Product Card',
22683        event_label: 'Quick Actions Menu Opened',
22684        product_id: productData.id,
22685        product_name: productData.name
22686      });
22687    }
22688  }
22689
22690  /**
22691   * Close bottom sheet
22692   */
22693  function closeBottomSheet() {
22694    if (!overlay || !bottomSheet) return;
22695
22696    overlay.classList.remove('active');
22697    bottomSheet.classList.remove('active');
22698    document.body.style.overflow = '';
22699
22700    activeSheet = false;
22701    activeProductData = null;
22702  }
22703
22704  /**
22705   * Update product preview in bottom sheet
22706   */
22707  function updateProductPreview(productData) {
22708    const preview = document.getElementById('pca-product-preview');
22709    if (!preview) return;
22710
22711    // Use full price display (shows range for variants)
22712    const priceDisplay = productData.priceDisplay || formatPrice(productData.price);
22713
22714    preview.innerHTML = `
22715      <img src="${escapeHtml(productData.image)}" 
22716           alt="${escapeHtml(productData.name)}" 
22717           class="pca-product-image"
22718           loading="lazy">
22719      <div class="pca-product-info">
22720        <h4 class="pca-product-name">${escapeHtml(productData.name)}</h4>
22721        <p class="pca-product-price">${escapeHtml(priceDisplay)}</p>
22722        <p class="pca-product-stock ${productData.stockStatus}">${productData.stockLabel}</p>
22723      </div>
22724    `;
22725  }
22726
22727  /**
22728   * Update cart button for variant products
22729   */
22730  function updateCartButtonForVariant(productData) {
22731    const cartBtn = document.getElementById('pca-add-cart');
22732    if (!cartBtn) return;
22733
22734    if (productData.hasVariants) {
22735      // Product has variants - change to "View Detail"
22736      cartBtn.dataset.hasVariants = 'true';
22737      cartBtn.dataset.productUrl = productData.url;
22738
22739      // Update icon to eye/external
22740      const iconSvg = cartBtn.querySelector('svg');
22741      if (iconSvg) {
22742        iconSvg.innerHTML = '<path d="M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9M12,17A5,5 0 0,1 7,12A5,5 0 0,1 12,7A5,5 0 0,1 17,12A5,5 0 0,1 12,17M12,4.5C7,4.5 2.73,7.61 1,12C2.73,16.39 7,19.5 12,19.5C17,19.5 21.27,16.39 23,12C21.27,7.61 17,4.5 12,4.5Z"/>';
22743      }
22744
22745      // Update text
22746      const label = cartBtn.querySelector('.pca-action-label');
22747      const desc = cartBtn.querySelector('.pca-action-desc');
22748      if (label) label.textContent = 'Lihat Detail Produk';
22749      if (desc) desc.textContent = 'Pilih varian terlebih dahulu';
22750
22751    } else {
22752      // Single product - normal cart button
22753      cartBtn.dataset.hasVariants = 'false';
22754
22755      // Restore cart icon
22756      const iconSvg = cartBtn.querySelector('svg');
22757      if (iconSvg) {
22758        iconSvg.innerHTML = '<path d="M17,18A2,2 0 0,1 19,20A2,2 0 0,1 17,22C15.89,22 15,21.1 15,20C15,18.89 15.89,18 17,18M1,2H4.27L5.21,4H20A1,1 0 0,1 21,5C21,5.17 20.95,5.34 20.88,5.5L17.3,11.97C16.96,12.58 16.3,13 15.55,13H8.1L7.2,14.63L7.17,14.75A0.25,0.25 0 0,0 7.42,15H19V17H7C5.89,17 5,16.1 5,15C5,14.65 5.09,14.32 5.24,14.04L6.6,11.59L3,4H1V2M7,18A2,2 0 0,1 9,20A2,2 0 0,1 7,22C5.89,22 5,21.1 5,20C5,18.89 5.89,18 7,18M16,11L18.78,6H6.14L8.5,11H16Z"/>';
22759      }
22760
22761      // Restore text
22762      const label = cartBtn.querySelector('.pca-action-label');
22763      const desc = cartBtn.querySelector('.pca-action-desc');
22764      if (label) label.textContent = 'Tambah ke Keranjang';
22765      if (desc) desc.textContent = 'Lanjut belanja produk lain';
22766    }
22767  }
22768
22769  /**
22770   * Update compare button state
22771   */
22772  function updateCompareButtonState(productId) {
22773    const compareBtn = document.getElementById('pca-add-compare');
22774    const compareBadge = document.getElementById('pca-compare-count');
22775
22776    if (!compareBtn) return;
22777
22778    // Check if product is in compare list (using global compareList if available)
22779    let isInCompare = false;
22780    let compareCount = 0;
22781
22782    // Try to get compare list from product-compare.js via window.productCompare
22783    if (typeof window.productCompare !== 'undefined' && typeof window.productCompare.getCompareList === 'function') {
22784      const compareList = window.productCompare.getCompareList();
22785      compareCount = compareList.length;
22786      isInCompare = compareList.some(p => parseInt(p.id) === parseInt(productId));
22787    } else {
22788      // Fallback: check badge count
22789      const mainBadge = document.getElementById('compareCount');
22790      if (mainBadge) {
22791        compareCount = parseInt(mainBadge.textContent) || 0;
22792      }
22793    }
22794
22795    // Update button state
22796    if (isInCompare) {
22797      compareBtn.classList.add('active');
22798      compareBtn.querySelector('.pca-action-label').textContent = 'Hapus dari Perbandingan';
22799      compareBtn.querySelector('.pca-action-desc').textContent = 'Sudah ada di daftar';
22800    } else {
22801      compareBtn.classList.remove('active');
22802      compareBtn.querySelector('.pca-action-label').textContent = 'Bandingkan Panduan';
22803      compareBtn.querySelector('.pca-action-desc').textContent = 'Analisis dengan AI';
22804    }
22805
22806    // Update badge
22807    if (compareBadge && compareCount > 0) {
22808      compareBadge.textContent = compareCount;
22809      compareBadge.style.display = 'inline-flex';
22810    } else if (compareBadge) {
22811      compareBadge.style.display = 'none';
22812    }
22813  }
22814
22815  /**
22816   * Handle add to cart
22817   */
22818  async function handleAddToCart() {
22819    if (!activeProductData) return;
22820
22821    const button = document.getElementById('pca-add-cart');
22822    if (!button) return;
22823
22824    // If product has variants, redirect to detail page
22825    if (activeProductData.hasVariants) {
22826      window.location.href = activeProductData.url;
22827      return;
22828    }
22829
22830    // Check stock
22831    if (activeProductData.stockStatus === 'out') {
22832      showToast('Produk sedang habis', 'warning');
22833      return;
22834    }
22835
22836    // Show loading state
22837    button.classList.add('loading');
22838    button.disabled = true;
22839
22840    // CSRF Token Helper
22841    function getCsrfToken() {
22842      return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
22843    }
22844
22845    try {
22846      // Call cart API with proper format
22847      const response = await fetch('/api/cart/add.php', {
22848        method: 'POST',
22849        headers: {
22850          'Content-Type': 'application/json',
22851          'X-CSRF-Token': getCsrfToken()
22852        },
22853        body: JSON.stringify({
22854          csrf_token: getCsrfToken(),
22855          product_id: activeProductData.id,
22856          qty: 1, // Use 'qty' not 'quantity'
22857          variant_id: 0, // Default variant
22858          meta: {
22859            name: activeProductData.name,
22860            image_url: activeProductData.image
22861          }
22862        }),
22863        credentials: 'same-origin'
22864      });
22865
22866      const result = await response.json();
22867
22868      // Check both response status and result.ok
22869      if (!response.ok || !result.ok) {
22870        throw new Error(result.error || result.message || 'Gagal menambahkan ke keranjang');
22871      }
22872
22873      // Success animation
22874      button.classList.remove('loading');
22875      button.classList.add('success');
22876
22877      // Update cart badge
22878      updateCartBadge();
22879
22880      // PlazaAnalytics tracking
22881      if (window.PlazaAnalytics) {
22882        const _slug = (activeProductData.url || '').replace(/.*/product//, '');
22883        window.PlazaAnalytics.track('add_to_cart', { slug: _slug });
22884      }
22885
22886      // Show success toast
22887      showToast('Produk ditambahkan ke keranjang', 'success');
22888
22889      // Google Analytics
22890      if (typeof gtag === 'function') {
22891        gtag('event', 'add_to_cart', {
22892          event_category: 'Ecommerce',
22893          event_label: 'Quick Add to Cart',
22894          items: [{
22895            item_id: activeProductData.id,
22896            item_name: activeProductData.name,
22897            price: activeProductData.price,
22898            quantity: 1
22899          }]
22900        });
22901      }
22902
22903      // Close sheet after short delay
22904      setTimeout(() => {
22905        closeBottomSheet();
22906        button.classList.remove('success');
22907        button.disabled = false;
22908      }, 1000);
22909
22910    } catch (error) {
22911      console.error('Add to cart error:', error);
22912      button.classList.remove('loading');
22913      button.disabled = false;
22914      showToast(error.message || 'Terjadi kesalahan', 'error');
22915    }
22916  }
22917
22918  /**
22919   * Handle add to compare
22920   */
22921  async function handleAddToCompare() {
22922    if (!activeProductData) return;
22923
22924    const button = document.getElementById('pca-add-compare');
22925    if (!button) return;
22926
22927    // Check if already in compare
22928    const isActive = button.classList.contains('active');
22929
22930    // Check 5-product limit before adding
22931    if (!isActive) {
22932      const mainBadge = document.getElementById('compareCount');
22933      const currentCount = mainBadge ? parseInt(mainBadge.textContent) || 0 : 0;
22934
22935      if (currentCount >= 5) {
22936        showToast('Maksimal 5 produk untuk perbandingan', 'warning');
22937        return;
22938      }
22939    }
22940
22941    // Show loading state
22942    button.classList.add('loading');
22943    button.disabled = true;
22944
22945    try {
22946      if (isActive) {
22947        // Remove from compare
22948        await removeFromCompare(activeProductData.id);
22949        showToast('Produk dihapus dari perbandingan', 'success');
22950      } else {
22951        // Add to compare
22952        await addToCompare(activeProductData.id);
22953        showToast('Produk ditambahkan ke perbandingan', 'success');
22954
22955        // Trigger fly animation
22956        triggerFlyToCompareAnimation();
22957      }
22958
22959      // Success animation
22960      button.classList.remove('loading');
22961      button.classList.add('success');
22962
22963      // Update compare button state
22964      updateCompareButtonState(activeProductData.id);
22965
22966      // Close sheet after short delay
22967      setTimeout(() => {
22968        closeBottomSheet();
22969        button.classList.remove('success');
22970        button.disabled = false;
22971      }, 800);
22972
22973    } catch (error) {
22974      console.error('Compare error:', error);
22975      button.classList.remove('loading');
22976      button.disabled = false;
22977      showToast(error.message || 'Terjadi kesalahan', 'error');
22978    }
22979  }
22980
22981  /**
22982   * Add to compare (calls existing API)
22983   */
22984  async function addToCompare(productId) {
22985    const response = await fetch('/api/compare/add.php', {
22986      method: 'POST',
22987      headers: {
22988        'Content-Type': 'application/json',
22989      },
22990      body: JSON.stringify({
22991        product_id: productId,
22992        variant_id: 0
22993      }),
22994      credentials: 'same-origin'
22995    });
22996
22997    const result = await response.json();
22998
22999    if (!result.success) {
23000      throw new Error(result.error || 'Gagal menambahkan produk');
23001    }
23002
23003    // Trigger compare list reload if function exists
23004    if (typeof window.productCompare !== 'undefined' && typeof window.productCompare.loadCompareList === 'function') {
23005      await window.productCompare.loadCompareList();
23006    }
23007
23008    return result;
23009  }
23010
23011  /**
23012   * Remove from compare (calls existing API)
23013   */
23014  async function removeFromCompare(productId) {
23015    const response = await fetch('/api/compare/remove.php', {
23016      method: 'POST',
23017      headers: {
23018        'Content-Type': 'application/json',
23019      },
23020      body: JSON.stringify({
23021        product_id: productId
23022      }),
23023      credentials: 'same-origin'
23024    });
23025
23026    const result = await response.json();
23027
23028    if (!result.success) {
23029      throw new Error(result.error || 'Gagal menghapus produk');
23030    }
23031
23032    // Trigger compare list reload if function exists
23033    if (typeof window.productCompare !== 'undefined' && typeof window.productCompare.loadCompareList === 'function') {
23034      await window.productCompare.loadCompareList();
23035    }
23036
23037    return result;
23038  }
23039
23040  /**
23041   * Update cart badge
23042   */
23043  function updateCartBadge() {
23044    // Try to call global cart update function if exists
23045    if (typeof window.updateCartBadge === 'function') {
23046      window.updateCartBadge();
23047    } else {
23048      // Fallback: increment badge manually
23049      const badge = document.getElementById('cartCount');
23050      if (badge) {
23051        const currentCount = parseInt(badge.textContent) || 0;
23052        badge.textContent = currentCount + 1;
23053        badge.classList.add('active');
23054      }
23055    }
23056  }
23057
23058  /**
23059   * Trigger fly to compare animation
23060   */
23061  function triggerFlyToCompareAnimation() {
23062    const compareBtn = document.getElementById('compareBtn');
23063    if (!compareBtn || !activeProductData) return;
23064
23065    // Create flying element
23066    const flyingElement = document.createElement('div');
23067    flyingElement.className = 'flying-product';
23068    flyingElement.style.cssText = `
23069      position: fixed;
23070      width: 60px;
23071      height: 60px;
23072      z-index: 99999;
23073      pointer-events: none;
23074    `;
23075
23076    flyingElement.innerHTML = `
23077      <img src="${activeProductData.image}" 
23078           style="width:100%;height:100%;object-fit:cover;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.3);">
23079    `;
23080
23081    // Position at bottom sheet
23082    const sheetRect = bottomSheet.getBoundingClientRect();
23083    const targetRect = compareBtn.getBoundingClientRect();
23084
23085    flyingElement.style.top = `${sheetRect.top + 20}px`;
23086    flyingElement.style.left = `${sheetRect.left + 20}px`;
23087
23088    document.body.appendChild(flyingElement);
23089
23090    // Animate to target
23091    requestAnimationFrame(() => {
23092      flyingElement.style.transition = 'all 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
23093      flyingElement.style.top = `${targetRect.top + 10}px`;
23094      flyingElement.style.left = `${targetRect.left + 10}px`;
23095      flyingElement.style.transform = 'scale(0.3)';
23096      flyingElement.style.opacity = '0';
23097    });
23098
23099    // Clean up
23100    setTimeout(() => {
23101      if (flyingElement.parentNode) {
23102        flyingElement.parentNode.removeChild(flyingElement);
23103      }
23104    }, 800);
23105  }
23106
23107  /**
23108   * Format price as Rupiah
23109   */
23110  function formatPrice(price) {
23111    if (!price || price === 0) return 'Rp 0';
23112    return 'Rp ' + parseInt(price).toLocaleString('id-ID');
23113  }
23114
23115  /**
23116   * Escape HTML
23117   */
23118  function escapeHtml(text) {
23119    const div = document.createElement('div');
23120    div.textContent = text;
23121    return div.innerHTML;
23122  }
23123
23124  /**
23125   * Show toast notification (uses global showToast if available)
23126   */
23127  function showToast(message, type = 'info') {
23128    if (typeof window.showToast === 'function') {
23129      window.showToast({
23130        title: type === 'success' ? 'Berhasil' : type === 'error' ? 'Gagal' : 'Info',
23131        message: message,
23132        type: type,
23133        duration: 3000
23134      });
23135    } else {
23136      // Fallback: console log
23137      console.log(`[${type.toUpperCase()}] ${message}`);
23138    }
23139  }
23140
23141  /**
23142   * Expose functions for external access
23143   */
23144  window.ProductCardActions = {
23145    openSheet: openBottomSheet,
23146    closeSheet: closeBottomSheet,
23147    updateCompareState: updateCompareButtonState
23148  };
23149
23150})();
23151
23152</script>
23152
23153    
23153<script data-inlined="product-reviews.js">
23154/**
23155 * Product Reviews - Frontend JavaScript
23156 * Handles review display, submission, and helpful voting
23157 */
23158
23159const ProductReviews = (function() {
23160  'use strict';
23161
23162  let currentPage = 1;
23163  let currentSort = 'helpful';
23164  let productId = null;
23165  let isLoading = false;
23166
23167  /**
23168   * Public API: Initialize reviews for a product
23169   */
23170  function init(pid) {
23171    productId = pid;
23172
23173    if (!productId) {
23174      console.warn('Product ID not provided to ProductReviews.init()');
23175      return;
23176    }
23177
23178    initializeReviewTab();
23179    loadReviews();
23180  }
23181
23182  /**
23183   * Initialize review tab functionality
23184   */
23185  function initializeReviewTab() {
23186    // Tab switching
23187    const tabButtons = document.querySelectorAll('.tab-btn');
23188    tabButtons.forEach(btn => {
23189      btn.addEventListener('click', function() {
23190        const tabName = this.dataset.tab;
23191        switchTab(tabName);
23192      });
23193    });
23194
23195    // Sort dropdown
23196    const sortSelect = document.getElementById('review-sort');
23197    if (sortSelect) {
23198      sortSelect.addEventListener('change', function() {
23199        currentSort = this.value;
23200        currentPage = 1;
23201        loadReviews();
23202      });
23203    }
23204
23205    // Load more button
23206    const loadMoreBtn = document.getElementById('load-more-reviews');
23207    if (loadMoreBtn) {
23208      loadMoreBtn.addEventListener('click', function() {
23209        currentPage++;
23210        loadReviews(true); // Append mode
23211      });
23212    }
23213
23214    // Write review button
23215    const writeReviewBtn = document.getElementById('write-review-btn');
23216    if (writeReviewBtn) {
23217      writeReviewBtn.addEventListener('click', showReviewModal);
23218    }
23219  }
23220
23221  /**
23222   * Switch between product tabs
23223   */
23224  function switchTab(tabName) {
23225    // Update button states
23226    document.querySelectorAll('.tab-btn').forEach(btn => {
23227      btn.classList.toggle('active', btn.dataset.tab === tabName);
23228    });
23229
23230    // Update content visibility
23231    document.querySelectorAll('.tab-content').forEach(content => {
23232      content.style.display = content.id === `${tabName}-tab` ? 'block' : 'none';
23233    });
23234  }
23235
23236  /**
23237   * Load reviews from API
23238   */
23239  async function loadReviews(append = false) {
23240    if (isLoading) return;
23241    isLoading = true;
23242
23243    const container = document.getElementById('reviews-list');
23244    const loadMoreBtn = document.getElementById('load-more-reviews');
23245
23246    // Check if review section exists (product might not have reviews section)
23247    if (!container) {
23248      console.log('Review section not found - product may not have reviews yet');
23249      isLoading = false;
23250      return;
23251    }
23252
23253    try {
23254      if (!append) {
23255        container.innerHTML = '<div class="review-loading">Memuat ulasan...</div>';
23256      } else {
23257        loadMoreBtn.disabled = true;
23258        loadMoreBtn.textContent = 'Memuat...';
23259      }
23260
23261      const response = await fetch(`/api/reviews/list.php?product_id=${productId}&sort=${currentSort}&page=${currentPage}&limit=10`);
23262      const data = await response.json();
23263
23264      if (!data.success) {
23265        throw new Error(data.message || 'Gagal memuat ulasan');
23266      }
23267
23268      // Update statistics (only on initial load)
23269      if (!append && data.stats) {
23270        updateReviewStats(data.stats);
23271      }
23272
23273      // Render reviews
23274      if (!append) {
23275        container.innerHTML = '';
23276      }
23277
23278      const reviews = data.reviews || [];
23279
23280      if (reviews.length === 0 && !append) {
23281        container.innerHTML = '<div class="no-reviews">Belum ada ulasan untuk produk ini. Jadilah yang pertama!</div>';
23282      } else {
23283        reviews.forEach(review => {
23284          container.appendChild(createReviewElement(review));
23285        });
23286      }
23287
23288      // Update load more button
23289      if (loadMoreBtn) {
23290        if (data.pagination.has_more) {
23291          loadMoreBtn.style.display = 'block';
23292          loadMoreBtn.disabled = false;
23293          loadMoreBtn.textContent = 'Muat Lebih Banyak';
23294        } else {
23295          loadMoreBtn.style.display = 'none';
23296        }
23297      }
23298
23299    } catch (error) {
23300      console.error('Load reviews error:', error);
23301      container.innerHTML = '<div class="review-error">Gagal memuat ulasan. Silakan coba lagi.</div>';
23302    } finally {
23303      isLoading = false;
23304    }
23305  }
23306
23307  /**
23308   * Update review statistics display
23309   */
23310  function updateReviewStats(stats) {
23311    const avgRatingEl = document.getElementById('avg-rating');
23312    const totalReviewsEl = document.getElementById('total-reviews');
23313    const ratingBarsContainer = document.getElementById('rating-bars');
23314
23315    if (avgRatingEl) {
23316      avgRatingEl.textContent = stats.average_rating.toFixed(1);
23317    }
23318
23319    if (totalReviewsEl) {
23320      totalReviewsEl.textContent = `${stats.total_reviews} ulasan`;
23321    }
23322
23323    // Update rating breakdown bars
23324    if (ratingBarsContainer) {
23325      ratingBarsContainer.innerHTML = '';
23326      stats.rating_breakdown.forEach(item => {
23327        const bar = document.createElement('div');
23328        bar.className = 'rating-bar';
23329        bar.innerHTML = `
23330          <span class="rating-label">${item.rating}★</span>
23331          <div class="bar-container">
23332            <div class="bar-fill" style="width: ${item.percentage}%"></div>
23333          </div>
23334          <span class="rating-count">${item.count}</span>
23335        `;
23336        ratingBarsContainer.appendChild(bar);
23337      });
23338    }
23339
23340    // Update tab count
23341    const reviewTab = document.querySelector('[data-tab="reviews"]');
23342    if (reviewTab && stats.total_reviews > 0) {
23343      reviewTab.textContent = `Ulasan (${stats.total_reviews})`;
23344    }
23345  }
23346
23347  /**
23348   * Create review element
23349   */
23350  function createReviewElement(review) {
23351    const div = document.createElement('div');
23352    div.className = 'review-item';
23353    div.dataset.reviewId = review.ID;
23354
23355    // Build stars
23356    const stars = '★'.repeat(review.RATING) + '☆'.repeat(5 - review.RATING);
23357
23358    // Build images if available
23359    let imagesHtml = '';
23360    if (review.images && review.images.length > 0) {
23361      imagesHtml = '<div class="review-images">';
23362      review.images.forEach(img => {
23363        imagesHtml += `<img src="${escapeHtml(img)}" alt="Review image" class="review-img" loading="lazy">`;
23364      });
23365      imagesHtml += '</div>';
23366    }
23367
23368    // Verified purchase badge
23369    const verifiedBadge = review.IS_VERIFIED_PURCHASE === 'Y' 
23370      ? '<span class="verified-badge">✓ Pembelian Terverifikasi</span>' 
23371      : '';
23372
23373    // Use DISPLAY_NAME if available (for anonymous reviews), otherwise use FULL_NAME
23374    const displayName = review.DISPLAY_NAME || review.FULL_NAME || 'Anonymous';
23375    const anonymousIndicator = review.IS_ANONYMOUS === 'Y' 
23376      ? '<span class="anonymous-badge" title="Nama pengguna disamark
23376an untuk privasi">🔒</span>' 
23377      : '';
23378
23379    div.innerHTML = `
23380      <div class="review-header">
23381        <div class="review-author">
23382          <strong>${escapeHtml(displayName)}</strong>
23383          ${anonymousIndicator}
23384          ${verifiedBadge}
23385        </div>
23386        <div class="review-rating">
23387          <span class="stars">${stars}</span>
23388        </div>
23389      </div>
23390      <div class="review-date">${review.created_ago}</div>
23391      <div class="review-text">${escapeHtml(review.REVIEW_TEXT).replace(/n/g, '<br>')}</div>
23392      ${imagesHtml}
23393      <div class="review-actions">
23394        <button class="btn-helpful" data-review-id="${review.ID}">
23395          <span class="helpful-icon">👍</span>
23396          <span class="helpful-text">Membantu (${review.HELPFUL_COUNT || 0})</span>
23397        </button>
23398      </div>
23399    `;
23400
23401    // Add helpful button listener
23402    const helpfulBtn = div.querySelector('.btn-helpful');
23403    helpfulBtn.addEventListener('click', () => markAsHelpful(review.ID));
23404
23405    return div;
23406  }
23407
23408  /**
23409   * Get CSRF token from meta tag
23410   */
23411  function getCsrfToken() {
23412    const metaTag = document.querySelector('meta[name="csrf-token"]');
23413    return metaTag ? metaTag.getAttribute('content') : '';
23414  }
23415
23416  /**
23417   * Mark review as helpful
23418   */
23419  async function markAsHelpful(reviewId) {
23420    // Check if logged in
23421    const isLoggedIn = document.body.classList.contains('logged-in') || 
23422                      document.querySelector('meta[name="user-logged-in"]')?.content === 'true';
23423
23424    if (!isLoggedIn) {
23425      showModal({
23426        title: 'Login Diperlukan',
23427        message: 'Silakan login terlebih dahulu untuk memberikan vote pada ulasan ini.',
23428        icon: 'info',
23429        buttons: [
23430          { text: 'Batal', className: 'modal-btn-secondary' },
23431          { text: 'Login', className: 'modal-btn-primary', callback: () => window.location.href = '/login' }
23432        ]
23433      });
23434      return;
23435    }
23436
23437    // Get current button element
23438    const reviewItem = document.querySelector(`[data-review-id="${reviewId}"]`);
23439    const helpfulBtn = reviewItem?.querySelector('.btn-helpful');
23440
23441    if (!helpfulBtn) return;
23442
23443    // Disable button during request
23444    helpfulBtn.disabled = true;
23445    const originalText = helpfulBtn.querySelector('.helpful-text').textContent;
23446
23447    try {
23448      const response = await fetch('/api/reviews/helpful.php', {
23449        method: 'POST',
23450        headers: { 
23451          'Content-Type': 'application/json',
23452          'X-CSRF-Token': getCsrfToken()
23453        },
23454        credentials: 'same-origin',
23455        body: JSON.stringify({ 
23456          csrf_token: getCsrfToken(),
23457          review_id: reviewId 
23458        })
23459      });
23460
23461      const data = await response.json();
23462
23463      if (data.success) {
23464        // Update button count immediately based on action
23465        const currentCount = parseInt(originalText.match(/d+/)?.[0] || 0);
23466        const newCount = data.action === 'added' ? currentCount + 1 : currentCount - 1;
23467        helpfulBtn.querySelector('.helpful-text').textContent = `Membantu (${newCount})`;
23468
23469        // Show success message
23470        showToast(data.message, 'success');
23471
23472        // Re-enable button
23473        helpfulBtn.disabled = false;
23474      } else {
23475        showToast(data.message, 'error');
23476        helpfulBtn.disabled = false;
23477      }
23478
23479    } catch (error) {
23480      console.error('Mark helpful error:', error);
23481      showToast('Gagal memberikan vote. Silakan coba lagi.', 'error');
23482      helpfulBtn.disabled = false;
23483    }
23484  }
23485
23486  /**
23487   * Show review submission modal (placeholder)
23488   */
23489  function showReviewModal() {
23490    // TODO: Implement review modal
23491    // For now, redirect to account page
23492    window.location.href = '/account/orders';
23493  }
23494
23495  /**
23496   * Show toast notification
23497   */
23498  function showToast(message, type = 'info') {
23499    const toast = document.createElement('div');
23500    toast.className = `review-toast toast-${type}`;
23501    toast.textContent = message;
23502
23503    document.body.appendChild(toast);
23504
23505    setTimeout(() => {
23506      toast.classList.add('show');
23507    }, 100);
23508
23509    setTimeout(() => {
23510      toast.classList.remove('show');
23511      setTimeout(() => toast.remove(), 300);
23512    }, 3000);
23513  }
23514
23515  /**
23516   * Show modal dialog
23517   * @param {Object} options - Modal options
23518   * @param {string} options.title - Modal title
23519   * @param {string} options.message - Modal message
23520   * @param {string} options.icon - Icon type (info, success, warning, danger)
23521   * @param {Array} options.buttons - Array of button objects {text, className, callback}
23522   */
23523  function showModal(options) {
23524    const {
23525      title = 'Konfirmasi',
23526      message = '',
23527      icon = 'info',
23528      buttons = [{ text: 'OK', className: 'modal-btn-primary' }]
23529    } = options;
23530
23531    // Icon emoji mapping
23532    const iconEmojis = {
23533      info: 'ℹ️',
23534      success: '✓',
23535      warning: '⚠️',
23536      danger: '✕'
23537    };
23538
23539    // Create modal HTML
23540    const modalHTML = `
23541      <div class="modal-overlay" id="helpful-vote-modal" style="display: flex !important; align-items: center !important; justify-content: center !important;">
23542        <div class="modal-dialog">
23543          <div class="modal-header">
23544            <h3 class="modal-title">
23545              <span class="modal-icon ${icon}">${iconEmojis[icon] || iconEmojis.info}</span>
23546              ${escapeHtml(title)}
23547            </h3>
23548          </div>
23549          <div class="modal-body">
23550            <p class="modal-message">${escapeHtml(message)}</p>
23551            <div class="modal-actions">
23552              ${buttons.map((btn, index) => 
23553                `<button class="modal-btn ${btn.className || 'modal-btn-secondary'}" data-index="${index}">${escapeHtml(btn.text)}</button>`
23554              ).join('')}
23555            </div>
23556          </div>
23557        </div>
23558      </div>
23559    `;
23560
23561    // Append to body
23562    const tempDiv = document.createElement('div');
23563    tempDiv.innerHTML = modalHTML;
23564    const modal = tempDiv.firstElementChild;
23565    document.body.appendChild(modal);
23566
23567    // Add event listeners to buttons
23568    buttons.forEach((btn, index) => {
23569      const btnElement = modal.querySelector(`[data-index="${index}"]`);
23570      btnElement.addEventListener('click', () => {
23571        closeModal(modal);
23572        if (btn.callback && typeof btn.callback === 'function') {
23573          btn.callback();
23574        }
23575      });
23576    });
23577
23578    // Close on overlay click
23579    modal.addEventListener('click', (e) => {
23580      if (e.target === modal) {
23581        closeModal(modal);
23582      }
23583    });
23584
23585    // Show modal with animation
23586    setTimeout(() => modal.classList.add('show'), 10);
23587  }
23588
23589  /**
23590   * Close modal dialog
23591   */
23592  function closeModal(modal) {
23593    modal.classList.remove('show');
23594    setTimeout(() => modal.remove(), 200);
23595  }
23596
23597  /**
23598   * Escape HTML for XSS protection
23599   */
23600  function escapeHtml(text) {
23601    const div = document.createElement('div');
23602    div.textContent = text;
23603    return div.innerHTML;
23604  }
23605
23606  /**
23607   * Get product ID from URL
23608   */
23609  function getProductIdFromUrl() {
23610    const match = window.location.pathname.match(//product/(d+)/);
23611    return match ? match[1] : null;
23612  }
23613
23614  // Public API
23615  return {
23616    init: init
23617  };
23618
23619})();
23620
23621</script>
23621
23622    
23622<script data-inlined="review-submission.js">
23623/**
23624 * Review Submission Modal & Handler
23625 * Handles review form display, validation, and submission
23626 */
23627
23628(function() {
23629    'use strict';
23630
23631    let currentProductId = null;
23632    let currentOrderId = null;
23633    let currentSoRefId = null;
23634    let selectedRating = 0;
23635    let uploadedImages = [];
23636
23637    // Get CSRF token from meta tag
23638    function getCsrfToken() {
23639        const metaTag = document.querySelector('meta[name="csrf-token"]');
23640        return metaTag ? metaTag.getAttribute('content') : '';
23641    }
23642
23643    // Initialize
23644    function init() {
23645        createReviewModal();
23646        attachEventListeners();
23647        loadReviewableOrders();
23648    }
23649
23650    // Create review modal HTML
23651    function createReviewModal() {
23652        const modalHTML = `
23653            <div class="modal-overlay" id="review-modal-overlay" style="display:none;">
23654                <div class="modal-content review-modal">
23655                    <div class="modal-header">
23656                        <h3 class="modal-title">Tulis Ulasan</h3>
23657                        <button type="button" class="modal-close" id="review-modal-close" aria-label="Tutup">
23658                            <svg width="20" height="20" viewBox="0 0 24 24">
23659                                <path fill="currentColor" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
23660                            </svg>
23661                        </button>
23662                    </div>
23663
23664                    <div class="modal-body">
23665                        <form id="review-form" class="review-form">
23666                            <!-- Product Info -->
23667                            <div class="review-product-info" id="review-product-info">
23668                                <img id="review-product-image" src="" alt="" class="review-product-thumb">
23669                                <div class="review-product-details">
23670                                    <h4 id="review-product-name"></h4>
23671                                    <p id="review-product-sku"></p>
23672                                </div>
23673                            </div>
23674
23675                            <!-- Rating -->
23676                            <div class="form-group">
23677                                <label class="form-label">Rating <span style="color:#ef4444;">*</span></label>
23678                                <div class="star-rating-input" id="star-rating-input">
23679                                    <button type="button" class="star-btn" data-rating="1">★</button>
23680                                    <button type="button" class="star-btn" data-rating="2">★</button>
23681                                    <button type="button" class="star-btn" data-rating="3">★</button>
23682                                    <button type="button" class="star-btn" data-rating="4">★</button>
23683                                    <button type="button" class="star-btn" data-rating="5">★</button>
23684                                </div>
23685                                <p class="rating-label" id="rating-label">Pilih rating Anda</p>
23686                            </div>
23687
23688                            <!-- Review Text -->
23689                            <div class="form-group">
23690                                <label class="form-label" for="review-text">Ulasan Anda <span style="color:#ef4444;">*</span></label>
23691                                <textarea 
23692                                    id="review-text" 
23693                                    name="review_text"
23694                                    class="form-control" 
23695                                    rows="6" 
23696                                    placeholder="Ceritakan pengalaman Anda dengan produk ini..."
23697                                    minlength="20"
23698                                    maxlength="5000"
23699                                    required></textarea>
23700                                <p class="form-hint">
23701                                    <span id="review-char-count">0</span>/5000 karakter
23702                                    <span style="color:#059669;font-weight:500;">
23703                                        💰 +1,000 point untuk ulasan ≥ 100 karakter
23704                                    </span>
23705                                </p>
23706                            </div>
23707
23708                            <!-- Image Upload -->
23709                            <div class="form-group">
23710                                <label class="form-label">Upload Foto (Opsional)</label>
23711                                <div class="image-upload-container">
23712                                    <input type="file" 
23713                                           id="review-images" 
23714                                           name="images[]" 
23715                                           accept="image/jpeg,image/jpg,image/png,image/webp"
23716                                           multiple
23717                                           style="display:none;">
23718                                    <button type="button" class="btn-upload-trigger" id="upload-trigger">
23719                                        <svg width="24" height="24" viewBox="0 0 24 24">
23720                                            <path fill="currentColor" d="M19 7v2.99s-1.99.01-2 0V7h-3s.01-1.99 0-2h3V2h2v3h3v2h-3zm-3 4V8h-3V5H5c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-8h-3zM5 19l3-4 2 3 3-4 4 5H5z"/>
23721                                        </svg>
23722                                        <span>Pilih Foto</span>
23723                                    </button>
23724                                    <p class="form-hint">
23725                                        Max 5 foto, format JPG/PNG/WebP, max 2MB per foto
23726                                        <span style="color:#059669;font-weight:500;">
23727                                            💰 +2,000 point untuk ulasan dengan foto
23728                                        </span>
23729                                    </p>
23730                                </div>
23731                                <div class="image-preview-grid" id="image-preview-grid"></div>
23732                            </div>
23733
23734                            <!-- Point Reward Info -->
23735                            <div class="point-reward-info">
23736                                <div class="pri-header">
23737                                    <span>🎁</span>
23738                                    <span>Dapatkan Point Reward</span>
23739                                </div>
23740                                <ul class="pri-list">
23741                                    <li>✅ Ulasan dasar: <strong>5,000 point</strong></li>
23742                                    <li id="pri-long-text" style="opacity:0.5;">📝 Ulasan panjang (≥100 kata): <strong>+1,000 point</strong></li>
23743                                    <li id="pri-images" style="opacity:0.5;">📸 Dengan foto: <strong>+2,000 point</strong></li>
23744                                </ul>
23745                                <div class="pri-total">
23746                                    Total Reward: <strong id="pri-total-points">5,000 point</strong>
23747                                </div>
23748                            </div>
23749
23750                            <!-- Privacy Option -->
23751                            <div class="form-group" style="margin-top:20px;padding:15px;background:#f8fafc;border-radius:8px;">
23752                                <label class="checkbox-label" style="display:flex;align-items:center;cursor:pointer;margin:0;">
23753                                    <input type="checkbox" 
23754                                           id="review-anonymous" 
23755                                           name="is_anonymous"
23756                                           style="margin-right:8px;width:18px;height:18px;cursor:pointer;">
23757                                    <span style="font-size:14px;">
23758                                        <strong>🔒 Sembunyikan nama saya</strong>
23759                                        <span style="color:#64748b;display:block;font-size:13px;margin-top:4px;" id="review-anonymous-preview">
23760                                            Nama Anda akan ditampilkan de
23760ngan disamarkan untuk menjaga privasi
23761                                        </span>
23762                                    </span>
23763                                </label>
23764                            </div>
23765
23766                            <!-- Error Alert -->
23767                            <div class="form-error" id="review-form-error" style="display:none;"></div>
23768
23769                            <!-- Submit Buttons -->
23770                            <div class="modal-footer">
23771                                <button type="button" class="btn btn-secondary" id="review-cancel">Batal</button>
23772                                <button type="submit" class="btn btn-primary" id="review-submit">
23773                                    Kirim Ulasan
23774                                </button>
23775                            </div>
23776                        </form>
23777                    </div>
23778                </div>
23779            </div>
23780        `;
23781
23782        document.body.insertAdjacentHTML('beforeend', modalHTML);
23783    }
23784
23785    // Attach event listeners
23786    function attachEventListeners() {
23787        // Modal triggers - event delegation
23788        document.addEventListener('click', function(e) {
23789            const btn = e.target.closest('.btn-write-review');
23790            if (btn) {
23791                e.preventDefault();
23792                openReviewModal(
23793                    btn.dataset.productId,
23794                    btn.dataset.orderId,
23795                    btn.dataset.productName,
23796                    btn.dataset.productImage,
23797                    btn.dataset.productSku,
23798                    btn.dataset.soRefId || null
23799                );
23800            }
23801        });
23802
23803        // Close modal
23804        setTimeout(() => {
23805            const closeBtn = document.getElementById('review-modal-close');
23806            const cancelBtn = document.getElementById('review-cancel');
23807            const overlay = document.getElementById('review-modal-overlay');
23808
23809            if (closeBtn) {
23810                closeBtn.addEventListener('click', closeReviewModal);
23811            }
23812            if (cancelBtn) {
23813                cancelBtn.addEventListener('click', closeReviewModal);
23814            }
23815            if (overlay) {
23816                overlay.addEventListener('click', function(e) {
23817                    if (e.target === overlay) closeReviewModal();
23818                });
23819            }
23820        }, 100);
23821
23822        // Star rating
23823        setTimeout(() => {
23824            const starButtons = document.querySelectorAll('.star-btn');
23825            starButtons.forEach(btn => {
23826                btn.addEventListener('click', function(e) {
23827                    e.preventDefault();
23828                    const rating = parseInt(this.dataset.rating);
23829                    setRating(rating);
23830                });
23831            });
23832        }, 100);
23833
23834        // Character count
23835        setTimeout(() => {
23836            const textarea = document.getElementById('review-text');
23837            if (textarea) {
23838                textarea.addEventListener('input', function() {
23839                    updateCharCount();
23840                    calculatePotentialPoints();
23841                });
23842            }
23843        }, 100);
23844
23845        // Image upload
23846        setTimeout(() => {
23847            const uploadTrigger = document.getElementById('upload-trigger');
23848            const fileInput = document.getElementById('review-images');
23849
23850            if (uploadTrigger && fileInput) {
23851                uploadTrigger.addEventListener('click', () => fileInput.click());
23852                fileInput.addEventListener('change', handleImageSelection);
23853            }
23854        }, 100);
23855
23856        // Form submit
23857        setTimeout(() => {
23858            const form = document.getElementById('review-form');
23859            if (form) {
23860                form.addEventListener('submit', handleSubmit);
23861            }
23862        }, 100);
23863    }
23864
23865    // Open review modal
23866    function openReviewModal(productId, orderId, productName, productImage, productSku, soRefId = null) {
23867        const overlay = document.getElementById('review-modal-overlay');
23868        if (!overlay) {
23869            console.error('Modal overlay not found!');
23870            return;
23871        }
23872
23873        currentProductId = productId;
23874        currentOrderId = orderId;
23875        currentSoRefId = soRefId;
23876        selectedRating = 0;
23877        uploadedImages = [];
23878
23879        // Set product info
23880        const nameEl = document.getElementById('review-product-name');
23881        const imageEl = document.getElementById('review-product-image');
23882        const skuEl = document.getElementById('review-product-sku');
23883
23884        if (nameEl) nameEl.textContent = productName;
23885        if (imageEl) imageEl.src = productImage || '/assets/img/no-image.png';
23886        if (skuEl) skuEl.textContent = 'SKU: ' + productSku;
23887
23888        // Update privacy preview with actual user name
23889        updatePrivacyPreview();
23890
23891        // Reset form
23892        const form = document.getElementById('review-form');
23893        const previewGrid = document.getElementById('image-preview-grid');
23894        const errorEl = document.getElementById('review-form-error');
23895
23896        if (form) form.reset();
23897        if (previewGrid) previewGrid.innerHTML = '';
23898        if (errorEl) errorEl.style.display = 'none';
23899
23900        setRating(0);
23901        updateCharCount();
23902        calculatePotentialPoints();
23903
23904        // Show modal
23905        overlay.style.display = 'flex';
23906        overlay.classList.add('show');
23907        document.body.style.overflow = 'hidden';
23908    }
23909
23910    // Close review modal
23911    function closeReviewModal() {
23912        const overlay = document.getElementById('review-modal-overlay');
23913        if (overlay) {
23914            overlay.classList.remove('show');
23915            setTimeout(() => {
23916                overlay.style.display = 'none';
23917            }, 200); // Wait for fade animation
23918        }
23919        document.body.style.overflow = '';
23920    }
23921
23922    // Set star rating
23923    function setRating(rating) {
23924        selectedRating = rating;
23925        const stars = document.querySelectorAll('.star-btn');
23926        const labels = ['', 'Sangat Buruk', 'Buruk', 'Cukup', 'Bagus', 'Sangat Bagus'];
23927
23928        stars.forEach((star, index) => {
23929            if (index < rating) {
23930                star.classList.add('active');
23931            } else {
23932                star.classList.remove('active');
23933            }
23934        });
23935
23936        document.getElementById('rating-label').textContent = labels[rating] || 'Pilih rating Anda';
23937    }
23938
23939    // Update character count
23940    function updateCharCount() {
23941        const textarea = document.getElementById('review-text');
23942        const count = textarea.value.length;
23943        document.getElementById('review-char-count').textContent = count;
23944    }
23945
23946    // Calculate potential points
23947    function calculatePotentialPoints() {
23948        const textarea = document.getElementById('review-text');
23949        if (!textarea) return;
23950
23951        const charCount = textarea.value.trim().length;
23952        const hasImages = uploadedImages.length > 0;
23953
23954        let totalPoints = 5000;
23955        let longTextActive = charCount >= 100;
23956        let imagesActive = hasImages;
23957
23958        if (longTextActive) totalPoints += 1000;
23959        if (imagesActive) totalPoints += 2000;
23960
23961        // Update UI
23962        const longTextEl = document.getElementById('pri-long-text');
23963        const imagesEl = document.getElementById('pri-images');
23964        const totalEl = document.getElementById('pri-total-points');
23965
23966        if (longTextEl) longTextEl.style.opacity = longTextActive ? '1' : '0.5';
23967        if (imagesEl) imagesEl.style.opacity = imagesActive ? '1' : '0.5';
23968        if (totalEl) totalEl.textContent = totalPoints.toLocaleString('id-ID') + ' point';
23969    }
23970
23971    // Handle image selection
23972    function handleImageSelection(e) {
23973        const files = Array.from(e.target.files);
23974
23975        if (files.length > 5) {
23976            showError('Maksimal 5 foto yang dapat diupload');
23977            e.target.value = ''; // Reset input
23978            return;
23979        }
23980
23981        uploadedImages = [];
23982        const previewGrid = document.getElementById('image-preview-grid');
23983        previewGrid.innerHTML = '';
23984
23985        files.forEach((file, index) => {
23986            // Validate file type
23987            if (!file.type.match(/image/(jpeg|jpg|png|webp)/)) {
23988                showError('Format file harus JPG, PNG, atau WebP');
23989                return;
23990            }
23991
23992            // Validate file size (2MB)
23993            if (file.size > 2 * 1024 * 1024) {
23994                showError('Ukuran file maksimal 2MB');
23995                return;
23996            }
23997
23998            uploadedImages.push(file);
23999
24000            // Create preview
24001            const reader = new FileReader();
24002            reader.onload = function(e) {
24003                const previewHTML = `
24004                    <div class="image-preview-item">
24005                        <img src="${e.target.result}" alt="Preview ${index + 1}">
24006                        <button type="button" class="remove-image" data-index="${index}" aria-label="Hapus">
24007                            <svg width="16" height="16" viewBox="0 0 24 24">
24008                                <path fill="currentColor" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
24009                            </svg>
24010                        </button>
24011                    </div>
24012                `;
24013                previewGrid.insertAdjacentHTML('beforeend', previewHTML);
24014            };
24015            reader.readAsDataURL(file);
24016        });
24017
24018        calculatePotentialPoints();
24019
24020        // Attach remove handlers
24021        setTimeout(() => {
24022            document.querySelectorAll('.remove-image').forEach(btn => {
24023                btn.addEventListener('click', function() {
24024                    const index = parseInt(this.dataset.index);
24025                    removeImage(index);
24026                });
24027            });
24028        }, 100);
24029    }
24030
24031    // Remove image
24032    function removeImage(index) {
24033        uploadedImages.splice(index, 1);
24034
24035        const previewGrid = document.getElementById('image-preview-grid');
24036        previewGrid.innerHTML = '';
24037
24038        uploadedImages.forEach((file, idx) => {
24039            const reader = new FileReader();
24040            reader.onload = function(e) {
24041                const previewHTML = `
24042                    <div class="image-preview-item">
24043                        <img src="${e.target.result}" alt="Preview ${idx + 1}">
24044                        <button type="button" class="remove-image" data-index="${idx}" aria-label="Hapus">
24045                            <svg width="16" height="16" viewBox="0 0 24 24">
24046                                <path fill="currentColor" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
24047                            </svg>
24048                        </button>
24049                    </div>
24050                `;
24051                previewGrid.insertAdjacentHTML('beforeend', previewHTML);
24052            };
24053            reader.readAsDataURL(file);
24054        });
24055
24056        calculatePotentialPoints();
24057
24058        setTimeout(() => {
24059            document.querySelectorAll('.remove-image').forEach(btn => {
24060                btn.addEventListener('click', function() {
24061                    const idx = parseInt(this.dataset.index);
24062                    removeImage(idx);
24063                });
24064            });
24065        }, 100);
24066    }
24067
24068    // Handle form submission
24069    async function handleSubmit(e) {
24070        e.preventDefault();
24071
24072        // Validate
24073        if (selectedRating === 0) {
24074            showError('Silakan pilih rating terlebih dahulu');
24075            return;
24076        }
24077
24078        const reviewText = document.getElementById('review-text').value.trim();
24079        if (reviewText.length < 20) {
24080            showError('Ulasan minimal 20 karakter');
24081            return;
24082        }
24083
24084        const submitBtn = document.getElementById('review-submit');
24085        submitBtn.disabled = true;
24086
24087        try {
24088            // Step 1: Upload images if any
24089            let imageUrls = [];
24090            if (uploadedImages.length > 0) {
24091                submitBtn.textContent = 'Mengupload foto...';
24092                imageUrls = await uploadImages();
24093                if (!imageUrls) {
24094                    // Upload failed
24095                    submitBtn.disabled = false;
24096                    submitBtn.textContent = 'Kirim Ulasan';
24097                    return;
24098                }
24099            }
24100
24101            // Step 2: Submit review with image URLs
24102            submitBtn.textContent = 'Mengirim ulasan...';
24103
24104            const payload = {
24105                csrf_token: getCsrfToken(),
24106                product_id: currentProductId,
24107                order_id: currentOrderId || 0,
24108                so_ref_id: currentSoRefId || undefined,
24109                rating: selectedRating,
24110                review_text: reviewText,
24111                is_anonymous: document.getElementById('review-anonymous').checked,
24112                images: imageUrls
24113            };
24114
24115            const response = await fetch('/api/reviews/submit.php', {
24116                method: 'POST',
24117                headers: {
24118                    'Content-Type': 'application/json',
24119                    'X-CSRF-Token': getCsrfToken()
24120                },
24121                body: JSON.stringify(payload)
24122            });
24123
24124            const result = await response.json();
24125
24126            if (result.success) {
24127                showSuccess(result.message || 'Ulasan berhasil dikirim!');
24128                closeReviewModal();
24129
24130                // Refresh reviewable orders if on account page
24131                if (typeof loadReviewableOrders === 'function') {
24132                    setTimeout(loadReviewableOrders, 500);
24133                }
24134            } else {
24135                showError(result.message || 'Gagal mengirim ulasan');
24136            }
24137        } catch (error) {
24138            console.error('Submit error:', error);
24139            showError('Terjadi kesalahan saat mengirim ulasan');
24140        } finally {
24141            submitBtn.disabled = false;
24142            submitBtn.textContent = 'Kirim Ulasan';
24143        }
24144    }
24145
24146    /**
24147     * Upload images to server
24148     * @returns {Promise<Array|null>} Array of image URLs or null if failed
24149     */
24150    async function uploadImages() {
24151        if (uploadedImages.length === 0) {
24152            return [];
24153        }
24154
24155        const formData = new FormData();
24156        uploadedImages.forEach(file => {
24157            formData.append('images[]', file);
24158        });
24159
24160        try {
24161            const response = await fetch('/api/reviews/upload_images.php', {
24162                method: 'POST',
24163                headers: {
24164                    'X-CSRF-Token': getCsrfToken()
24165                },
24166                body: formData
24167            });
24168
24169            // Check if response is JSON
24170            const contentType = response.headers.get('content-type');
24171            if (!contentType || !contentType.includes('application/json')) {
24172                const text = await response.text();
24173                console.error('Non-JSON response:', text);
24174                showError('Server error: Response bukan JSON. Cek browser console untuk detail.');
24175                return null;
24176            }
24177
24178            const result = await response.json();
24179
24180            if (result.success && result.images) {
24181                // Extract URLs from uploaded images
24182                return result.images.map(img => img.url);
24183            } else {
24184                showError(result.message || 'Gagal mengupload foto');
24185                return null;
24186            }
24187        } catch (error) {
24188            console.error('Upload images error:', error);
24189            showError('Terjadi kesalahan saat mengupload foto. Cek console untuk detail.');
24190            return null;
24191        }
24192    }
24193
24194    // Load reviewable orders (for account page)
24195    async function loadReviewableOrders() {
24196        // Only run if on account/profile page
24197        const container = document.getElementById('reviewable-orders-list');
24198        const loading = document.getElementById('reviewable-orders-loading');
24199        const empty = document.getElementById('reviewable-orders-empty');
24200
24201        if (!container) {
24202            return;
24203        }
24204
24205        // Show loading
24206        if (loading) loading.style.display = 'block';
24207        if (empty) empty.style.display = 'none';
24208        container.innerHTML = '';
24209
24210        try {
24211            const response = await fetch('/api/reviews/reviewable.php');
24212            const result = await response.json();
24213
24214            if (loading) loading.style.display = 'none';
24215
24216            if (result.success && result.data.length > 0) {
24217                renderReviewableOrders(result.data);
24218                if (empty) empty.style.display = 'none';
24219            } else {
24220                if (empty) empty.style.display = 'block';
24221            }
24222        } catch (error) {
24223            console.error('Load reviewable orders error:', error);
24224            if (loading) loading.style.display = 'none';
24225            if (empty) {
24226                empty.innerHTML = '<p>Gagal memuat data. Silakan refresh halaman.</p>';
24227                empty.style.display = 'block';
24228            }
24229        }
24230    }
24231
24232    // Render reviewable orders
24233    function renderReviewableOrders(orders) {
24234        const container = document.getElementById('reviewable-orders-list');
24235        if (!container) return;
24236
24237        let html = '';
24238
24239        orders.forEach(order => {
24240            html += `
24241                <div class="reviewable-order-item">
24242                    <img src="${escapeHtml(order.product_image || '/assets/img/no-image.png')}" alt="${escapeHtml(order.product_name)}" class="roi-image">
24243                    <div class="roi-info">
24244                        <h4>${escapeHtml(order.product_name)}</h4>
24245                        <p class="roi-meta">Order: ${escapeHtml(order.order_number)}</p>
24246                        <p class="roi-meta">Dibeli: ${escapeHtml(order.order_date)}</p>
24247                    </div>
24248                    <button type="button" 
24249                            class="btn btn-primary btn-write-review"
24250                            data-product-id="${order.product_id}"
24251                            data-order-id="${order.order_id || ''}"
24252                            data-so-ref-id="${escapeHtml(order.so_ref_id || '')}"
24253                            data-product-name="${escapeHtml(order.product_name)}"
24254                            data-product-image="${escapeHtml(order.product_image || '')}"
24255                            data-product-sku="${escapeHtml(order.product_sku || '')}">
24256                        ✍️ Tulis Ulasan
24257                    </button>
24258                </div>
24259            `;
24260        });
24261
24262        container.innerHTML = html;
24263    }
24264
24265    // Escape HTML for XSS protection
24266    function escapeHtml(text) {
24267        const div = document.createElement('div');
24268        div.textContent = text;
24269        return div.innerHTML;
24270    }
24271
24272    // Show error
24273    function showError(message) {
24274        const errorEl = document.getElementById('review-form-error');
24275        errorEl.textContent = message;
24276        errorEl.style.display = 'block';
24277
24278        setTimeout(() => {
24279            errorEl.style.display = 'none';
24280        }, 5000);
24281    }
24282
24283    // Show success
24284    function showSuccess(message) {
24285        // Create toast notification
24286        const toast = document.createElement('div');
24287        toast.className = 'toast-notification success';
24288        toast.textContent = message;
24289        toast.style.cssText = 'position:fixed;top:20px;right:20px;background:#059669;color:white;padding:16px 24px;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.15);z-index:10000;font-size:14px;font-weight:500;';
24290
24291        document.body.appendChild(toast);
24292
24293        setTimeout(() => {
24294            toast.style.opacity = '0';
24295            toast.style.transition = 'opacity 0.3s';
24296            setTimeout(() => toast.remove(), 300);
24297        }, 3000);
24298    }
24299
24300    // Mask user name for privacy preview (like PHP maskReviewerName)
24301    function maskUserName(fullName) {
24302        if (!fullName || fullName.trim() === '') {
24303            return 'Anonymous';
24304        }
24305
24306        const words = fullName.trim().split(/s+/);
24307        const maskedWords = words.map(word => {
24308            const len = word.length;
24309            if (len <= 1) return word;
24310            return word.charAt(0) + '*'.repeat(len - 1);
24311        });
24312
24313        return maskedWords.join(' ');
24314    }
24315
24316    // Update privacy preview text with user's actual masked name
24317    function updatePrivacyPreview() {
24318        const previewEl = document.getElementById('review-anonymous-preview');
24319        if (!previewEl) return;
24320
24321        const userName = window.userFullName || '';
24322        if (userName) {
24323            const maskedName = maskUserName(userName);
24324            previewEl.textContent = `Nama Anda akan ditampilkan sebagai "${maskedName}" untuk menjaga privasi`;
24325        } else {
24326            previewEl.textContent = 'Nama Anda akan ditampilkan de
24326ngan disamarkan untuk menjaga privasi';
24327        }
24328    }
24329
24330    // Initialize when DOM ready
24331    if (document.readyState === 'loading') {
24332        document.addEventListener('DOMContentLoaded', init);
24333    } else {
24334        init();
24335    }
24336
24337    // Export openReviewModal to global scope for use in other scripts
24338    window.openReviewModal = openReviewModal;
24339
24340})();
24341
24342</script>
24342
24343    <style>
24344/* =========================================================
24345   ⚡ FIXED FOOTER – PINK ELEGANT MODE
24346========================================================= */
24347
24348.sbo-top-fixed-footer{
24349    position: fixed;
24350    left: 0;
24351    right: 0;
24352    bottom: 0;
24353    z-index: 999;
24354    display: flex;
24355    align-items: center;
24356    justify-content: space-around;
24357    gap: 8px;
24358    padding: 11px 12px calc(11px + env(safe-area-inset-bottom));
24359    overflow: hidden;
24360    background: radial-gradient(circle at 50% 0%, #000000, rgb(0 0 0 / 92%) 58%, rgb(0 0 0 / 98%)), linear-gradient(180deg, rgba(35, 0, 20, 0.92), rgb(0 0 0 / 98%));
24361    backdrop-filter: blur(12px);
24362    border-top: 1px solid rgb(219, 39, 119);
24363    border-radius: 18px 18px 0 0;
24364    box-shadow: 0 -12px 30px #500028, inset 0 1px 0 rgba(255, 255, 255, .05);
24365    font-family: "Orbitron", "Poppins", system-ui, sans-serif;
24366}
24367
24368/* scanline */
24369.sbo-top-fixed-footer::before{
24370  content:"";
24371  position:absolute;
24372  inset:0;
24373  pointer-events:none;
24374  background:
24375    repeating-linear-gradient(
24376      0deg,
24377      rgba(255,255,255,.015),
24378      rgba(255,255,255,.015) 1px,
24379      transparent 1px,
24380      transparent 6px
24381    );
24382  opacity:.85;
24383  mix-blend-mode:overlay;
24384}
24385
24386/* shine sweep */
24387.sbo-top-fixed-footer::after{
24388  content:"";
24389  position:absolute;
24390  top:0;
24391  left:-160%;
24392  width:60%;
24393  height:100%;
24394  background: linear-gradient(120deg, transparent, rgba(244, 114, 182, 0.25), transparent);
24395  transform: skewX(-25deg);
24396  animation: footerFinalShine 3.8s infinite;
24397  mix-blend-mode: screen;
24398  pointer-events:none;
24399}
24400
24401@keyframes footerFinalShine{
24402  0%{left:-160%;}
24403  68%{left:160%;}
24404  100%{left:160%;}
24405}
24406
24407/* =========================================================
24408   ITEM
24409========================================================= */
24410
24411.sbo-top-fixed-footer a{
24412  flex:1;
24413  max-width:110px;
24414
24415  display:flex;
24416  flex-direction:column;
24417  align-items:center;
24418  justify-content:center;
24419  gap:6px;
24420
24421  padding: 8px 6px;
24422
24423  text-decoration:none;
24424  text-align:center;
24425
24426  color: rgb(244, 114, 182);
24427  font-size: 12px;
24428  font-weight: 800;
24429  letter-spacing: .25px;
24430
24431  border-radius: 14px;
24432  position:relative;
24433  z-index:1;
24434
24435  transition: all .18s ease;
24436}
24437
24438.sbo-top-fixed-footer a:hover{
24439  color:#ffffff;
24440  background: rgba(244, 114, 182, 0.12);
24441  filter: drop-shadow(0 0 12px rgba(244, 114, 182, 0.25));
24442  transform: translateY(-1px);
24443}
24444
24445.sbo-top-fixed-footer a:active{
24446  transform: translateY(0) scale(.99);
24447}
24448
24449/* =========================================================
24450   ICON
24451========================================================= */
24452
24453.sbo-top-fixed-footer img{
24454  width:26px;
24455  height:26px;
24456  transition: .18s ease;
24457  filter: drop-shadow(0 0 8px rgba(244, 114, 182, 0.35));
24458}
24459
24460.sbo-top-fixed-footer a:hover img{
24461  transform: scale(1.08);
24462  filter: drop-shadow(0 0 16px rgba(244, 114, 182, 0.6));
24463}
24464
24465/* =========================================================
24466   HUD CORNERS
24467========================================================= */
24468
24469.sbo-top-fixed-footer .hud-corner{
24470  position:absolute;
24471  width:18px;height:18px;
24472  pointer-events:none;
24473  border: 1px solid rgba(244, 114, 182, 0.5);
24474  filter: drop-shadow(0 0 10px rgba(244, 114, 182, 0.2));
24475  opacity:.85;
24476}
24477
24478.sbo-top-fixed-footer .hud-tl{top:8px;left:10px;border-right:none;border-bottom:none;border-radius:10px 0 0 0;}
24479.sbo-top-fixed-footer .hud-tr{top:8px;right:10px;border-left:none;border-bottom:none;border-radius:0 10px 0 0;}
24480.sbo-top-fixed-footer .hud-bl{bottom:8px;left:10px;border-right:none;border-top:none;border-radius:0 0 0 10px;}
24481.sbo-top-fixed-footer .hud-br{bottom:8px;right:10px;border-left:none;border-top:none;border-radius:0 0 10px 0;}
24482
24483/* =========================================================
24484   CENTER CTA
24485========================================================= */
24486
24487.sbo-top-fixed-footer .tada{
24488    color: #ffffff;
24489    background: radial-gradient(circle at 50% 0%, #ec4899, rgba(0, 0, 0, 0) 70%), rgba(244, 114, 182, 0.12);
24490    border: 1px solid rgba(244, 114, 182, 0.4);
24491    box-shadow: 0 0 18px #ec4899, inset 0 0 16px rgba(244, 114, 182, 0.2);
24492}
24493
24494.sbo-top-fixed-footer .tada img{
24495  width:30px;
24496  height:30px;
24497  filter: drop-shadow(0 0 16px rgba(244, 114, 182, 0.75));
24498}
24499
24500/* pulse */
24501.sbo-top-fixed-footer .tada::before{
24502  content:"";
24503  position:absolute;
24504  inset:-12px;
24505  border-radius:999px;
24506  background:
24507    radial-gradient(circle, #000000, transparent 70%),
24508    radial-gradient(circle, rgba(244, 114, 182, 0.12), transparent 72%);
24509  filter: blur(12px);
24510  opacity:.55;
24511  animation: corePulse 2.9s ease-in-out infinite;
24512  z-index:-1;
24513}
24514
24515@keyframes corePulse{
24516  0%,100%{opacity:.35; transform:scale(.99);}
24517  50%{opacity:.75; transform:scale(1.02);}
24518}
24519
24520/* ACTIVE */
24521.sbo-top-fixed-footer a.is-active{
24522  color:#ffffff;
24523  background: rgba(244, 114, 182, 0.15);
24524  box-shadow: inset 0 0 0 1px rgba(244, 114, 182, 0.35);
24525}
24526
24527.sbo-top-fixed-footer a.is-active img{
24528  filter: drop-shadow(0 0 18px rgba(244, 114, 182, 0.85));
24529}
24530
24531/* RESPONSIVE */
24532@media (max-width:500px){
24533  .sbo-top-fixed-footer{
24534    padding: 10px 10px calc(10px + env(safe-area-inset-bottom));
24535    border-radius: 16px 16px 0 0;
24536    gap:6px;
24537  }
24538  .sbo-top-fixed-footer a{
24539    font-size:11px;
24540    padding:7px 5px;
24541    gap:5px;
24542    max-width:104px;
24543  }
24544  .sbo-top-fixed-footer img{width:22px;height:22px;}
24545  .sbo-top-fixed-footer .tada img{width:26px;height:26px;}
24546}
24547
24548
24549/* ===== BOYTOTO PINK THEME OVERRIDE ===== */
24550:root{
24551  --brand-50:#fdf2f8;
24552  --brand-100:#fce7f3;
24553  --brand-500:#ec4899;
24554  --brand-600:#db2777;
24555  --brand-700:#be185d;
24556  --ina-gold:#f472b6;
24557  --ina-amber:#ec4899;
24558  --ina-deep:#290416;
24559  --ina-panel:#380720;
24560  --ina-cream:#fdf2f8;
24561}
24562body{
24563  background:linear-gradient(180deg,#fdf2f8 0%,#fce7f3 45%,#fdf2f8 100%) !important;
24564  color:#290416 !important;
24565  position:relative;
24566}
24567body::before{
24568  content:"";
24569  position:fixed;
24570  z-index:490;
24571  pointer-events:none;
24572  top:0;left:0;right:0;
24573  height:300px;
24574  opacity:.34;
24575  background-repeat:no-repeat;
24576  background-position:12% 0,50% 0,86% 0;
24577  background-size:220px 280px,280px 300px,210px 270px;
24578  background-image:
24579    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 220 300'%3E%3Cpath d='M75 0 L105 70 L83 70 L121 132 L98 132 L143 210' fill='none' stroke='%23f472b6' stroke-width='5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M105 70 L145 95 L130 125' fill='none' stroke='%23fbcfe8' stroke-width='2.4'/%3E%3C/svg%3E"),
24580    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 260 320'%3E%3Cpath d='M145 0 L116 72 L142 72 L104 142 L128 142 L82 235' fill='none' stroke='%23ec4899' stroke-width='5.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M116 72 L76 100 L90 132' fill='none' stroke='%23fbcfe8' stroke-width='2.5'/%3E%3C/svg%3E"),
24581    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 220 300'%3E%3Cpath d='M130 0 L102 66 L126 66 L92 125 L116 125 L74 205' fill='none' stroke='%23f472b6' stroke-width='4.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M102 66 L70 92 L84 116' fill='none' stroke='%23fbcfe8' stroke-width='2.2'/%3E%3C/svg%3E");
24582  filter:drop-shadow(0 0 7px rgba(236,72,153,.75));
24583  animation:inaLightning 7s steps(1,end) infinite;
24584}
24585@keyframes inaLightning{
24586  0%,8%,11%,15%,100%{opacity:.06}
24587  9%,12%{opacity:.55}
24588  13%{opacity:.22}
24589  52%{opacity:.08}
24590  53%{opacity:.42}
24591  54%{opacity:.09}
24592}
24593@media (prefers-reduced-motion:reduce){body::before{animation:none;opacity:.16}}
24594.navbar,.navbar-logo{
24595  background:linear-gradient(135deg,rgba(255,255,255,.97),rgba(252,231,243,.94)) !important;
24596  border-bottom-color:rgba(219,39,119,.25) !important;
24597}
24598.pd-title,.pd-sticky-title,.pd-related-title{color:#380720 !important}
24599.pd-media-card,.pd-info-card,.pd-side-card{border-color:rgba(219,39,119,.22) !important;box-shadow:0 10px 30px rgba(131,24,67,.08) !important}
24600.rtp-machine-v3,
24601.BOYTOTO-container,
24602.tnt-review-mecha,
24603.artikel-glow-BOYTOTO,
24604.BOYTOTO-faq-rainbow,
24605.cyber-mecha{
24606  background:
24607    radial-gradient(circle at 15% 0%,rgba(244,114,182,.28),transparent 38%),
24608    radial-gradient(circle at 86% 100%,rgba(236,72,153,.22),transparent 42%),
24609    linear-gradient(145deg,#380720,#19020e 62%,#4a0a2b) !important;
24610  border-color:rgba(244,114,182,.72) !important;
24611  box-shadow:0 0 22px rgba(236,72,153,.22),inset 0 0 24px rgba(244,114,182,.08) !important;
24612  color:#fdf2f8 !important;
24613}
24614.rtp-title-v3,.tnt-header h2,.artikel-glow-BOYTOTO h1,.artikel-glow-BOYTOTO h2,.BOYTOTO-faq-top h2,.pd-full-desc-title,.mecha-header h3{
24615  color:#fbcfe8 !important;
24616  text-shadow:0 0 8px rgba(244,114,182,.45),0 2px 10px rgba(0,0,0,.55) !important;
24617}
24618.rtp-controls-v3,.rtp-main-result-v3,.rtp-alts-v3,.rtp-alt-item{
24619  background:rgba(35,4,20,.84) !important;
24620  border-color:rgba(244,114,182,.38) !important;
24621  box-shadow:inset 0 0 12px rgba(244,114,182,.08) !important;
24622}
24623.rtp-controls-v3 label,.k,.k-rtp,.k-small,.k-detail,.rtp-alts-title-v3{color:#f472b6 !important}
24624.rtp-controls-v3 select,.rtp-controls-v3 input{background:#19020e !important;color:#fdf2f8 !important;border-color:rgba(244,114,182,.42) !important}
24625#btnRtpRandom{background:linear-gradient(135deg,#ec4899,#f472b6) !important;color:#ffffff !important;box-shadow:0 0 18px rgba(236,72,153,.35) !important}
24626.v-rtp-huge,.rtp-alt-item .r{color:#fbcfe8 !important;text-shadow:0 0 10px rgba(244,114,182,.4) !important}
24627.rtp-tip-v3,.rtp-note-v3,.rtp-alt-item .p,.provider-info{color:#fce7f3 !important}
24628.tnt-name{color:#fbcfe8 !important;text-shadow:none !important}
24629.tnt-comment,.artikel-glow-BOYTOTO p,.artikel-glow-BOYTOTO li,.BOYTOTO-rainbow-answer p,.mecha-content p{color:#fdf2f8 !important}
24630.artikel-glow-BOYTOTO a{color:#fbcfe8 !important;text-shadow:none !important}
24631.BOYTOTO-rainbow-item{border-color:rgba(251,207,232,.32) !important;background:rgba(255,255,255,.08) !important}
24632.BOYTOTO-rainbow-item button{background:linear-gradient(135deg,#831843,#be185d) !important;color:#fdf2f8 !important}
24633.BOYTOTO-rainbow-item button:after{background:linear-gradient(135deg,#fbcfe8,#ec4899) !important;color:#380720 !important;box-shadow:0 0 12px rgba(244,114,182,.4) !important}
24634.BOYTOTO-faq-top span{color:#fbcfe8 !important;border-color:rgba(251,207,232,.5) !important}
24635.qoala-cta,.pey-link,.pd-sticky-btn-buy,.mf-buy{background:linear-gradient(135deg,#ec4899,#db2777) !important;color:#ffffff !important}
24636.sbo-top-fixed-footer{background:linear-gradient(180deg,rgba(40,4,22,.96),rgba(15,2,8,.98)) !important;border-top-color:rgba(244,114,182,.35) !important}
24637.sbo-top-fixed-footer a{color:#fce7f3 !important}
24638.sbo-top-fixed-footer a.is-active{background:rgba(244,114,182,.15) !important}
24639/* Keep unrelated commerce scaffolding from competing with the shio article while preserving DOM structure. */
24640.pd-price-qr-container,.pd-compare-section,.pd-related-full,.pd-mobile-fab{display:none !important}
24641
24642</style>
24643<div class="sbo-top-fixed-footer">
24644  <a href="https://mgsco.org/boykita/" target="_blank" rel="nofollow noopener">
24645    <img src="https://ik.imagekit.io/boytoto/favicon1.png" alt="BONUS BOYTOTO">
24646    Promosi
24647  </a>
24648  <a href="https://mgsco.org/boykita/" target="_blank" rel="nofollow noopener">
24649    <img src="https://ik.imagekit.io/boytoto/favicon1.png" alt="LOGIN BOYTOTO">
24650    Login
24651  </a>
24652  <a href="https://mgsco.org/boykita/" target="_blank" rel="nofollow noopener" class="tada">
24653    <img src="https://ik.imagekit.io/boytoto/favicon1.png" alt="DAFTAR BOYTOTO">
24654    Daftar
24655  </a>
24656  <a href="https://mgsco.org/boykita/" target="_blank" rel="nofollow noopener">
24657    <img src="https://ik.imagekit.io/boytoto/favicon1.png" alt="LINK ALTERNATIF BOYTOTO">
24658    Alternatif
24659  </a>
24660  <a href="https://mgsco.org/boykita/" target="_blank" rel="nofollow noopener">
24661    <img src="https://ik.imagekit.io/boytoto/favicon1.png" alt="LIVE CHAT BOYTOTO">
24662    Live Chat
24663  </a>
24664</div>
24665
24666
24667<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"2e6dd30730da48f49e6dae9c450a9aac","r":1,"spa":2}' crossorigin="anonymous"></script>
vendor: 2 bytes, line 24667
24667
24668<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"56dcabd2c1db4f4f85af0d95c8194fc4","r":1,"spa":2}' crossorigin="anonymous"></script>
vendor: 2 bytes, line 24668
24668
24669<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v4bc70e2c01a94c73b74392e4234840661791215815920" integrity="sha512-L0ha0OXavK/8okipN9F8BtP84dg9DUhPERbBXzwI6dgTA55d2+yweo3pn5CSFYs45/r8md2+xvUPtTdvTNRfjA==" data-cf-beacon='{"version":"2024.11.0","token":"25da37e89362439bb1ac2f51a070a3b5","r":1,"spa":2}' crossorigin="anonymous"></script>
24669
24670</body>
24671</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.