PageSourceSearch

https://bim.miami/

html bim.miami collected 2026-09-28 10:41:50 UTC 830,125 bytes, 24,624 lines download raw bytes

1
2<!DOCTYPE html>
3<html lang="id">
4<head>
5  <meta charset="UTF-8">
6  <title>Apa Itu BERLINTOTO | Situs Toto Slot Gacor & Bandar Togel Online 4D Resmi</title>
7  <meta name="description" content="BERLINTOTO hadir sebagai platform hiburan digital serba ada yang mengintegrasikan permainan tebak angka presisi dan slot dengan tingkat kemenangan tinggi.">
8  <meta name="robots" content="index,follow">
9  <link rel="canonical" href="https://www.bim.miami/">
10  <meta name="google-site-verification" content="GdDmVTk4T-oneBYNmEZWUzkor6JveNfrJIvtzHVclPQ" />
11  <link rel="amphtml" href="https://bim.miami/aboutus/">
12  <link rel="alternate" href="https://bim.miami/aboutus/" hreflang="id">
13  <link rel="alternate" href="https://bim.miami/aboutus/" hreflang="en">
14  <link rel="alternate" href="https://bim.miami/aboutus/" hreflang="x-default">
15  <link rel="alternate" hreflang="id-ID" href="https://bim.miami/aboutus/" />
16  <link rel="alternate" hreflang="en-US" href="https://bim.miami/aboutus/" />
17  <link rel="alternate" hreflang="en-SG" href="https://bim.miami/aboutus/" />
18    <link rel="preconnect" href="https://www.googletagmanager.com" crossorigin>
vendor: 66 bytes, lines 18-19
18
19  <script async src="https://www.googletagmanager.com/gtag/js?id=
19G-0LSM1YJSPZ
vendor: 47 bytes, lines 19-20
19" nonce="se3iyzEHMc9FQEB4wSyd0w=="></script>
20  
20<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>
20
21  <meta property="og:locale" content="id_ID">
22  <meta property="og:site_name" content="TOTO TOGEL">
23  <meta property="og:type" content="product">
24  <meta property="og:title" content="Apa Itu BERLINTOTO | Situs Toto Slot Gacor & Bandar Togel Online 4D Resmi">
25  <meta property="og:description" content="BERLINTOTO hadir sebagai platform hiburan digital serba ada yang mengintegrasikan permainan tebak angka presisi dan slot dengan tingkat kemenangan tinggi.">
26  <meta property="og:url" content="https://www.bim.miami/">
27  <meta property="og:image" content="https://www.bim.miami/berlintoto.png">
28  <meta property="og:image:alt" content="Apa Itu BERLINTOTO | Situs Toto Slot Gacor & Bandar Togel Online 4D Resmi">
29<meta property="product:price:amount" content="5000">
30<meta property="product:price:currency" content="IDR">
31<meta property="product:availability" content="out of stock">
32<meta name="twitter:card" content="summary_large_image">
33<meta name="twitter:title" content="Apa Itu BERLINTOTO | Situs Toto Slot Gacor & Bandar Togel Online 4D Resmi">
34<meta name="twitter:description" content="BERLINTOTO hadir sebagai platform hiburan digital serba ada yang mengintegrasikan permainan tebak angka presisi dan slot dengan tingkat kemenangan tinggi.">
35<meta name="twitter:image" content="https://www.bim.miami/berlintoto.png">
36
37<script type="application/ld+json">
38      {
39        "@context": "http:\/\/schema.org",
40        "@type": "BreadcrumbList",
41        "itemListElement": [
42          {
43            "@type": "ListItem",
44            "item": {
45              "@id": "https://www.bim.miami/",
46              "name": "Apa Itu BERLINTOTO | Situs Toto Slot Gacor & Bandar Togel Online 4D Resmi"
47            },
48            "position": 1
49          }
50        ]
51      }
52    </script>
52
53    
53<script type="application/ld+json">
54      {
55        "@context": "http:\/\/schema.org",
56        "@type": "Product",
57        "name": "Apa Itu BERLINTOTO | Situs Toto Slot Gacor & Bandar Togel Online 4D Resmi",
58        "description": "BERLINTOTO hadir sebagai platform hiburan digital serba ada yang mengintegrasikan permainan tebak angka presisi dan slot dengan tingkat kemenangan tinggi.",
59        "image": "https://www.bim.miami/berlintoto.png",
60        "offers": {
61          "@type": "http:\/\/schema.org\/Offer",
62          "price": 9999,
63          "url": "https://www.bim.miami/",
64          "priceCurrency": "IDR",
65          "availability": "http:\/\/schema.org\/InStock"
66        }
67      }
68    </script>
68
69    
69<script type="application/ld+json">
70      {
71        "@context": "http:\/\/schema.org\/",
72        "@type": "WebPage",
73        "speakable": {
74          "@type": "SpeakableSpecification",
75          "cssSelector": [".description"],
76          "xpath": ["\/html\/head\/title"]
77        }
78      }
79    </script>
79
80    <!-- ZS -->
81    <!-- Schema JSON untuk SBO -->
82<script type="application/ld+json">
83{
84"@context": "https://schema.org/",
85"@type": "Product",
86"name": "Apa Itu BERLINTOTO | Situs Toto Slot Gacor & Bandar Togel Online 4D Resmi",
87"url": "https://www.bim.miami/",
88"sku": "BERLINTOTO",
89"image": [
90  "https://www.bim.miami/berlintoto.png"
91],
92"description": "BERLINTOTO hadir sebagai platform hiburan digital serba ada yang mengintegrasikan permainan tebak angka presisi dan slot dengan tingkat kemenangan tinggi.",
93"brand": {
94  "@type": "Organization",
95  "name": "BERLIN TOTO"
96},
97"offers": {
98  "@type": "Offer",
99  "name": "Apa Itu BERLINTOTO | Situs Toto Slot Gacor & Bandar Togel Online 4D Resmi",
100  "sku": "LOGIN BERLINTOTO",
101  "price": "9999",
102  "priceCurrency": "IDR",
103  "availability": "https://schema.org/InStock"
104}
105}
106</script>
106
107
108<script type="application/ld+json">
109{
110"@context": "https://schema.org/",
111"@type": "Product",
112"name": "Apa Itu BERLINTOTO | Situs Toto Slot Gacor & Bandar Togel Online 4D Resmi",
113"image": "https://www.bim.miami/berlintoto.png",
114"description": "BERLINTOTO hadir sebagai platform hiburan digital serba ada yang mengintegrasikan permainan tebak angka presisi dan slot dengan tingkat kemenangan tinggi.",
115"sku": "DAFTAR BERLINTOTO",
116"offers": {
117  "@type": "Offer",
118  "priceCurrency": "IDR",
119  "url": "https://www.bim.miami/",
120  "availability": "https://schema.org/InStock",
121  "price": "9999.00"
122}
123}
124</script>
124
125
126<script type="application/ld+json" id="bv-jsonld-bvloader-summary">
127{
128"@context": "https://schema.org/",
129"@id": "https://www.bim.miami/",
130"aggregateRating": {
131  "@type": "AggregateRating",
132  "itemReviewed": {
133    "@type": "IndividualProduct",
134    "name": "LINK BERLINTOTO"
135  },
136  "ratingValue": "5.0",
137  "reviewCount": 954638,
138  "bestRating": 5
139}
140}
141</script>
141
142
143<script type="application/ld+json">
144{
145"@context": "https://schema.org",
146"@type": "BreadcrumbList",
147"itemListElement": [
148  {
149    "@type": "ListItem",
150    "position": 1,
151    "item": {
152      "@id": "https://www.bim.miami/",
153      "name": "BERLINTOTO"
154    }
155  },
156  {
157    "@type": "ListItem",
158    "position": 2,
159    "item": {
160      "@id": "https://www.bim.miami/",
161      "name": "Apa Itu BERLINTOTO | Situs Toto Slot Gacor & Bandar Togel Online 4D Resmi"
162    }
163  },
164  {
165    "@type": "ListItem",
166    "position": 3,
167    "item": {
168      "@id": "https://www.bim.miami/",
169      "name": "BERLIN TOTO"
170    }
171  },
172  {
173    "@type": "ListItem",
174    "position": 4,
175    "item": {
176      "@id": "https://www.bim.miami/",
177      "name": "Apa Itu BERLINTOTO | Situs Toto Slot Gacor & Bandar Togel Online 4D Resmi"
178    }
179  }
180]
181}
182</script>
182
183
184<script type="application/ld+json" id="bv-jsonld-reviews-data">
185{
186  "@context": "https://schema.org",
187  "@type": "Organization",
188  "@id": "https://www.bim.miami/",
189  "name": "BERLINTOTO",
190  "alternateName": "BANDAR BERLINTOTO",
191  "url": "https://www.bim.miami/",
192  "review": [
193    {
194      "@type": "Review",
195      "dateCreated": "2026-03-31T12:28:35+00:00",
196      "datePublished": "2026-03-31T12:28:35+00:00",
197      "headline": "BERLINTOTO",
198      "reviewBody": "BERLINTOTO memudahkan saya membuka situs toto slot dan situs toto togel melalui satu portal utama. Menu yang tersedia mudah ditemukan sehingga saya bisa berpindah halaman dengan lebih sederhana saat menggunakan akun.",
199      "reviewRating": {
200        "@type": "Rating",
201        "ratingValue": 5,
202        "bestRating": 5,
203        "worstRating": 1
204      },
205      "author": {
206        "@type": "Person",
207        "name": "Taufan – Surabaya"
208      }
209    },
210    {
211      "@type": "Review",
212      "dateCreated": "2026-04--02T18:28:21+00:00",
213      "datePublished": "2026-04-03T18:28:21+00:00",
214      "headline": "BERLINTOTO",
215      "reviewBody": "Urusan transaksi finansial di sini jempolan banget. Deposit pakai scan QRIS instan masuk hitungan detik tanpa perlu konfirmasi manual yang berbelit-belit. Withdraw berapapun selalu diproses cepat dan transparan tanpa ada biaya potongan tersembunyi.",
216      "reviewRating": {
217        "@type": "Rating",
218        "ratingValue": 5,
219        "bestRating": 5,
220        "worstRating": 1
221      },
222      "author": {
223        "@type": "Person",
224        "name": "Thomas Tampi – Semarang"
225      }
226    },
227    {
228      "@type": "Review",
229      "dateCreated": "2026-04-05T19:28:18+00:00",
230      "datePublished": "2026-04-05T19:28:18+00:00",
231      "headline": "BERLINTOTO",
232      "reviewBody": "BERLINTOTO memiliki tampilan yang mudah dipahami untuk membuka situs toto slot dan situs toto togel. Saya dapat menemukan menu yang dibutuhkan dan mengakses halaman melalui perangkat yang digunakan.",
233      "reviewRating": {
234        "@type": "Rating",
235        "ratingValue": 5,
236        "bestRating": 5,
237        "worstRating": 1
238      },
239      "author": {
240        "@type": "Person",
241        "name": "Soesilowati – Makassar"
242      }
243    }
244  ]
245}
246</script>
vendor: 1 bytes, line 246
246
247<script type="application/ld+json">
248{
249  "@context": "https://schema.org",
250  "@type": "FAQPage",
251  "mainEntity": [
252    {
253  "@type": "Question",
254  "name": "Apa itu BERLINTOTO sebagai portal utama situs toto slot gacor dan situs toto togel 2026?",
255  "acceptedAnswer": {
256    "@type": "Answer",
257    "text": "Hadir situs Toto slot gacor & bandar togel online 4D resmi, BERLINTOTO menyajikan ekosistem permainan digital terlengkap yang didukung oleh teknologi server modern, stabilitas akses tanpa kendala pemblokiran, serta jaminan transparansi permainan secara menyeluruh."
258  }
259},
260{
261  "@type": "Question",
262  "name": "Pasaran togel apa saja yang dapat dimainkan di BERLINTOTO?",
263  "acceptedAnswer": {
264    "@type": "Answer",
265    "text": "BERLINTOTO menyediakan pilihan pasaran togel online 4D resmi terpopuler dan terlengkap. Seluruh hasil pengeluaran angka diputar secara live draw dan diperbarui secara real-time langsung dari server pusat yang akurat."
266  }
267},
268{
269  "@type": "Question",
270  "name": "Bagaimana cara menggunakan BERLINTOTO untuk membuka situs toto slot dan situs toto togel?",
271  "acceptedAnswer": {
272    "@type": "Answer",
273    "text": "Keamanan member adalah prioritas utama. Seluruh infrastruktur jaringan BERLINTOTO dilindungi oleh sistem enkripsi data canggih yang menjaga kerahasiaan informasi pribadi serta menjamin keamanan penuh pada setiap riwayat transaksi finansial."
274  }
275},
276{
277  "@type": "Question",
278  "name": "Metode pembayaran apa saja yang disediakan untuk transaksi deposit dan withdraw?",
279  "acceptedAnswer": {
280    "@type": "Answer",
281    "text": "Proses transaksi diproses secara serba otomatis dan fleksibel. Member dapat menggunakan transfer bank lokal utama, fitur scan QRIS instan, hingga berbagai opsi e-wallet populer untuk melakukan pengisian saldo maupun penarikan dana."
282  }
283},
284{
285  "@type": "Question",
286  "name": "Ke mana harus menghubungi jika membutuhkan bantuan transaksi atau kendala akun?",
287  "acceptedAnswer": {
288    "@type": "Answer",
289    "text": "Layanan Customer Service profesional BERLINTOTO bersiaga penuh selama 24 jam nonstop setiap hari melalui fitur Live Chat dan kontak resmi untuk membantu kendala Anda dengan cepat dan ramah."
290  }
291}
292  ]
293}
294</script>
294
295  <!-- User Session Data -->
296    <meta name="csrf-token" content="9ce6e4a17f7cc189893df9e47789b13e98829450d516a0cae5ca7b5f43e5dadc">
297  <meta name="user-logged-in" content="false">
298  <meta name="user-id" content="0">
299  
300  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
301
302  <link rel="shortcut icon" href="https://www.bim.miami/iconberlintoto.png" type="image/x-icon">
303  <style data-inlined="style.css">
304@font-face {
305    font-family: 'Segoe UI';
306    src: url('/assets/fonts/segoeui.woff2') format('woff2'),
307         url('/assets/fonts/segoeui.woff') format('woff');
308    font-weight: 400;
309    font-style: normal;
310}
311
312:root {
313  --brand-50:#FFF5E6;
314  --brand-100:#FFE7CC;
315  --brand-500:#F28C1A;
316  --brand-600:#D97809;
317  --gray-900:#1F1F23;
318  --gray-700:#3D3F45;
319  --gray-500:#6F737B;
320  --gray-300:#C5C9CF;
321  --gray-100:#F3F5F7;
322  --border:#E4E7EB;
323  --radius:8px;
324  --transition: .18s ease;
325  /* font-family: "Inter", system-ui, sans-serif; */
326  font-family: 'Segoe UI', sans-serif;
327}
328
329* { box-sizing: border-box; }
330body { 
331  margin:0; 
332  padding-bottom: 0; /* Reset for desktop */
333  color:var(--gray-900); 
334  background:#fff; 
335}
336img { max-width:100%; display:block; }
337
338.container {
339  width:100%;
340  max-width:1280px;
341  margin:0 auto;
342  padding:10px 0;
343}
344
345.topbar {
346  background:#111;
347  color:#fff;
348  font-size:13px;
349}
350.topbar-inner {
351  display:flex;
352  justify-content:space-between;
353  align-items:center;
354  min-height:36px;
355}
356.topbar a { color:#fff; text-decoration:none; margin-left:12px; }
357
358/* ========== Desktop base (Glassmorphism Modern with Warm Gradient) ========== */
359.navbar {
360  background:linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(255,245,230,0.85) 100%);
361  backdrop-filter:blur(12px);
362  -webkit-backdrop-filter:blur(12px);
363  border-bottom:1px solid rgba(228,231,235,0.6);
364  box-shadow:0 2px 16px -4px rgba(0,0,0,0.08);
365  position:sticky;
366  top:0;
367  z-index:500;
368  transition:all .3s ease;
369}
370
371/* Accent line gradient orange */
372.navbar::after {
373  content:'';
374  position:absolute;
375  bottom:0;
376  left:0;
377  right:0;
378  height:2px;
379  background:linear-gradient(90deg,transparent 0%, var(--brand-500) 20%, var(--brand-600) 50%, var(--brand-500) 80%, transparent 100%);
380  opacity:0.5;
381}
382
383.nav-inner {
384  display:flex;
385  align-items:center;
386  gap:24px;
387  min-height:70px;
388}
389.nav-inner .search .m-search-ico { display: none; }
390
391/* Secara default (desktop) ikon login disembunyikan; teks muncul */
392.login-btn .login-ico { display:none; }
393.navbar-logo {
394  display: none;
395  background:linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(255,245,230,0.85) 100%);
396  backdrop-filter:blur(12px);
397  -webkit-backdrop-filter:blur(12px);
398  border-bottom:1px solid rgba(228,231,235,0.6);
399  box-shadow:0 2px 16px -4px rgba(0,0,0,0.08);
400  position:sticky;
401  top:0;
402  z-index:500;
403}
404
405/* Hide bottom navigation on desktop - only show on mobile */
406.bottom-nav {
407  display: none;
408}
409
410/* ==============================================================
411   MOBILE (<=640px)
412   ============================================================== */
413@media (max-width:640px){
414  .container { padding:10px 16px; }
415  
416  .topbar { display:none !important; }
417
418  /* Add padding bottom for sticky bottom nav */
419  body {
420    padding-bottom: 68px; /* Height of bottom nav + safe area */
421  }
422
423  /* Mobile Top Navbar - Clean & Minimal */
424  .navbar {
425    background:linear-gradient(135deg, rgba(255,255,255,0.97) 0%, rgba(255,245,230,0.9) 100%);
426    backdrop-filter:blur(16px);
427    -webkit-backdrop-filter:blur(16px);
428    box-shadow:0 2px 12px -3px rgba(0,0,0,0.08);
429    border: 0;
430  }
431
432  /* Simple grid layout - Logo, Search, Category */
433  .nav-inner {
434    display:grid;
435    grid-template-columns:1fr;
436    gap:10px;
437    padding:12px 16px 0 12px;
438    min-height:auto;
439  }
440
441  /* Logo centered at top */
442  .nav-inner .logo {
443    justify-self:center;
444    margin:0;
445    display:flex;
446  }
447  .nav-inner .logo img { height:36px; }
448
449  /* Search bar - full width */
450  .nav-inner .search {
451    position:relative;
452    display:flex;
453    align-items:center;
454    gap:0;
455    width:100%;
456    height:44px;
457    background:#fff;
458    border:1.5px solid #e5e7eb;
459    border-radius:12px;
460    padding:0 12px;
461    box-shadow:0 1px 3px rgba(0,0,0,0.04);
462    transition:all .25s;
463  }
464  .nav-inner .search:focus-within {
465    border-color:#fb923c;
466    box-shadow:0 0 0 3px rgba(251, 146, 60, 0.1);
467  }
468
469  .nav-inner .search-icon-btn {
470    color:#9ca3af;
471    padding:4px;
472    margin-right:4px;
473  }
474  
475  .nav-inner .search-icon-btn svg {
476    width:18px;
477    height:18px;
478  }
479
480  .nav-inner .search input {
481    flex:1;
482    min-width:0;
483    border:none;
484    outline:none;
485    background:transparent;
486    font-size:14px;
487    padding:0 8px;
488    height:100%;
489  }
490  
491  .nav-inner .ai-bot-toggle {
492    padding:6px;
493    margin-left:0;
494  }
495  
496  .nav-inner .ai-bot-toggle .bot-icon {
497    width:20px;
498    height:20px;
499  }
500  
501  .nav-inner .search-suggest {
502    left:0;
503    right:0;
504    width:calc(100vw - 32px);
505    margin-top:6px;
506    border-radius:12px;
507    max-height:70vh;
508  }
509
510  /* Hide desktop nav actions but keep structure for userMenu */
511  .nav-inner .nav-actions {
512    display:flex !important; /* Keep flex to allow userMenu positioning */
513    visibility:hidden; /* Hide visually but keep in DOM */
514    pointer-events:none; /* Disable interactions with hidden elements */
515    position:absolute; /* Remove from flow */
516    right:0;
517    top:0;
518  }
519  
520  /* Re-enable userMenu for logged-in users */
521  .nav-inner .nav-actions #userMenu {
522    visibility:visible !important;
523    pointer-events:auto !important;
524  }
525  
526  .nav-inner .logo { display: none; }
527  .navbar-logo {
528    display: block;
529    /* background:linear-gradient(135deg, rgba(255,255,255,0.97) 0%, rgba(255,245,230,0.9) 100%); */
530    backdrop-filter:blur(16px);
531    -webkit-backdrop-filter:blur(16px);
532    position: static; /* Remove sticky on mobile */
533    box-shadow: none;
534    border-bottom: 0;
535    padding-top: .6rem;
536  }
537  .nav-inner .logo-mobile { display: block; }
538  
539  /* Category menu wrapper will handle visibility */
540
541  /* ==============================================================
542     BOTTOM NAVIGATION - Styles moved to bottom-nav.css
543     ============================================================== */
544  /* All bottom-nav styles now in /assets/css/bottom-nav.css */
545
546  /* Hide desktop elements that now moved to bottom nav */
547  #wishlistBtn,
548  #notifBtn,
549  #loginBtn { display:none !important; }
550  
551  /* Hide desktop user menu on mobile - use bottom sheet instead */
552  .navbar #userMenu { 
553    display:none !important;
554  }
555  
556  .nav-actions .cart-indicator,
557  .nav-actions .compare-indicator {
558    display:none !important;
559  }
560
561  /* Hide category button from bottom nav */
562  #bottomCategoryBtn {
563    display: none !important;
564  }
565  
566  /* User menu button styling for mobile - scoped to navbar only */
567  .navbar #userMenu .login-btn.logged-in {
568    width:40px;
569    height:40px;
570    padding:0;
571    border-radius:50%;
572    display:flex;
573    align-items:center;
574    justify-content:center;
575    background:rgba(255,255,255,0.95);
576    backdrop-filter:blur(12px);
577    -webkit-backdrop-filter:blur(12px);
578    border:1px solid rgba(228,231,235,0.6);
579    box-shadow:0 2px 12px -3px rgba(0,0,0,0.12);
580  }
581  
582  .navbar #userMenu .login-btn.logged-in .login-text { 
583    display:none; 
584  }
585  
586  .navbar #userMenu .login-btn.logged-in .login-ico {
587    display:inline;
588    font-size:20px;
589    line-height:1;
590  }
591  
592  /* User dropdown positioning for mobile */
593  .navbar #userMenu .user-dropdown {
594    position:fixed !important;
595    top:60px !important;
596    right:16px !important;
597    left:auto !important;
598    min-width:200px;
599    background:rgba(255,255,255,0.98);
600    backdrop-filter:blur(20px);
601    -webkit-backdrop-filter:blur(20px);
602    border:1px solid rgba(228,231,235,0.6);
603    box-shadow:0 8px 24px -8px rgba(0,0,0,0.2);
604    z-index:1002 !important;
605    border-radius:12px;
606    padding:8px 0;
607  }
608  
609  /* Force visibility when not hidden attribute */
610  .navbar #userMenu .user-dropdown:not([hidden]) {
611    display:block !important;
612    visibility:visible !important;
613    opacity:1 !important;
614  }
615
616  /* Container untuk kategori dan tombol kategori sejajar */
617  .navbar .container:last-child {
618    display:flex !important;
619    align-items:center !important;
620    padding:4px 16px 6px !important;
621    gap:5px;
622    overflow:visible;
623  }
624  
625  /* Wrapper untuk mega-cats dengan fade gradient (mobile only) */
626  .mega-cats-wrapper {
627    position:relative;
628    flex:1;
629    overflow:hidden;
630    min-height:34px; /* Ensure content is visible */
631    display:flex; /* Ensure wrapper is flex container */
632  }
633  
634  /* Fade gradient indicator di ujung kanan (mobile only) */
635  .mega-cats-fade {
636    position:absolute;
637    top:0;
638    right:0;
639    bottom:0;
640    width:80px; /* Increased from 60px for better visibility */
641    background:linear-gradient(to left, 
642      rgba(255,245,230,1) 0%, 
643      rgba(255,245,230,0.98) 15%,
644      rgba(255,245,230,0.85) 35%, 
645      transparent 100%);
646    pointer-events:none;
647    z-index:5; /* Increased z-index */
648    transition:opacity 0.3s ease;
649  }
650  
651  /* Kategori menu di mobile */
652  .mega-cats.container {
653    display:flex !important;
654    gap:12px;
655    overflow-x:auto;
656    overflow-y:visible;
657    -webkit-overflow-scrolling:touch;
658    scrollbar-width:none;
659    flex:1;
660    padding:0 !important;
661    margin:0 !important;
662    position:relative; /* For z-index stacking */
663    z-index:1; /* Below fade gradient */
664  }
665  .mega-cats.container::-webkit-scrollbar { display:none; }
666
667  /* Styling untuk menu items di mobile */
668  .mega-cats a {
669    font-size:13px;
670    padding:6px 10px;
671    white-space:nowrap;
672    flex-shrink:0;
673    position:relative; /* Ensure proper stacking */
674  }
675  .mega-cats a.active {
676    background:var(--brand-600);
677    color:#fff;
678    font-weight:600;
679    border-radius:6px;
680  }
681  
682  /* Tombol kategori di mobile */
683  .mm-wp {
684    flex-shrink:0;
685  }
686  .mm2-trigger {
687    display:flex;
688    align-items:center;
689    gap:6px;
690    padding:6px 12px;
691    background:var(--gray-100);
692    color:var(--gray-700);
693    text-decoration:none;
694    border-radius:6px;
695    font-size:13px;
696    white-space:nowrap;
697  }
698  
699  .mm2-trigger .label-cat {display: none; }
700  /* Tombol more kategori */
701  .mega-more-btn {
702    border:1px solid var(--border);
703    background:#fff;
704    border-radius:10px;
705    padding:6px 14px;
706    font-size:18px;
707    cursor:pointer;
708  }
709  .mega-cats-more.active > .mega-more-btn {
710    background:var(--brand-600);
711    color:#fff;
712    border-color:var(--brand-600);
713  }
714  .mega-more-dropdown {
715    top:calc(100% + 10px);
716    right:0;
717    min-width:85vw;
718    max-width:90vw;
719    border-radius:16px;
720  }
721  .mega-more-grid {
722    grid-template-columns:repeat(auto-fill,minmax(120px,1fr));
723  }
724  
725  /* ==============================================================
726     MOBILE USER BOTTOM SHEET
727     ============================================================== */
728  .mobile-user-sheet {
729    position:fixed;
730    top:0;
731    left:0;
732    right:0;
733    bottom:0;
734    z-index:10000;
735    pointer-events:none;
736  }
737  
738  .mobile-user-sheet.active {
739    pointer-events:auto;
740  }
741  
742  .mobile-user-sheet-backdrop {
743    position:absolute;
744    top:0;
745    left:0;
746    right:0;
747    bottom:0;
748    background:rgba(0,0,0,0.5);
749    opacity:0;
750    transition:opacity .3s ease;
751  }
752  
753  .mobile-user-sheet.active .mobile-user-sheet-backdrop {
754    opacity:1;
755  }
756  
757  .mobile-user-sheet-content {
758    position:absolute;
759    bottom:0;
760    left:0;
761    right:0;
762    background:#fff;
763    border-radius:20px 20px 0 0;
764    box-shadow:0 -4px 24px rgba(0,0,0,0.15);
765    transform:translateY(100%);
766    transition:transform .3s cubic-bezier(0.4, 0.0, 0.2, 1);
767    padding-bottom:calc(68px + env(safe-area-inset-bottom));
768  }
769  
770  .mobile-user-sheet.active .mobile-user-sheet-content {
771    transform:translateY(0);
772  }
773  
774  .mobile-user-sheet-header {
775    padding:16px 20px 12px;
776    border-bottom:1px solid #e5e7eb;
777  }
778  
779  .mobile-user-sheet-handle {
780    width:40px;
781    height:4px;
782    background:#d1d5db;
783    border-radius:2px;
784    margin:0 auto 16px;
785  }
786  
787  .mobile-user-info {
788    display:flex;
789    align-items:center;
790    gap:12px;
791  }
792  
793  .mobile-user-avatar {
794    width:48px;
795    height:48px;
796    border-radius:50%;
797    background:linear-gradient(135deg, #FFE7CC, #FFD9A8);
798    display:flex;
799    align-items:center;
800    justify-content:center;
801    font-size:24px;
802    flex-shrink:0;
803  }
804  
805  .mobile-user-details {
806    flex:1;
807    min-width:0;
808  }
809  
810  .mobile-user-name {
811    font-size:16px;
812    font-weight:600;
813    color:#111827;
814    margin-bottom:2px;
815    white-space:nowrap;
816    overflow:hidden;
817    text-overflow:ellipsis;
818  }
819  
820  .mobile-user-email {
821    font-size:13px;
822    color:#6b7280;
823    white-space:nowrap;
824    overflow:hidden;
825    text-overflow:ellipsis;
826  }
827  
828  .mobile-user-sheet-body {
829    padding:8px 0;
830  }
831  
832  .mobile-user-sheet-item {
833    display:flex;
834    align-items:center;
835    gap:16px;
836    padding:14px 20px;
837    text-decoration:none;
838    color:#374151;
839    font-size:15px;
840    transition:background .2s;
841    border:none;
842    background:none;
843    width:100%;
844    text-align:left;
845    cursor:pointer;
846  }
847  
848  .mobile-user-sheet-item:active {
849    background:#f3f4f6;
850  }
851  
852  .mobile-user-sheet-icon {
853    font-size:20px;
854    width:24px;
855    text-align:center;
856    flex-shrink:0;
857  }
858  
859  .mobile-user-sheet-label {
860    flex:1;
861    font-weight:500;
862  }
863  
864  .mobile-user-sheet-divider {
865    height:1px;
866    background:#e5e7eb;
867    margin:8px 0;
868  }
869}
870
871/* END NAVBAR */
872
873.logo img { display:block; }
874.search {
875  flex:1;
876  position:relative;
877  display:flex;
878  align-items:center;
879  gap:0;
880  background:#fff;
881  border:1.5px solid #e5e7eb;
882  border-radius:12px;
883  padding:0 16px;
884  height:38px;
885  transition:all 0.3s ease;
886}
887.search:focus-within {
888  border-color:#fb923c;
889  box-shadow:0 0 0 3px rgba(251, 146, 60, 0.1);
890}
891.search input {
892  flex:1;
893  border:none;
894  outline:none;
895  background:transparent;
896  font-size:15px;
897  padding:0 12px;
898  height:100%;
899  color:#1f2937;
900}
901.search input::placeholder {
902  color:#9ca3af;
903}
904.search-icon-btn {
905  background:transparent;
906  border:none;
907  padding:0;
908  display:flex;
909  align-items:center;
910  justify-content:center;
911  cursor:pointer;
912  color:#6b7280;
913  transition:color 0.2s ease;
914  flex-shrink:0;
915}
916.search-icon-btn:hover {
917  color:#fb923c;
918}
919.search-suggest {
920  position:absolute;
921  left:0; right:0; top:100%;
922  background:#fff;
923  border:1px solid var(--border);
924  border-radius:var(--radius);
925  padding:8px 0;
926  box-shadow:0 8px 20px rgba(0,0,0,.08);
927  margin-top:4px;
928  font-size:14px;
929  max-height:260px;
930  overflow:auto;
931}
932.search-suggest div {
933  padding:6px 12px;
934  cursor:pointer;
935}
936.search-suggest div:hover { background:var(--gray-100); }
937
938.nav-actions { display:flex; gap:10px; align-items:center; }
939.icon-btn, .login-btn {
940  background:linear-gradient(135deg, rgba(255,255,255,0.95), rgba(249,250,251,0.9));
941  border:1px solid rgba(228,231,235,0.6);
942  padding:8px 12px;
943  border-radius:var(--radius);
944  cursor:pointer;
945  font-size:14px;
946  transition:all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
947  height: 38px;
948  position:relative;
949  box-shadow:
950    0 1px 3px rgba(0,0,0,0.06),
951    0 0 0 1px rgba(255,255,255,0.5) inset;
952  backdrop-filter:blur(8px);
953  -webkit-backdrop-filter:blur(8px);
954}
955.icon-btn:hover, .login-btn:hover { 
956  background:linear-gradient(135deg, rgba(249,250,251,1), rgba(243,244,246,0.95));
957  border-color:rgba(209,213,219,0.8);
958  box-shadow:
959    0 2px 8px rgba(0,0,0,0.08),
960    0 0 0 1px rgba(255,255,255,0.6) inset;
961  transform:translateY(-1px);
962}
963.cart-indicator, .compare-indicator { position:relative; }
964.badge {
965  background:linear-gradient(135deg, #EF4444, #dcce77);
966  color:#fff;
967  font-size:10.5px;
968  font-weight:600;
969  padding:2px 6px;
970  border-radius:999px;
971  position:absolute;
972  top:-6px; 
973  right:-6px;
974  min-width:18px;
975  height:18px;
976  display:flex;
977  align-items:center;
978  justify-content:center;
979  box-shadow:
980    0 2px 8px rgba(239,68,68,0.4),
981    0 0 0 2px #fff,
982    0 0 12px -2px rgba(239,68,68,0.6);
983  transition:all 0.3s ease;
984}
985.icon-btn:hover .badge {
986  transform:scale(1.08);
987  box-shadow:
988    0 4px 12px rgba(239,68,68,0.5),
989    0 0 0 2px #fff,
990    0 0 16px -2px rgba(239,68,68,0.7);
991}
992.badge.is-empty { display:none; }
993
994/* User Menu Dropdown */
995.user-menu {
996  position:relative;
997}
998.user-dropdown {
999  position:absolute;
1000  top:calc(100% + 8px);
1001  right:0;
1002  background:#fff;
1003  border:1px solid var(--border);
1004  border-radius:12px;
1005  box-shadow:0 10px 40px rgba(0,0,0,0.12);
1006  min-width:200px;
1007  padding:8px 0;
1008  z-index:9999;
1009}
1010
1011/* Ensure dropdown is visible when not hidden */
1012.user-dropdown:not([hidden]) {
1013  display:block !important;
1014  visibility:visible !important;
1015  opacity:1 !important;
1016}
1017
1018/* Mobile: Force user dropdown to be visible above everything */
1019@media (max-width:640px) {
1020  .user-dropdown:not([hidden]) {
1021    position:fixed !important;
1022    z-index:9999 !important;
1023    top:60px !important;
1024    right:16px !important;
1025  }
1026}
1027
1028.dropdown-item {
1029  display:flex;
1030  align-items:center;
1031  gap:12px;
1032  padding:10px 16px;
1033  text-decoration:none;
1034  color:var(--gray-700);
1035  font-size:14px;
1036  transition:var(--transition);
1037  border:none;
1038  background:none;
1039  width:100%;
1040  text-align:left;
1041  cursor:pointer;
1042}
1043.dropdown-item:hover {
1044  background:var(--gray-100);
1045  color:var(--gray-900);
1046}
1047.dropdown-icon {
1048  font-size:16px;
1049  opacity:0.7;
1050}
1051.dropdown-separator {
1052  height:1px;
1053  background:var(--border);
1054  margin:8px 0;
1055}
1056.login-btn.logged-in {
1057  background:linear-gradient(135deg, rgba(255,245,230,0.8), rgba(255,231,204,0.6));
1058  border:1px solid rgba(242,140,26,0.2);
1059  color:var(--brand-700);
1060  max-width:200px; /* Prevent button from getting too wide */
1061  display:inline-flex;
1062  align-items:center;
1063  gap:10px;
1064  padding:4px 12px 4px 4px;
1065  transition:all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1066  position:relative;
1067  overflow:hidden;
1068  box-shadow:
1069    0 1px 3px rgba(242,140,26,0.08),
1070    0 0 0 1px rgba(255,255,255,0.5) inset,
1071    0 0 12px -6px rgba(242,140,26,0.1);
1072  backdrop-filter:blur(8px);
1073  -webkit-backdrop-filter:blur(8px);
1074}
1075/* Subtle background glow effect */
1076.login-btn.logged-in::before {
1077  content:'';
1078  position:absolute;
1079  top:50%;
1080  left:0;
1081  width:32px;
1082  height:32px;
1083  background:radial-gradient(circle, rgba(242,140,26,0.15), transparent 70%);
1084  transform:translateY(-50%);
1085  opacity:0;
1086  transition:opacity 0.3s ease;
1087}
1088.login-btn.logged-in:hover::before {
1089  opacity:1;
1090}
1091.login-btn.logged-in .user-avatar {
1092  width:32px;
1093  height:32px;
1094  border-radius:50%;
1095  background:linear-gradient(135deg, #FFE7CC 0%, #FFD9A8 50%, #FFCC80 100%);
1096  color:#D97809;
1097  font-size:11.5px;
1098  font-weight:700;
1099  display:flex;
1100  align-items:center;
1101  justify-content:center;
1102  flex-shrink:0;
1103  letter-spacing:0.3px;
1104  position:relative;
1105  box-shadow:
1106    0 2px 8px rgba(242,140,26,0.25),
1107    0 1px 3px rgba(0,0,0,0.12),
1108    inset 0 1px 0 rgba(255,255,255,0.5);
1109  transition:all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1110}
1111/* Ring gradient border */
1112.login-btn.logged-in .user-avatar::before {
1113  content:'';
1114  position:absolute;
1115  inset:-2px;
1116  border-radius:50%;
1117  padding:2px;
1118  background:linear-gradient(135deg, #F28C1A, #FFD9A8, #F28C1A);
1119  -webkit-mask:linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
1120  -webkit-mask-composite:xor;
1121  mask:linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
1122  mask-composite:exclude;
1123  opacity:0;
1124  transition:opacity 0.3s ease;
1125}
1126.login-btn.logged-in:hover .user-avatar::before {
1127  opacity:1;
1128}
1129.login-btn.logged-in:hover .user-avatar {
1130  transform:scale(1.08) rotate(-3deg);
1131  box-shadow:
1132    0 4px 12px rgba(242,140,26,0.35),
1133    0 2px 6px rgba(0,0,0,0.15),
1134    inset 0 1px 0 rgba(255,255,255,0.6);
1135}
1136.login-btn.logged-in .login-text {
1137  overflow:hidden;
1138  text-overflow:ellipsis;
1139  white-space:nowrap;
1140  flex:1;
1141  min-width:0;
1142  font-weight:500;
1143  font-size:14px;
1144  transition:color 0.2s ease;
1145}
1146.login-btn.logged-in:hover .login-text {
1147  color:var(--brand-600);
1148}
1149.login-btn.logged-in:hover {
1150  background:linear-gradient(135deg, rgba(255,245,230,0.95), rgba(255,231,204,0.85));
1151  border-color:rgba(242,140,26,0.3);
1152  box-shadow:
1153    0 4px 16px -4px rgba(242,140,26,0.25),
1154    0 0 0 1px rgba(242,140,26,0.1) inset,
1155    0 1px 2px rgba(0,0,0,0.05);
1156  transform:translateY(-1px);
1157}
1158
1159/* Desktop: hide wrapper elements, show menu normally */
1160@media (min-width: 761px) {
1161  .mega-cats-wrapper {
1162    display:contents; /* Remove wrapper from layout */
1163  }
1164  .mega-cats-fade {
1165    display:none !important; /* Hide fade on desktop */
1166  }
1167}
1168
1169.mega-cats {
1170  display:flex;
1171  gap:5px;
1172  /* padding:6px 16px 10px; */
1173  scrollbar-width:none;
1174  position: relative;
1175  z-index: 510;
1176  overflow-x:auto;
1177  -webkit-overflow-scrolling:touch;
1178  flex:1;
1179}
1180.mega-cats::-webkit-scrollbar { 
1181  display:none; 
1182}
1183.mega-cats a {
1184  text-decoration:none;
1185  font-size:14px;
1186  color:var(--gray-700);
1187  padding:6px 10px;
1188  border-radius:6px;
1189  white-space:nowrap;
1190  transition:var(--transition);
1191}
1192.mega-cats a:hover { background:var(--brand-100); color:var(--brand-600); }
1193/*.mega-cats::after {
1194  content:'';
1195  position:absolute;
1196  right:0;
1197  top:0;
1198  width:40px;
1199  height:100%;
1200  background:linear-gradient(to right,rgba(255,255,255,0),#fff);
1201  pointer-events:none;
1202}*/
1203
1204/* Tambahan untuk tombol More & dropdown.
1205   Style dasar .mega-cats dan .mega-cats a tetap pakai definisi Anda. */
1206
1207.mega-cats-more {
1208  position:relative;
1209  display:flex;
1210  align-items:center;
1211}
1212
1213.mega-cats-more.active > .mega-more-btn {
1214  background:var(--brand-600);
1215  color:#fff;
1216}
1217
1218.mega-more-btn {
1219  cursor:pointer;
1220  border:none;
1221  background:var(--gray-100,#f5f5f5);
1222  color:var(--gray-700,#333);
1223  padding:6px 12px;
1224  font-size:16px;
1225  line-height:1;
1226  border-radius:6px;
1227  display:flex;
1228  align-items:center;
1229  justify-content:center;
1230  transition:var(--transition);
1231}
1232.mega-more-btn:hover {
1233  background:var(--brand-100);
1234  color:var(--brand-600);
1235}
1236
1237.mega-more-dropdown {
1238  position:absolute;
1239  top:100%;
1240  right:0;
1241  margin-top:8px;
1242  background:#fff;
1243  border:1px solid var(--gray-200,#e3e6e9);
1244  border-radius:10px;
1245  padding:12px 14px 14px;
1246  min-width:400px;
1247  max-width:520px;
1248  max-height:420px;
1249  box-shadow:0 12px 32px -8px rgba(25,40,65,.25);
1250  z-index: 3000; 
1251}
1252
1253.mega-more-grid {
1254  display:grid;
1255  grid-template-columns:repeat(auto-fill,minmax(120px,1fr));
1256  gap:8px 10px;
1257  overflow-x: hidden;
1258  max-height:380px;
1259  overscroll-behavior:contain;
1260  scrollbar-width:thin;
1261}
1262.mega-more-grid::-webkit-scrollbar {
1263  width:6px;
1264}
1265.mega-more-grid::-webkit-scrollbar-thumb {
1266  background:var(--gray-300,#c7ccd1);
1267  border-radius:3px;
1268}
1269
1270/* Link di dropdown mewarisi style .mega-cats a (warna, padding, radius).
1271   Kita tambahkan state active agar konsisten. */
1272.mega-more-grid a.active {
1273  background:var(--brand-600);
1274  color:#fff;
1275  font-weight:600;
1276}
1277.hero {
1278  background:linear-gradient(135deg,var(--brand-50), #fff);
1279}
1280.hero-inner {
1281  display:grid;
1282  grid-template-columns:repeat(auto-fit,minmax(320px,1fr));
1283  gap:48px;
1284  align-items:center;
1285}
1286.hero-text h1 {
1287  font-size:48px;
1288  line-height:1.1;
1289  margin:0;
1290  font-weight:600;
1291  font-family:"Poppins",sans-serif;
1292}
1293.hero-text .text-brand { color:var(--brand-500); }
1294.lead { font-size:18px; color:var(--gray-700); margin-top:16px; }
1295.hero-cta { margin-top:28px; display:flex; gap:16px; }
1296.hero-bullets { margin-top:28px; display:flex; gap:28px; font-size:14px; color:var(--gray-600); }
1297.hero-box {
1298  background:#fff;
1299  padding:10px;
1300  border-radius:16px;
1301  box-shadow:0 4px 18px rgba(0,0,0,.08);
1302  /* min-height:320px; */
1303  display:flex;
1304  align-items:center;
1305  justify-content:center;
1306}
1307.hero-box img {border-radius: 10px; }
1308
1309@media (max-width:680px) {
1310    .hero-inner { gap: 25px; }    
1311}
1312
1313.usp-section {
1314  position:relative;
1315  background:
1316    linear-gradient(180deg,#FFFDFB 0%, #FFFFFF 60%) ;
1317  border-top:1px solid var(--border);
1318  border-bottom:1px solid var(--border);
1319  padding:10px 0 10px;
1320}
1321
1322.usp-list {
1323  list-style:none;
1324  margin:0;
1325  padding:0;
1326  display:grid;
1327  gap:26px;
1328}
1329
1330.usp-list.variant-soft {
1331  grid-template-columns:repeat(auto-fit,minmax(230px,1fr));
1332}
1333
1334.usp-item {
1335  display:flex;
1336  gap:16px;
1337  align-items:flex-start;
1338  background:#fff;
1339  padding:18px 18px 18px 16px;
1340  border:1px solid rgba(226,226,226,0.7);
1341  border-radius:18px;
1342  position:relative;
1343  overflow:hidden;
1344  isolation:isolate;
1345  box-shadow:0 2px 4px -1px rgba(0,0,0,.04), 0 6px 18px -6px rgba(0,0,0,.08);
1346  transition: .35s cubic-bezier(.16,.8,.32,1);
1347}
1348
1349.usp-item::before {
1350  content:"";
1351  position:absolute;
1352  inset:0;
1353  background:radial-gradient(circle at 18% 22%, rgba(242,140,26,.18), transparent 60%);
1354  opacity:.65;
1355  transition:.5s;
1356  z-index:-1;
1357}
1358
1359.usp-item:hover {
1360  transform:translateY(-6px);
1361  box-shadow:0 10px 26px -8px rgba(0,0,0,.15), 0 3px 8px -2px rgba(0,0,0,.08);
1362  border-color:rgba(242,140,26,.35);
1363}
1364
1365.usp-item:hover::before {
1366  background:radial-gradient(circle at 70% 40%, rgba(242,140,26,.22), transparent 65%);
1367}
1368
1369.usp-icon {
1370  flex:0 0 56px;
1371  width:56px;
1372  height:56px;
1373  border-radius:16px;
1374  display:flex;
1375  align-items:center;
1376  justify-content:center;
1377  background:
1378    linear-gradient(135deg,#FFE7CC,#FFD9A8);
1379  position:relative;
1380  box-shadow:inset 0 0 0 1px rgba(255,255,255,.6), 0 4px 10px -3px rgba(0,0,0,.15);
1381  transition:.45s;
1382}
1383
1384.usp-item:hover .usp-icon {
1385  transform:rotate(-4deg) scale(1.06);
1386  box-shadow:inset 0 0 0 1px rgba(255,255,255,.8), 0 6px 14px -4px rgba(0,0,0,.20);
1387}
1388
1389.usp-icon svg {
1390  width:28px;
1391  height:28px;
1392  display:block;
1393  filter:drop-shadow(0 2px 2px rgba(0,0,0,.18));
1394}
1395
1396.usp-text h3 {
1397  font-size:15px;
1398  font-weight:600;
1399  margin:2px 0 6px;
1400  letter-spacing:.2px;
1401}
1402
1403.usp-text p {
1404  font-size:13px;
1405  line-height:1.3;
1406  margin:0;
1407  color:var(--gray-600);
1408}
1409
1410@media (max-width:680px) {
1411  .usp-section { padding: 5px 0 5px; margin-bottom: 0.75rem;}
1412  .usp-item { padding:16px 14px; }
1413  .usp-icon { width:50px;height:50px;border-radius:14px; }
1414  .usp-text h3 { font-size:14px; margin-bottom:4px; }
1415  .usp-text p { font-size:12.5px; }
1416  .usp-list { gap: 10px; }
1417}
1418
1419.flash { padding:50px 0 30px; background:#fff; }
1420.flash-head {
1421  display:flex; align-items:center;
1422  justify-content:space-between;
1423  margin-bottom:20px;
1424}
1425.flash-head h2 { margin:0; font-size:24px; font-family:"Poppins"; }
1426.flash .countdown {
1427  font-family:monospace;
1428  background:#111;
1429  color:#fff;
1430  padding:8px 14px;
1431  border-radius:8px;
1432  font-size:16px;
1433  letter-spacing:1px;
1434}
1435.flash-scroll {
1436  display:flex;
1437  gap:16px;
1438  overflow-x:auto;
1439  padding-bottom:6px;
1440  scrollbar-width:none;
1441}
1442.flash-scroll::-webkit-scrollbar { display:none; }
1443
1444.products { padding:60px 0; background:#fff; }
1445.products-head {
1446  display:flex; justify-content:space-between; align-items:center;
1447  margin-bottom:24px;
1448  flex-wrap:wrap;
1449  gap:16px;
1450}
1451.products-head h2 { margin:0; font-size:24px; font-family:"Poppins"; }
1452.product-filters input {
1453  padding:8px 12px;
1454  border:1px solid var(--border);
1455  border-radius:8px;
1456  font-size:14px;
1457  width:220px;
1458}
1459
1460.product-grid {
1461  display:grid;
1462  gap:24px;
1463  grid-template-columns:repeat(auto-fill,minmax(180px,1fr));
1464}
1465
1466.prod-card {
1467  background:#fff;
1468  border:1px solid var(--border);
1469  padding:12px;
1470  border-radius:12px;
1471  position:relative;
1472  display:flex;
1473  flex-direction:column;
1474  transition:var(--transition);
1475}
1476.prod-card.small { min-width:160px; }
1477.prod-card:hover { box-shadow:0 8px 20px rgba(0,0,0,.12); transform:translateY(-3px); }
1478.prod-card .img-box {
1479  background:var(--gray-100);
1480  border-radius:10px;
1481  aspect-ratio:1/1;
1482  display:flex;
1483  align-items:center;
1484  justify-content:center;
1485  overflow:hidden;
1486}
1487.prod-card .img-box img { width:100%; height:100%; object-fit:contain; transition:var(--transition); }
1488.prod-card:hover .img-box img { transform:scale(1.04); }
1489.prod-card .name {
1490  font-size:13.5px;
1491  font-weight:500;
1492  margin:10px 0 6px;
1493  min-height:36px;
1494  line-height:1.3;
1495}
1496.price {
1497  display:flex;
1498  gap:6px;
1499  align-items:center;
1500  flex-wrap:wrap;
1501}
1502.price strong { color:var(--brand-600); font-size:14px; }
1503.price .old {
1504  font-size:11px;
1505  color:var(--gray-500);
1506  text-decoration:line-through;
1507}
1508.tag.discount {
1509  position:absolute;
1510  top:8px; left:8px;
1511  background:#E54848;
1512  color:#fff;
1513  font-size:11px;
1514  padding:4px 6px;
1515  border-radius:6px;
1516  font-weight:600;
1517}
1518.btn {
1519  border:none;
1520  cursor:pointer;
1521  font-weight:500;
1522  font-size:14px;
1523  padding:12px 22px;
1524  border-radius:10px;
1525  background:var(--gray-900);
1526  color:#fff;
1527  transition:var(--transition);
1528  text-decoration:none;
1529  text-align:center;
1530  display:inline-flex;
1531  align-items:center;
1532  justify-content:center;
1533  gap:6px;
1534}
1535.btn:hover { background:#000; }
1536.btn-primary { background:var(--brand-500); }
1537.btn-primary:hover { background:var(--brand-600); }
1538.btn-outline {
1539  background:#fff;
1540  color:var(--gray-900);
1541  border:1px solid var(--border);
1542}
1543.btn-outline:hover { background:var(--gray-100); }
1544.btn-sm { padding:8px 14px; font-size:13px; }
1545.btn-mini { padding:6px 10px; font-size:12px; }
1546.add-cart { margin-top:auto; background:var(--brand-500); color:#fff; }
1547.add-cart:hover { background:var(--brand-600); }
1548
1549/* ==== FOOTER COMPACT FIX PATCH (final) ==== */
1550
1551/* 1. Pastikan grid aktif */
1552
1553.site-footer.footer-compact .ft-grid {
1554  display:grid;
1555  grid-template-columns:repeat(auto-fit,minmax(240px,1fr));
1556  gap:30px 40px;
1557  align-items:start;
1558}
1559
1560/* 2. Spacing atas bawah dipadatkan */
1561.site-footer.footer-compact .ft-top {
1562  padding:42px 0 26px;
1563}
1564
1565/* 3. Judul & teks */
1566.site-footer.footer-compact .ft-title {
1567  margin:0 0 14px;
1568  font-size:14px;
1569  letter-spacing:.8px;
1570  text-transform:uppercase;
1571  font-weight:600;
1572  color:#82b1c9;
1573}
1574
1575.site-footer.footer-compact .ft-desc,
1576.site-footer.footer-compact .ft-text {
1577  margin:0 0 16px;
1578  font-size:13.5px;
1579  line-height:1.45;
1580  color:#c6d6e0;
1581  max-width:330px;
1582}
1583.site-footer.footer-compact .ft-desc.sm-gap { margin-top:18px; }
1584
1585/* 4. Reset ikon SVG agar tidak membesar */
1586.site-footer.footer-compact svg {
1587  width:20px;
1588  height:20px;
1589  flex:0 0 20px;
1590  display:inline-block;
1591}
1592.site-footer.footer-compact .ft-social svg { width:18px; height:18px; }
1593.site-footer.footer-compact .ft-wa-btn svg { width:18px; height:18px; }
1594
1595/* Jika ada aturan global svg { width:100%; } — paksa override */
1596svg { max-width:100%; }
1597.site-footer.footer-compact svg {
1598  width:20px !important;
1599  height:20px !important;
1600}
1601
1602/* 5. Link list */
1603.site-footer.footer-compact .ft-links {
1604  list-style:none;
1605  margin:0 0 12px;
1606  padding:0;
1607  display:flex;
1608  flex-direction:column;
1609  gap:4px;
1610}
1611.site-footer.footer-compact .ft-links li { margin:0; }
1612.site-footer.footer-compact .ft-links a {
1613  display:flex;
1614  align-items:center;
1615  gap:8px;
1616  font-size:13.5px;
1617  padding:5px 2px;
1618  text-decoration:none;
1619  color:#c6d6e0;
1620  border-radius:8px;
1621  transition:.25s;
1622}
1623.site-footer.footer-compact .ft-links a:hover {
1624  background:rgba(255,255,255,.06);
1625  padding-left:6px;
1626  color:#fff;
1627}
1628
1629.site-footer.footer-compact .ico-s {
1630  width:24px;
1631  height:24px;
1632  background:#103346;
1633  color:#89b8cf;
1634  border-radius:6px;
1635  display:flex;
1636  align-items:center;
1637  justify-content:center;
1638  flex:0 0 24px;
1639  font-size:12px;
1640  transition:.3s;
1641}
1642.site-footer.footer-compact .ft-links a:hover .ico-s {
1643  background:#F28C1A;
1644  color:#fff;
1645  transform:rotate(-4deg);
1646}
1647
1648/* 6. WhatsApp button */
1649.site-footer.footer-compact .ft-wa-btn {
1650  display:inline-flex;
1651  align-items:center;
1652  gap:8px;
1653  background:#fff;
1654  color:#062437;
1655  padding:8px 18px;
1656  font-size:13.5px;
1657  border-radius:22px;
1658  font-weight:500;
1659  text-decoration:none;
1660  box-shadow:0 3px 8px -3px rgba(0,0,0,.45);
1661  transition:.25s;
1662}
1663.site-footer.footer-compact .ft-wa-btn:hover {
1664  transform:translateY(-2px);
1665  box-shadow:0 6px 14px -6px rgba(0,0,0,.55);
1666}
1667
1668/* 7. Sosial */
1669.site-footer.footer-compact .ft-social {
1670  display:flex;
1671  gap:12px;
1672  margin-top:4px;
1673}
1674.site-footer.footer-compact .ft-social a {
1675  width:34px;
1676  height:34px;
1677  background:rgba(255,255,255,.08);
1678  display:flex;
1679  align-items:center;
1680  justify-content:center;
1681  border-radius:10px;
1682  color:#c6d6e0;
1683  transition:.25s;
1684}
1685.site-footer.footer-compact .ft-social a:hover {
1686  background:#F28C1A;
1687  color:#fff;
1688}
1689
1690/* 8. Alamat */
1691.site-footer.footer-compact .ft-address {
1692  font-size:12.5px;
1693  line-height:1.44;
1694  color:#8fa4b1;
1695  max-width:330px;
1696  margin-top:12px;
1697}
1698.site-footer.footer-compact .ft-address .company {
1699  margin:0 0 4px;
1700  font-weight:600;
1701  color:#e5f2f7;
1702  font-size:12.8px;
1703}
1704
1705/* 9. Payments kecil & rapi */
1706.site-footer.footer-compact .ft-payments {
1707  margin-top:14px;
1708  display:flex;
1709  flex-direction:column;
1710  gap:8px;
1711  max-width:350px;
1712}
1713.site-footer.footer-compact .ft-payments .pay-row {
1714  display:flex;
1715  gap:8px;
1716  flex-wrap:wrap;
1717}
1718.site-footer.footer-compact .ft-payments img {
1719  width:46px;
1720  height:32px;
1721  padding:3px 5px;
1722  background:#fff;
1723  border-radius:7px;
1724  object-fit:contain;
1725  box-shadow:0 2px 6px -3px rgba(0,0,0,.45);
1726  transition:.25s;
1727}
1728.site-footer.footer-compact .ft-payments img:hover {
1729  transform:translateY(-3px);
1730  box-shadow:0 5px 12px -6px rgba(0,0,0,.55);
1731}
1732
1733/* 10. Bottom bar */
1734.site-footer.footer-compact .ft-bottom {
1735  border-top:1px solid rgba(255,255,255,.08);
1736  background:#041f31;
1737  padding:12px 0 16px;
1738  margin-top:4px;
1739}
1740.site-footer.footer-compact .ft-copy {
1741  font-size:11.5px;
1742  color:#8fa7b5;
1743  display:flex;
1744  flex-wrap:wrap;
1745  gap:12px;
1746  justify-content:center;
1747  line-height:1.3;
1748  margin:0;
1749}
1750.site-footer.footer-compact .ft-copy span {
1751  position:relative;
1752  padding:0 10px;
1753}
1754.site-footer.footer-compact .ft-copy span+span:before {
1755  content:"";
1756  position:absolute;
1757  left:0;
1758  top:50%;
1759  width:1px;
1760  height:12px;
1761  background:rgba(255,255,255,.14);
1762  transform:translateY(-50%);
1763}
1764@media (max-width:580px){
1765  .site-footer.footer-compact .ft-top { padding:34px 0 18px; }
1766  .site-footer.footer-compact .ft-grid { gap:26px 20px; }
1767  .site-footer.footer-compact .ft-copy span { padding:0; }
1768  .site-footer.footer-compact .ft-copy span+span:before { display:none; }
1769}
1770.copy {
1771  margin-top:40px;
1772  border-top:1px solid #222;
1773  text-align:center;
1774  padding:18px 0;
1775  font-size:12px;
1776  color:#666;
1777}
1778
1779@media (max-width:760px) {
1780  .hero-text h1 { font-size:38px; }
1781  .hero { padding:0px 0 30px; }
1782  .topbar-inner { flex-direction:column; gap:4px; padding:6px 0; }
1783  .nav-inner { flex-wrap:wrap; }
1784  .mega-cats { gap:10px; }
1785  .product-grid { gap:16px; }
1786  .prod-card { padding:10px; }
1787}
1788
1789/* === PLAZA IT DARK ORANGE FOOTER THEME === */
1790.site-footer.footer-compact {
1791  --ft-bg-0:#051722;
1792  --ft-bg-1:#072534;
1793  --ft-bg-2:#0B2F40;
1794  --ft-bg-3:#103A4E;
1795  --ft-orange:#F28C1A;
1796  --ft-orange-dark:#D97809;
1797  --ft-text:#E9F2F6;
1798  --ft-text-sec:#B8CAD3;
1799  --ft-text-muted:#8FA4B1;
1800  --ft-divider:rgba(255,255,255,0.08);
1801  --ft-glow:rgba(242,140,26,0.18);
1802  --ft-glow-fade:rgba(242,140,26,0);
1803  color:var(--ft-text);
1804}
1805
1806/* BASE TEXT / LINKS (override sebelumnya jika perlu) */
1807.site-footer.footer-compact .ft-title { color:#9CC5D7; }
1808.site-footer.footer-compact .ft-links a { color:var(--ft-text-sec); }
1809.site-footer.footer-compact .ft-links a:hover { color:#fff; }
1810.site-footer.footer-compact .ft-address { color:var(--ft-text-muted); }
1811.site-footer.footer-compact .ft-copy { color:#9AB2BD; }
1812
1813/* ICON BACKGROUND (kubu gelap) */
1814.site-footer.footer-compact .ico-s {
1815  background:#0F3647;
1816  color:#9FC7DA;
1817}
1818.site-footer.footer-compact .ft-links a:hover .ico-s {
1819  background:var(--ft-orange);
1820  color:#fff;
1821}
1822
1823/* WhatsApp button biarkan putih agar kontras */
1824.site-footer.footer-compact .ft-wa-btn {
1825  box-shadow:0 4px 12px -5px rgba(0,0,0,.6), 0 0 0 3px rgba(255,255,255,0.05);
1826}
1827.site-footer.footer-compact .ft-wa-btn:hover {
1828  box-shadow:0 8px 20px -8px rgba(0,0,0,.65), 0 0 0 3px rgba(242,140,26,0.25);
1829}
1830
1831/* Payment cards border halus */
1832.site-footer.footer-compact .ft-payments img {
1833  box-shadow:0 2px 6px -3px rgba(0,0,0,.55), 0 0 0 1px rgba(255,255,255,0.05);
1834}
1835.site-footer.footer-compact .ft-payments img:hover {
1836  box-shadow:0 6px 14px -6px rgba(0,0,0,.65), 0 0 0 1px rgba(242,140,26,0.4);
1837}
1838
1839/* ========== VARIANT A: DIAGONAL EMBER (AKTIF) ========== */
1840.site-footer.footer-compact.variant-ember {
1841  background:
1842    radial-gradient(circle at 78% 82%, var(--ft-glow) 0%, var(--ft-glow-fade) 55%),
1843    linear-gradient(140deg,var(--ft-bg-1) 0%, var(--ft-bg-2) 55%, var(--ft-bg-0) 100%);
1844}
1845.site-footer.footer-compact.variant-ember .ft-bottom {
1846  background:rgba(0,0,0,0.25);
1847  backdrop-filter:blur(2px);
1848}
1849
1850/* ========== VARIANT B: TOP BAR ORANGE ========== */
1851.site-footer.footer-compact.variant-bar {
1852  background:var(--ft-bg-1);
1853  position:relative;
1854}
1855.site-footer.footer-compact.variant-bar::before {
1856  content:"";
1857  position:absolute;
1858  left:0;top:0;
1859  width:100%;height:3px;
1860  background:linear-gradient(90deg,var(--ft-orange-dark),var(--ft-orange));
1861  box-shadow:0 0 12px -2px var(--ft-orange);
1862}
1863.site-footer.footer-compact.variant-bar .ft-bottom {
1864  background:#041c28;
1865}
1866
1867/* ========== VARIANT C: RADIAL GLOW BAWAH ========== */
1868.site-footer.footer-compact.variant-radial {
1869  background:
1870    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%),
1871    linear-gradient(150deg,#052131 0%, #062d3f 50%, #041924 100%);
1872}
1873.site-footer.footer-compact.variant-radial .ft-bottom {
1874  background:#041c29;
1875}
1876
1877/* ========== VARIANT D: LAYERED STRIPES ========== */
1878.site-footer.footer-compact.variant-stripes {
1879  background:
1880    repeating-linear-gradient(180deg, rgba(255,255,255,0.015) 0 38px, rgba(255,255,255,0) 38px 76px),
1881    linear-gradient(130deg,#052636 0%, #062a3c 60%, #041d28 100%);
1882  position:relative;
1883}
1884.site-footer.footer-compact.variant-stripes::after {
1885  content:"";
1886  position:absolute;
1887  left:0;bottom:0;
1888  width:100%;height:110px;
1889  background:linear-gradient(0deg,rgba(0,0,0,.35),rgba(0,0,0,0));
1890  pointer-events:none;
1891}
1892.site-footer.footer-compact.variant-stripes .ft-bottom {
1893  background:#031821;
1894}
1895
1896/* Garis aksen oranye tipis di atas bottom */
1897.site-footer.footer-compact.variant-stripes .ft-bottom {
1898  position:relative;
1899}
1900.site-footer.footer-compact.variant-stripes .ft-bottom::before {
1901  content:"";
1902  position:absolute;
1903  left:0;top:0;
1904  width:100%;height:2px;
1905  background:linear-gradient(90deg,rgba(242,140,26,0),rgba(242,140,26,.65),rgba(242,140,26,0));
1906}
1907
1908/* ========== VARIANT E: GLASS PANEL ========== */
1909.site-footer.footer-compact.variant-glass {
1910  background:linear-gradient(145deg,#051c29 0%, #041620 65%, #03121b 100%);
1911}
1912.site-footer.footer-compact.variant-glass .ft-grid {
1913  position:relative;
1914  padding:32px clamp(10px,2vw,24px);
1915  border-radius:22px;
1916  background:linear-gradient(140deg,rgba(255,255,255,0.06),rgba(255,255,255,0.02));
1917  border:1px solid rgba(255,255,255,0.08);
1918  box-shadow:0 12px 28px -12px rgba(0,0,0,0.45), 0 0 0 1px rgba(255,255,255,0.04) inset;
1919  backdrop-filter:blur(8px);
1920  -webkit-backdrop-filter:blur(8px);
1921}
1922.site-footer.footer-compact.variant-glass .ft-bottom {
1923  background:#02141d;
1924}
1925
1926/* HOVER ACCENTS */
1927.site-footer.footer-compact a {
1928  transition:color .25s, background .25s, box-shadow .25s;
1929}
1930.site-footer.footer-compact .ft-links a:hover {
1931  box-shadow:0 2px 6px -3px rgba(0,0,0,.5),
1932             0 0 0 1px rgba(242,140,26,0.55);
1933}
1934.site-footer.footer-compact .ft-social a {
1935  background:#0E3445;
1936  color:#bcd4dd;
1937}
1938.site-footer.footer-compact .ft-social a:hover {
1939  background:var(--ft-orange);
1940  color:#fff;
1941  box-shadow:0 0 0 3px rgba(242,140,26,0.28);
1942}
1943
1944/* Divider di atas footer (opsional) */
1945.footer-divider-top {
1946  height:1px;
1947  background:linear-gradient(90deg,transparent,rgba(255,255,255,0.15),transparent);
1948  width:100%;
1949  margin:0 0 4px;
1950}
1951
1952/* ==== MOBILE FOOTER COLLAPSE (Non-Homepage Only) ==== */
1953
1954/* Desktop: wrapper tidak mengganggu grid layout */
1955.ft-collapsible-content {
1956  display: contents;
1957}
1958
1959@media (max-width: 768px) {
1960  /* Wrapper untuk konten yang bisa collapse */
1961  .ft-top.ft-collapsible .ft-collapsible-content {
1962    display: block;
1963    max-height: 80px;
1964    overflow: hidden;
1965    position: relative;
1966    transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1);
1967  }
1968  
1969  /* Fade gradient overlay */
1970  .ft-top.ft-collapsible .ft-collapsible-content::after {
1971    content: '';
1972    position: absolute;
1973    bottom: 0;
1974    left: 0;
1975    right: 0;
1976    height: 70px;
1977    background: linear-gradient(to bottom, transparent 0%, rgba(6, 43, 69, 0.6) 40%, #062b45 100%);
1978    pointer-events: none;
1979    opacity: 1;
1980    transition: opacity 0.3s ease;
1981    z-index: 1;
1982  }
1983  
1984  /* State: Expanded */
1985  .ft-top.ft-collapsible.expanded .ft-collapsible-content {
1986    max-height: 1500px;
1987  }
1988  
1989  .ft-top.ft-collapsible.expanded .ft-collapsible-content::after {
1990    opacity: 0;
1991  }
1992  
1993  /* Expand Button - Di luar collapsible, selalu visible */
1994  .ft-expand-btn {
1995    display: flex;
1996    align-items: center;
1997    justify-content: center;
1998    gap: 8px;
1999    width: 100%;
2000    margin: -8px 0 0;
2001    padding: 12px 20px;
2002    background: rgba(255,255,255,0.12);
2003    border: 1px solid rgba(255,255,255,0.25);
2004    border-radius: 10px;
2005    color: #e5f2f7;
2006    font-size: 13.5px;
2007    font-weight: 500;
2008    cursor: pointer;
2009    transition: all 0.3s ease;
2010    outline: none;
2011    position: relative;
2012    z-index: 10;
2013  }
2014  
2015  .ft-expand-btn:hover {
2016    background: rgba(255,255,255,0.18);
2017    border-color: rgba(255,255,255,0.35);
2018    color: #fff;
2019    transform: translateY(-2px);
2020  }
2021  
2022  .ft-expand-btn:active {
2023    transform: scale(0.98);
2024  }
2025  
2026  .ft-expand-btn svg {
2027    width: 18px;
2028    height: 18px;
2029    transition: transform 0.3s ease;
2030    fill: currentColor;
2031  }
2032  
2033  .ft-top.ft-collapsible.expanded .ft-expand-btn {
2034    margin-top: 15px;
2035  }
2036  
2037  .ft-top.ft-collapsible.expanded .ft-expand-btn svg {
2038    transform: rotate(180deg);
2039  }
2040}
2041
2042/* Desktop: hide expand button */
2043@media (min-width: 769px) {
2044  .ft-expand-btn {
2045    display: none !important;
2046  }
2047}
2048
2049/* RESPONSIVE (jika background panel butuh penyesuaian) */
2050@media (max-width:640px){
2051  .site-footer.footer-compact.variant-glass .ft-grid {
2052    padding:26px 18px;
2053  }
2054}
2055
2056</style>
2057  <style data-inlined="bottom-nav.css">
2058/* Mobile Bottom Navigation */
2059.bottom-nav {
2060  position: fixed;
2061  bottom: 0;
2062  left: 0;
2063  right: 0;
2064  background: #ffffff;
2065  border-top: 1px solid #e5e7eb;
2066  z-index: 9998; /* Below chat widget */
2067  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
2068  display: none; /* Hidden on desktop */
2069}
2070
2071.bottom-nav-inner {
2072  display: flex;
2073  justify-content: space-around;
2074  align-items: center;
2075  padding: 2px 0;
2076  max-width: 640px;
2077  margin: 0 auto;
2078}
2079
2080.bottom-nav-item {
2081  flex: 1;
2082  display: flex;
2083  flex-direction: column;
2084  align-items: center;
2085  justify-content: center;
2086  gap: 4px;
2087  padding: 8px 8px;
2088  background: none;
2089  border: none;
2090  cursor: pointer;
2091  text-decoration: none;
2092  color: #6b7280;
2093  transition: all 0.3s ease;
2094  position: relative;
2095}
2096
2097.bottom-nav-item:hover {
2098  color: #F28C1A;
2099}
2100
2101.bottom-nav-item.active {
2102  flex-direction: row;
2103  gap: 8px;
2104  background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
2105  color: white;
2106  border-radius: 20px;
2107  padding: 8px 16px;
2108  box-shadow: 0 2px 8px rgba(242, 140, 26, 0.3);
2109}
2110
2111.bottom-nav-item-icon {
2112  width: 24px;
2113  height: 24px;
2114  position: relative;
2115}
2116
2117.bottom-nav-item-icon svg {
2118  width: 100%;
2119  height: 100%;
2120}
2121
2122/* Icon variant switching */
2123.bottom-nav-item .icon-outlined {
2124  display: block;
2125}
2126
2127.bottom-nav-item .icon-filled {
2128  display: none;
2129}
2130
2131.bottom-nav-item.active .icon-outlined {
2132  display: none;
2133}
2134
2135.bottom-nav-item.active .icon-filled {
2136  display: block;
2137}
2138
2139.bottom-nav-item-label {
2140  font-size: 12px;
2141  font-weight: 600;
2142  line-height: 1;
2143  display: none;
2144  white-space: nowrap;
2145}
2146
2147.bottom-nav-item.active .bottom-nav-item-label {
2148  display: block;
2149}
2150
2151/* Avatar styles for bottom-nav */
2152.bottom-nav-avatar-img,
2153.bottom-nav-avatar-initials {
2154  width: 24px;
2155  height: 24px;
2156  border-radius: 50%;
2157  display: block;
2158}
2159
2160.bottom-nav-avatar-img {
2161  object-fit: cover;
2162}
2163
2164.bottom-nav-avatar-initials {
2165  background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
2166  color: white;
2167  font-size: 10px;
2168  font-weight: 700;
2169  display: flex;
2170  align-items: center;
2171  justify-content: center;
2172  line-height: 1;
2173  letter-spacing: 0.5px;
2174}
2175
2176/* Hide SVG when avatar is shown */
2177.bottom-nav-item-icon:has(.bottom-nav-avatar-img) .bottom-nav-svg-icon,
2178.bottom-nav-item-icon:has(.bottom-nav-avatar-initials) .bottom-nav-svg-icon {
2179  display: none;
2180}
2181
2182/* Active state adjustments for avatar */
2183.bottom-nav-item.active .bottom-nav-avatar-initials {
2184  background: white;
2185  color: #F28C1A;
2186  border: 2px solid rgba(255, 255, 255, 0.8);
2187  box-sizing: border-box;
2188}
2189
2190.bottom-nav-item.active .bottom-nav-avatar-img {
2191  border: 2px solid rgba(255, 255, 255, 0.9);
2192  box-sizing: border-box;
2193}
2194
2195.bottom-nav-item-badge {
2196  position: absolute;
2197  top: -4px;
2198  right: -4px;
2199  background: #ef4444;
2200  color: white;
2201  border-radius: 10px;
2202  padding: 2px 5px;
2203  font-size: 10px;
2204  font-weight: 600;
2205  min-width: 18px;
2206  height: 18px;
2207  display: flex;
2208  align-items: center;
2209  justify-content: center;
2210  line-height: 1;
2211}
2212
2213/* Show on mobile only */
2214@media (max-width: 640px) {
2215  .bottom-nav {
2216    display: block;
2217  }
2218  
2219  /* Add bottom padding to body to prevent content being hidden */
2220  body {
2221    padding-bottom: 60px;
2222  }
2223}
2224
2225/* Mobile User Sheet - Bottom Sheet for Profile Menu */
2226.mobile-user-sheet {
2227  position: fixed;
2228  bottom: 0;
2229  left: 0;
2230  right: 0;
2231  z-index: 10000;
2232  pointer-events: none;
2233}
2234
2235.mobile-user-sheet:not([hidden]) {
2236  pointer-events: auto;
2237}
2238
2239.mobile-user-sheet-backdrop {
2240  position: fixed;
2241  inset: 0;
2242  background: rgba(0, 0, 0, 0.5);
2243  opacity: 0;
2244  transition: opacity 0.2s ease-out;
2245}
2246
2247.mobile-user-sheet.active .mobile-user-sheet-backdrop {
2248  opacity: 1;
2249}
2250
2251.mobile-user-sheet-content {
2252  position: absolute;
2253  bottom: 0;
2254  left: 0;
2255  right: 0;
2256  background: white;
2257  border-radius: 16px 16px 0 0;
2258  max-height: 80vh;
2259  overflow-y: auto;
2260  transform: translateY(100%);
2261  transition: transform 0.3s ease-out;
2262}
2263
2264.mobile-user-sheet.active .mobile-user-sheet-content {
2265  transform: translateY(0);
2266}
2267
2268.mobile-user-sheet-handle {
2269  width: 40px;
2270  height: 4px;
2271  background: #d1d5db;
2272  border-radius: 2px;
2273  margin: 12px auto 16px;
2274}
2275
2276.mobile-user-sheet-header {
2277  padding: 0 20px 16px;
2278  border-bottom: 1px solid #e5e7eb;
2279}
2280
2281.mobile-user-info {
2282  display: flex;
2283  align-items: center;
2284  gap: 12px;
2285}
2286
2287.mobile-user-avatar {
2288  width: 48px;
2289  height: 48px;
2290  background: linear-gradient(135deg, #FFE7CC 0%, #FFD9A8 50%, #FFCC80 100%);
2291  border-radius: 50%;
2292  display: flex;
2293  align-items: center;
2294  justify-content: center;
2295  font-size: 24px;
2296  color: #D97809;
2297  font-weight: 600;
2298}
2299
2300.mobile-user-details {
2301  flex: 1;
2302}
2303
2304.mobile-user-name {
2305  font-weight: 600;
2306  font-size: 16px;
2307  color: #1f2937;
2308  margin-bottom: 4px;
2309}
2310
2311.mobile-user-email {
2312  font-size: 13px;
2313  color: #6b7280;
2314}
2315
2316.mobile-user-sheet-body {
2317  padding: 8px 0;
2318}
2319
2320.mobile-user-sheet-item {
2321  display: flex;
2322  align-items: center;
2323  gap: 12px;
2324  padding: 14px 20px;
2325  background: none;
2326  border: none;
2327  width: 100%;
2328  text-align: left;
2329  cursor: pointer;
2330  text-decoration: none;
2331  color: #374151;
2332  transition: background 0.2s;
2333}
2334
2335.mobile-user-sheet-item:hover {
2336  background: #f9fafb;
2337}
2338
2339.mobile-user-sheet-icon {
2340  font-size: 20px;
2341  width: 24px;
2342  text-align: center;
2343}
2344
2345.mobile-user-sheet-label {
2346  font-size: 15px;
2347  font-weight: 500;
2348}
2349
2350.mobile-user-sheet-divider {
2351  height: 1px;
2352  background: #e5e7eb;
2353  margin: 8px 0;
2354}
2355
2356@keyframes fadeIn {
2357  from {
2358    opacity: 0;
2359  }
2360  to {
2361    opacity: 1;
2362  }
2363}
2364
2365@keyframes slideUp {
2366  from {
2367    transform: translateY(100%);
2368  }
2369  to {
2370    transform: translateY(0);
2371  }
2372}
2373
2374/* Ensure chat widget works well with bottom nav */
2375@media (max-width: 640px) {
2376  .chat-widget {
2377    bottom: 0 !important; /* Chat opens from bottom */
2378  }
2379  
2380  .chat-popup {
2381    /* Chat popup should be above bottom nav */
2382    z-index: 9999;
2383  }
2384}
2385
2386</style>
2387  <style data-inlined="search-suggest.css">
2388.search { 
2389  position: relative; 
2390}
2391
2392/* AI Bot Toggle Button */
2393.ai-bot-toggle {
2394  display: flex;
2395  align-items: center;
2396  justify-content: center;
2397  background: transparent;
2398  border: none;
2399  padding: 8px;
2400  cursor: pointer;
2401  border-radius: 8px;
2402  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
2403  margin-left: 4px;
2404  flex-shrink: 0;
2405  display: none;
2406}
2407
2408.ai-bot-toggle .bot-icon {
2409  color: #9ca3af;
2410  transition: all 0.3s ease;
2411}
2412
2413.ai-bot-toggle:hover .bot-icon {
2414  color: #6b7280;
2415  transform: scale(1.1);
2416}
2417
2418.ai-bot-toggle.active .bot-icon {
2419  color: #fb923c;
2420  animation: botPulse 2s ease-in-out infinite;
2421}
2422
2423.ai-bot-toggle.active {
2424  background: rgba(251, 146, 60, 0.1);
2425}
2426
2427.ai-bot-toggle.active:hover .bot-icon {
2428  color: #f97316;
2429}
2430
2431@keyframes botPulse {
2432  0%, 100% { transform: scale(1); }
2433  50% { transform: scale(1.12); }
2434}
2435
2436/* Search Suggest Box */
2437.search-suggest {
2438  position: absolute;
2439  top: 100%; left: 0; right: 0;
2440  background: #fff;
2441  border: 1px solid #e1e4e8;
2442  border-top: none;
2443  box-shadow: 0 4px 12px rgba(0,0,0,0.08);
2444  max-height: 400px;
2445  overflow-y: auto;
2446  z-index: 100;
2447  padding: 0;
2448  font-size: 14px;
2449  border-radius: 0 0 8px 8px;
2450}
2451
2452.suggest-item {
2453  display: flex;
2454  align-items: center;
2455  gap: 8px;
2456  padding: 10px 16px;
2457  cursor: pointer;
2458  transition: all 0.2s ease;
2459  border-bottom: 1px solid #f1f5f9;
2460}
2461
2462.suggest-item:last-child {
2463  border-bottom: none;
2464}
2465
2466.suggest-item:hover,
2467.suggest-item.active {
2468  background: linear-gradient(90deg, #f8fafc 0%, #f1f5f9 100%);
2469  padding-left: 20px;
2470}
2471
2472.suggest-thumb {
2473  width: 42px;
2474  height: 42px;
2475  object-fit: cover;
2476  border: 1px solid #ddd;
2477  border-radius: 4px;
2478  background: #fafafa;
2479}
2480
2481.suggest-meta { flex: 1; min-width: 0; }
2482
2483.suggest-name {
2484  font-weight: 500;
2485  color: #222;
2486  line-height: 1.2;
2487  white-space: nowrap;
2488  overflow: hidden;
2489  text-overflow: ellipsis;
2490}
2491
2492.suggest-price {
2493  color: #d32f2f;
2494  font-size: 12px;
2495  margin-top: 2px;
2496}
2497
2498/* AI Insights */
2499.suggest-ai-insight {
2500  display: flex;
2501  align-items: center;
2502  gap: 8px;
2503  padding: 8px 12px;
2504  background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
2505  border-bottom: 1px solid #bae6fd;
2506  color: #0369a1;
2507  font-size: 12px;
2508  font-weight: 500;
2509  line-height: 1.4;
2510}
2511
2512.suggest-ai-insight svg {
2513  flex-shrink: 0;
2514  opacity: 0.8;
2515}
2516
2517.suggest-ai-insight span {
2518  flex: 1;
2519}
2520
2521/* AI Progress States */
2522.ai-progress-container {
2523  padding: 20px 16px;
2524  background: linear-gradient(135deg, #fafbff 0%, #f5f7ff 100%);
2525}
2526
2527.ai-progress-header {
2528  display: flex;
2529  align-items: center;
2530  gap: 10px;
2531  margin-bottom: 16px;
2532}
2533
2534.ai-progress-icon {
2535  width: 36px;
2536  height: 36px;
2537  display: flex;
2538  align-items: center;
2539  justify-content: center;
2540  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
2541  border-radius: 50%;
2542  color: #fff;
2543  font-size: 18px;
2544  animation: aiIconSpin 3s linear infinite;
2545}
2546
2547@keyframes aiIconSpin {
2548  0% { transform: rotate(0deg); }
2549  100% { transform: rotate(360deg); }
2550}
2551
2552.ai-progress-text {
2553  flex: 1;
2554}
2555
2556.ai-progress-title {
2557  font-weight: 600;
2558  color: #1e293b;
2559  font-size: 14px;
2560  margin-bottom: 2px;
2561}
2562
2563.ai-progress-subtitle {
2564  font-size: 12px;
2565  color: #64748b;
2566}
2567
2568/* Progress Steps */
2569.ai-progress-steps {
2570  display: flex;
2571  flex-direction: column;
2572  gap: 10px;
2573  margin-top: 16px;
2574}
2575
2576.ai-progress-step {
2577  display: flex;
2578  align-items: center;
2579  gap: 10px;
2580  padding: 8px 12px;
2581  background: #fff;
2582  border-radius: 8px;
2583  border: 1px solid #e2e8f0;
2584  transition: all 0.3s ease;
2585}
2586
2587.ai-progress-step.active {
2588  border-color: #667eea;
2589  background: #f8f9ff;
2590  box-shadow: 0 2px 8px rgba(102, 126, 234, 0.1);
2591}
2592
2593.ai-progress-step.completed {
2594  border-color: #10b981;
2595  background: #f0fdf4;
2596}
2597
2598.ai-step-icon {
2599  width: 20px;
2600  height: 20px;
2601  display: flex;
2602  align-items: center;
2603  justify-content: center;
2604  border-radius: 50%;
2605  font-size: 11px;
2606  flex-shrink: 0;
2607}
2608
2609.ai-progress-step.active .ai-step-icon {
2610  background: #667eea;
2611  color: #fff;
2612  animation: pulse 1.5s ease-in-out infinite;
2613}
2614
2615.ai-progress-step.completed .ai-step-icon {
2616  background: #10b981;
2617  color: #fff;
2618}
2619
2620.ai-progress-step.pending .ai-step-icon {
2621  background: #e2e8f0;
2622  color: #94a3b8;
2623}
2624
2625.ai-step-text {
2626  flex: 1;
2627  font-size: 13px;
2628  color: #475569;
2629  font-weight: 500;
2630}
2631
2632.ai-progress-step.completed .ai-step-text {
2633  color: #059669;
2634}
2635
2636/* AI Analysis Result Banner */
2637.ai-result-banner {
2638  padding: 12px 16px;
2639  background: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%);
2640  border-bottom: 1px solid #86efac;
2641  display: flex;
2642  align-items: center;
2643  gap: 10px;
2644}
2645
2646.ai-result-icon {
2647  font-size: 20px;
2648  animation: bounceIn 0.5s ease;
2649}
2650
2651@keyframes bounceIn {
2652  0% { transform: scale(0); opacity: 0; }
2653  50% { transform: scale(1.1); }
2654  100% { transform: scale(1); opacity: 1; }
2655}
2656
2657.ai-result-text {
2658  flex: 1;
2659}
2660
2661.ai-result-title {
2662  font-weight: 600;
2663  color: #065f46;
2664  font-size: 13px;
2665  margin-bottom: 2px;
2666}
2667
2668.ai-result-description {
2669  font-size: 12px;
2670  color: #047857;
2671  opacity: 0.9;
2672}
2673
2674/* Product Items Section Header */
2675.suggest-section-header {
2676  padding: 10px 16px;
2677  background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
2678  border-bottom: 1px solid #e2e8f0;
2679}
2680
2681.suggest-header-main {
2682  display: flex;
2683  align-items: center;
2684  gap: 6px;
2685  margin-bottom: 4px;
2686}
2687
2688.suggest-header-icon {
2689  font-size: 14px;
2690}
2691
2692.suggest-header-text {
2693  font-size: 12px;
2694  font-weight: 600;
2695  color: #64748b;
2696  text-transform: uppercase;
2697  letter-spacing: 0.5px;
2698}
2699
2700.suggest-header-hint {
2701  font-size: 11px;
2702  color: #94a3b8;
2703  font-weight: 400;
2704  text-transform: none;
2705  letter-spacing: 0;
2706  display: flex;
2707  align-items: center;
2708  gap: 4px;
2709}
2710
2711.suggest-header-hint kbd {
2712  display: inline-block;
2713  padding: 2px 6px;
2714  font-size: 10px;
2715  font-weight: 600;
2716  line-height: 1;
2717  color: #475569;
2718  background: #fff;
2719  border: 1px solid #cbd5e1;
2720  border-radius: 4px;
2721  box-shadow: 0 1px 2px rgba(0,0,0,0.05);
2722  font-family: 'Segoe UI', monospace;
2723}
2724
2725/* Footer Hint */
2726.suggest-footer-hint {
2727  display: flex;
2728  align-items: center;
2729  justify-content: center;
2730  padding: 12px 16px;
2731  background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
2732  border-top: 1px solid #fbbf24;
2733  color: #92400e;
2734  font-size: 12px;
2735  font-weight: 500;
2736  text-align: center;
2737  cursor: default;
2738}
2739
2740.suggest-footer-hint svg {
2741  color: #f59e0b;
2742  flex-shrink: 0;
2743}
2744
2745.suggest-footer-hint kbd {
2746  display: inline-block;
2747  padding: 3px 7px;
2748  margin: 0 3px;
2749  font-size: 11px;
2750  font-weight: 600;
2751  line-height: 1;
2752  color: #78350f;
2753  background: #fff;
2754  border: 1px solid #f59e0b;
2755  border-radius: 4px;
2756  box-shadow: 0 1px 3px rgba(245, 158, 11, 0.2);
2757  font-family: 'Segoe UI', monospace;
2758}
2759
2760.suggest-footer-link {
2761  color: #ea580c;
2762  font-weight: 600;
2763  text-decoration: underline;
2764  text-underline-offset: 2px;
2765  text-decoration-thickness: 1px;
2766  cursor: pointer;
2767  transition: all 0.2s ease;
2768}
2769
2770.suggest-footer-link:hover {
2771  color: #c2410c;
2772  text-decoration-thickness: 2px;
2773  text-shadow: 0 0 8px rgba(234, 88, 12, 0.3);
2774}
2775
2776/* Loading state */
2777.suggest-loading {
2778  display: flex;
2779  align-items: center;
2780  justify-content: center;
2781  gap: 8px;
2782  padding: 16px 12px;
2783  color: #64748b;
2784  font-size: 13px;
2785}
2786
2787@keyframes pulse {
2788  0%, 100% { opacity: 1; }
2789  50% { opacity: 0.5; }
2790}
2791
2792.suggest-loading {
2793  animation: pulse 1.5s ease-in-out infinite;
2794}
2795
2796/* Responsive Mobile Styles */
2797@media (max-width: 768px) {
2798  .ai-bot-toggle {
2799    padding: 6px;
2800    margin-left: 0;
2801  }
2802  
2803  .ai-bot-toggle .bot-icon {
2804    width: 20px;
2805    height: 20px;
2806  }
2807  
2808  .search-icon-btn {
2809    margin-right: 4px;
2810  }
2811  
2812  .search-icon-btn svg {
2813    width: 18px;
2814    height: 18px;
2815  }
2816  
2817  .search-suggest {
2818    max-height: 350px;
2819  }
2820  
2821  /* Mobile hint adjustments */
2822  .suggest-header-hint {
2823    font-size: 10px;
2824  }
2825  
2826  .suggest-header-hint kbd {
2827    padding: 1px 4px;
2828    font-size: 9px;
2829  }
2830  
2831  .suggest-footer-hint {
2832    padding: 10px 12px;
2833    font-size: 11px;
2834  }
2835  
2836  .suggest-footer-hint svg {
2837    width: 14px;
2838    height: 14px;
2839  }
2840  
2841  .suggest-footer-hint kbd {
2842    padding: 2px 5px;
2843    font-size: 10px;
2844  }
2845  
2846  .ai-progress-container {
2847    padding: 16px 12px;
2848  }
2849  
2850  .ai-progress-icon {
2851    width: 32px;
2852    height: 32px;
2853    font-size: 16px;
2854  }
2855  
2856  .ai-progress-title {
2857    font-size: 13px;
2858  }
2859  
2860  .ai-progress-subtitle {
2861    font-size: 11px;
2862  }
2863  
2864  .ai-progress-step {
2865    padding: 6px 10px;
2866  }
2867  
2868  .ai-step-text {
2869    font-size: 12px;
2870  }
2871  
2872  .suggest-item {
2873    padding: 8px 12px;
2874  }
2875  
2876  .suggest-thumb {
2877    width: 36px;
2878    height: 36px;
2879  }
2880  
2881  .suggest-name {
2882    font-size: 13px;
2883  }
2884  
2885  .suggest-price {
2886    font-size: 11px;
2887  }
2888}
2889
2890@media (max-width: 480px) {
2891  .ai-bot-toggle {
2892    padding: 4px;
2893  }
2894  
2895  .ai-bot-toggle .bot-icon {
2896    width: 18px;
2897    height: 18px;
2898  }
2899  
2900  .search-icon-btn svg {
2901    width: 16px;
2902    height: 16px;
2903  }
2904  
2905  /* Ultra-compact hints for small screens */
2906  .suggest-header-hint {
2907    display: none; /* Hide header hint on very small screens */
2908  }
2909  
2910  .suggest-footer-hint {
2911    padding: 8px 10px;
2912    font-size: 10px;
2913    flex-direction: column;
2914    gap: 4px;
2915  }
2916  
2917  .suggest-footer-hint svg {
2918    display: none; /* Hide icon to save space */
2919  }
2920  
2921  .suggest-footer-hint kbd {
2922    padding: 2px 4px;
2923    font-size: 9px;
2924  }
2925  
2926  .ai-progress-steps {
2927    gap: 8px;
2928  }
2929  
2930  .ai-step-text {
2931    font-size: 11px;
2932  }
2933}
2934
2935/* AI Search Prompt Box */
2936.ai-search-prompt {
2937  padding: 24px;
2938  background: linear-gradient(135deg, #fff5f0 0%, #ffffff 100%);
2939}
2940
2941.ai-prompt-header {
2942  display: flex;
2943  align-items: center;
2944  gap: 12px;
2945  margin-bottom: 16px;
2946}
2947
2948.ai-prompt-icon {
2949  width: 48px;
2950  height: 48px;
2951  background: linear-gradient(135deg, #fb923c 0%, #f97316 100%);
2952  border-radius: 12px;
2953  display: flex;
2954  align-items: center;
2955  justify-content: center;
2956  font-size: 24px;
2957  box-shadow: 0 4px 12px rgba(251, 146, 60, 0.3);
2958}
2959
2960.ai-prompt-text {
2961  flex: 1;
2962}
2963
2964.ai-prompt-title {
2965  font-size: 16px;
2966  font-weight: 700;
2967  color: #1f2937;
2968  margin-bottom: 4px;
2969}
2970
2971.ai-prompt-subtitle {
2972  font-size: 13px;
2973  color: #6b7280;
2974}
2975
2976.ai-prompt-query {
2977  background: white;
2978  border: 2px solid #fb923c;
2979  border-radius: 8px;
2980  padding: 12px 16px;
2981  margin-bottom: 16px;
2982}
2983
2984.ai-query-label {
2985  font-size: 11px;
2986  font-weight: 600;
2987  color: #fb923c;
2988  text-transform: uppercase;
2989  letter-spacing: 0.5px;
2990  margin-bottom: 4px;
2991}
2992
2993.ai-query-text {
2994  font-size: 15px;
2995  font-weight: 600;
2996  color: #1f2937;
2997}
2998
2999.ai-search-trigger-btn {
3000  width: 100%;
3001  padding: 14px 20px;
3002  background: linear-gradient(135deg, #fb923c 0%, #f97316 100%);
3003  color: white;
3004  border: none;
3005  border-radius: 10px;
3006  font-size: 15px;
3007  font-weight: 700;
3008  cursor: pointer;
3009  display: flex;
3010  align-items: center;
3011  justify-content: center;
3012  gap: 8px;
3013  transition: all 0.3s ease;
3014  box-shadow: 0 4px 12px rgba(251, 146, 60, 0.4);
3015}
3016
3017.ai-search-trigger-btn:hover {
3018  transform: translateY(-2px);
3019  box-shadow: 0 6px 20px rgba(251, 146, 60, 0.5);
3020}
3021
3022.ai-search-trigger-btn:active {
3023  transform: translateY(0);
3024}
3025
3026.ai-btn-icon {
3027  font-size: 18px;
3028  animation: sparkle 2s ease-in-out infinite;
3029}
3030
3031@keyframes sparkle {
3032  0%, 100% { opacity: 1; transform: scale(1); }
3033  50% { opacity: 0.7; transform: scale(1.2); }
3034}
3035
3036.ai-btn-text {
3037  font-size: 15px;
3038}
3039
3040.ai-prompt-footer {
3041  margin-top: 12px;
3042  text-align: center;
3043  font-size: 12px;
3044  color: #9ca3af;
3045  font-style: italic;
3046}
3047
3048/* ==================== TRENDING SEARCH STYLES ==================== */
3049
3050.search-suggest.trending-mode {
3051  border-top: 3px solid #fb923c;
3052  max-width: 900px;
3053  min-width: 600px;
3054}
3055
3056/* Two-column container */
3057.trending-container {
3058  display: flex;
3059  gap: 0;
3060  padding: 0 !important;
3061}
3062
3063.trending-column {
3064  flex: 1;
3065  min-width: 0;
3066  padding: 0 !important;
3067}
3068
3069.trending-column.trending-searches {
3070  border-right: 2px solid #e2e8f0; padding: 0 !important;
3071}
3072
3073/* Mobile: stack columns vertically */
3074@media (max-width: 768px) {
3075  .search-suggest.trending-mode {
3076    min-width: auto;
3077    max-width: 100%;
3078  }
3079  
3080  .trending-container {
3081    flex-direction: column;
3082  }
3083  
3084  .trending-column.trending-searches {
3085    border-right: none;
3086    border-bottom: 2px solid #e2e8f0;
3087  }
3088}
3089
3090.trending-header {
3091  display: flex;
3092  align-items: center;
3093  gap: 8px;
3094  padding: 14px 16px;
3095  border-bottom: 1px solid #e2e8f0;
3096  background: linear-gradient(135deg, #fb923c 0%, #f97316 100%);
3097  color: white;
3098  position: sticky;
3099  top: 0;
3100  z-index: 10;
3101}
3102
3103.trending-icon {
3104  font-size: 20px;
3105  animation: trendingPulse 2s infinite;
3106}
3107
3108@keyframes trendingPulse {
3109  0%, 100% { 
3110    transform: scale(1); 
3111    filter: drop-shadow(0 0 0 rgba(251, 146, 60, 0));
3112  }
3113  50% { 
3114    transform: scale(1.15); 
3115    filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.8));
3116  }
3117}
3118
3119.trending-title {
3120  font-weight: 700;
3121  font-size: 14px;
3122  letter-spacing: 0.3px;
3123}
3124
3125.trending-subtitle {
3126  font-size: 11px;
3127  opacity: 0.9;
3128  margin-left: auto;
3129  font-style: italic;
3130}
3131
3132/* Trending searches item */
3133.trending-item {
3134  display: flex;
3135  align-items: center;
3136  gap: 12px;
3137  padding: 12px 16px;
3138  cursor: pointer;
3139  transition: all 0.2s ease;
3140  border-bottom: 1px solid #f1f5f9;
3141  position: relative;
3142}
3143
3144.trending-item:hover {
3145  background: linear-gradient(90deg, #fff7ed 0%, #ffedd5 100%);
3146  padding-left: 20px;
3147}
3148
3149.trending-item:hover::before {
3150  content: '';
3151  position: absolute;
3152  left: 0;
3153  top: 0;
3154  bottom: 0;
3155  width: 4px;
3156  background: linear-gradient(180deg, #fb923c 0%, #f97316 100%);
3157}
3158
3159.trending-item:last-child {
3160  border-bottom: none;
3161}
3162
3163.trending-rank {
3164  font-size: 20px;
3165  flex-shrink: 0;
3166  width: 32px;
3167  text-align: center;
3168}
3169
3170.trending-query {
3171  flex: 1;
3172  font-size: 14px;
3173  color: #1f2937;
3174  font-weight: 500;
3175  line-height: 1.4;
3176}
3177
3178.trending-category {
3179  font-size: 11px;
3180  color: #f97316;
3181  background: #fff7ed;
3182  padding: 3px 10px;
3183  border-radius: 12px;
3184  white-space: nowrap;
3185  font-weight: 500;
3186  border: 1px solid #fed7aa;
3187}
3188
3189/* Trending products item */
3190.trending-product-item {
3191  display: flex;
3192  align-items: center;
3193  gap: 12px;
3194  padding: 12px 16px;
3195  cursor: pointer;
3196  transition: all 0.2s ease;
3197  border-bottom: 1px solid #f1f5f9;
3198  position: relative;
3199}
3200
3201.trending-product-item:hover {
3202  background: linear-gradient(90deg, #fef3c7 0%, #fde68a 100%);
3203  padding-left: 20px;
3204}
3205
3206.trending-product-item:hover::before {
3207  content: '';
3208  position: absolute;
3209  left: 0;
3210  top: 0;
3211  bottom: 0;
3212  width: 4px;
3213  background: linear-gradient(180deg, #fbbf24 0%, #f59e0b 100%);
3214}
3215
3216.trending-product-item:last-child {
3217  border-bottom: none;
3218}
3219
3220.trending-product-thumb {
3221  width: 48px;
3222  height: 48px;
3223  object-fit: cover;
3224  border-radius: 8px;
3225  border: 1px solid #e5e7eb;
3226  flex-shrink: 0;
3227  background: white;
3228}
3229
3230.trending-product-info {
3231  flex: 1;
3232  min-width: 0;
3233}
3234
3235.trending-product-name {
3236  font-size: 13px;
3237  font-weight: 400;
3238  color: #1f2937;
3239  line-height: 1.4;
3240  margin-bottom: 0;
3241  padding: 0 !important;
3242  overflow: hidden;
3243  text-overflow: ellipsis;
3244  display: -webkit-box;
3245  -webkit-line-clamp: 2;
3246  -webkit-box-orient: vertical;
3247}
3248
3249.trending-product-price {
3250  font-size: 12px;
3251  font-weight: 700;
3252  color: #f59e0b;
3253  padding: 4px 0 !important;
3254}
3255.trending-product { padding: 0;}
3256/* ==================== END TRENDING STYLES ==================== */
3257
3258</style>
3259  <style data-inlined="product-detail.css">
3260/* === PRODUCT DETAIL v3 (Tokopedia-like) — Full stylesheet === */
3261
3262/* Root / Container */
3263.product-single {
3264  --pd-gap: 2rem;
3265  --pd-border: #e2e8f0;
3266  --pd-bg: #ffffff;
3267  --pd-muted: #64748b;
3268  --pd-dark: #0f172a;
3269  --pd-accent: #0d6efd; /* tema utama situs */
3270  --pd-radius: 12px;
3271  --pd-soft: #f8fafc;
3272  --pd-soft-border: #e5e7eb;
3273  --pd-danger: #dcce77;
3274  --pd-success-bg: #dcfce7;
3275  --pd-success-text: #166534;
3276  --pd-danger-bg: #fee2e2;
3277  --pd-danger-text: #dcce77;
3278  --pd-shadow: 0 6px 20px -10px rgba(0,0,0,.12);
3279  --pd-transition: .25s cubic-bezier(.4,.0,.2,1);
3280  max-width:1280px;
3281  margin-inline:auto;
3282}
3283
3284/* === Sticky Product Header Bar (muncul saat scroll) === */
3285.pd-sticky-header {
3286  position: fixed;
3287  top: 0;
3288  left: 0;
3289  right: 0;
3290  z-index: 998;
3291  background: rgba(255, 255, 255, 0.98);
3292  backdrop-filter: blur(12px);
3293  -webkit-backdrop-filter: blur(12px);
3294  border-bottom: 1px solid rgba(226, 232, 240, 0.8);
3295  box-shadow: 0 4px 16px -4px rgba(0, 0, 0, 0.08);
3296  transform: translateY(-100%);
3297  opacity: 0;
3298  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), 
3299              opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);
3300  pointer-events: none;
3301}
3302
3303.pd-sticky-header.visible {
3304  transform: translateY(0);
3305  opacity: 1;
3306  pointer-events: auto;
3307}
3308
3309.pd-sticky-header-container {
3310  max-width: 1280px;
3311  margin: 0 auto;
3312  padding: 12px 20px;
3313  display: flex;
3314  align-items: center;
3315  gap: 16px;
3316}
3317
3318.pd-sticky-thumb {
3319  width: 48px;
3320  height: 48px;
3321  flex-shrink: 0;
3322  border-radius: 8px;
3323  border: 1px solid #e2e8f0;
3324  overflow: hidden;
3325  background: #f8fafc;
3326}
3327
3328.pd-sticky-thumb img {
3329  width: 100%;
3330  height: 100%;
3331  object-fit: cover;
3332  display: block;
3333}
3334
3335.pd-sticky-info {
3336  flex: 1;
3337  min-width: 0;
3338  display: flex;
3339  flex-direction: column;
3340  gap: 4px;
3341}
3342
3343.pd-sticky-title {
3344  font-size: 0.9rem;
3345  font-weight: 600;
3346  color: #0f172a;
3347  line-height: 1.3;
3348  white-space: nowrap;
3349  overflow: hidden;
3350  text-overflow: ellipsis;
3351  margin: 0;
3352}
3353
3354.pd-sticky-price-row {
3355  display: flex;
3356  align-items: center;
3357  gap: 8px;
3358  flex-wrap: wrap;
3359}
3360
3361.pd-sticky-price {
3362  font-size: 1.1rem;
3363  font-weight: 700;
3364  color: #0d6efd;
3365  line-height: 1;
3366}
3367
3368.pd-sticky-price-original {
3369  font-size: 0.75rem;
3370  color: #94a3b8;
3371  text-decoration: line-through;
3372  line-height: 1;
3373}
3374
3375.pd-sticky-discount {
3376  background: #dcce77;
3377  color: #fff;
3378  font-size: 0.65rem;
3379  font-weight: 600;
3380  padding: 2px 6px;
3381  border-radius: 4px;
3382  letter-spacing: 0.3px;
3383  line-height: 1;
3384}
3385
3386.pd-sticky-actions {
3387  display: flex;
3388  gap: 8px;
3389  flex-shrink: 0;
3390  align-items: center;
3391}
3392
3393.pd-sticky-share-btn {
3394  padding: 8px 16px;
3395  border-radius: 8px;
3396  border: 1px solid #e2e8f0;
3397  background: #fff;
3398  color: #64748b;
3399  cursor: pointer;
3400  transition: all 0.2s ease;
3401  display: inline-flex;
3402  align-items: center;
3403  justify-content: center;
3404  flex-shrink: 0;
3405}
3406
3407.pd-sticky-share-btn:hover {
3408  background: #f8fafc;
3409  border-color: #cbd5e1;
3410  color: #0f172a;
3411  transform: translateY(-1px);
3412}
3413
3414.pd-sticky-share-btn:active {
3415  transform: translateY(0);
3416}
3417
3418.pd-sticky-share-btn svg {
3419  display: block;
3420  width: 16px;
3421  height: 16px;
3422}
3423
3424.pd-sticky-btn-chat svg {
3425  width: 16px !important;
3426  height: 16px !important;
3427  max-width: none !important;
3428  margin-right: 4px;
3429  flex-shrink: 0;
3430}
3431
3432.pd-sticky-btn {
3433  padding: 8px 16px;
3434  border-radius: 8px;
3435  font-size: 0.85rem;
3436  font-weight: 600;
3437  cursor: pointer;
3438  transition: all 0.2s ease;
3439  border: 0;
3440  display: inline-flex;
3441  align-items: center;
3442  justify-content: center;
3443  white-space: nowrap;
3444}
3445
3446.pd-sticky-btn-cart {
3447  background: #fff;
3448  color: #0d6efd;
3449  border: 1px solid #0d6efd;
3450}
3451
3452.pd-sticky-btn-cart:hover {
3453  background: rgba(13, 110, 253, 0.06);
3454}
3455
3456.pd-sticky-btn-chat {
3457  background: #fff;
3458  color: #64748b;
3459  border: 1px solid #cbd5e1;
3460  flex: 0.8;
3461}
3462
3463.pd-sticky-btn-chat:hover {
3464  background: #f8fafc;
3465  border-color: #94a3b8;
3466  color: #475569;
3467}
3468
3469.pd-sticky-btn-buy {
3470  background: #0d6efd;
3471  color: #fff;
3472  border: 1px solid #0d6efd;
3473  box-shadow: 0 2px 8px -2px rgba(13, 110, 253, 0.4);
3474}
3475
3476.pd-sticky-btn-buy:hover {
3477  filter: brightness(0.95);
3478}
3479
3480.pd-sticky-btn:disabled {
3481  opacity: 0.5;
3482  cursor: not-allowed;
3483}
3484
3485/* Hide on mobile (use existing FAB instead) */
3486@media (max-width: 880px) {
3487  .pd-sticky-header {
3488    display: none;
3489  }
3490}
3491
3492/* === PRINT STYLES (for PDF Export) === */
3493@media print {
3494  /* Reset & Base */
3495  * {
3496    -webkit-print-color-adjust: exact !important;
3497    print-color-adjust: exact !important;
3498  }
3499  
3500  body {
3501    background: white !important;
3502    margin: 0;
3503    padding: 0;
3504  }
3505  
3506  /* Hide all navigation, buttons, and interactive elements */
3507  nav, header, footer,
3508  nav *, header *, footer *,
3509  .navbar, .navbar *,
3510  .nav-item, .nav-link,
3511  .pd-sticky-header,
3512  .pd-sticky-header.visible,
3513  .pd-breadcrumb,
3514  .pd-sidebar,
3515  .pd-mobile-fab,
3516  .pd-share-btn,
3517  .pd-sticky-share-btn,
3518  #pd-share-btn,
3519  .share-button,
3520  [data-share-btn],
3521  button,
3522  .btn-add-cart,
3523  .btn-buy,
3524  .pd-actions,
3525  .pd-compare-section,
3526  .pd-compare-btn,
3527  .pd-compare-info,
3528  #pd-compare-btn,
3529  .compare-note,
3530  .compare-container,
3531  .compare-dropdown,
3532  .compare-panel,
3533  .compare-wrapper,
3534  [data-compare],
3535  [id*="compare"],
3536  [class*="compare"],
3537  .pd-thumb-gallery,
3538  .pd-installment-card,
3539  .pd-installment-card2,
3540  .inst-alt-banner--compact,
3541  .qoala-banner-card,
3542  .tradein-compact-card,
3543  .pwp-deals-section,
3544  #pwp-deals-section,
3545  .pwp-trigger-badge,
3546  .pd-related-full,
3547  #pd-similar,
3548  .pd-similar-strip,
3549  .pd-scroll-top,
3550  .stock-info-btn,
3551  .stock-popover,
3552  .pd-countdown-modern,
3553  .pd-share-modal-overlay,
3554  [id*="promo-"],
3555  .bottom-nav,
3556  .pd-buy-form,
3557  .pd-buy-card,
3558  .pd-extra-meta,
3559  .pd-reviews-section,
3560  #reviews,
3561  hr,
3562  .divider,
3563  .separator {
3564    display: none !important;
3565    visibility: hidden !important;
3566    opacity: 0 !important;
3567    height: 0 !important;
3568    max-height: 0 !important;
3569    overflow: hidden !important;
3570    margin: 0 !important;
3571    padding: 0 !important;
3572    border: none !important;
3573  }
3574  
3575  /* Extra aggressive reset for any remaining borders */
3576  .pd-info-card *,
3577  .pd-meta-inline *,
3578  .pd-variants * {
3579    border: none !important;
3580    border-top: none !important;
3581    border-bottom: none !important;
3582  }
3583  
3584  /* Layout restructure */
3585  .product-single {
3586    max-width: 100% !important;
3587    margin: 0 !important;
3588    padding: 0 !important;
3589    box-shadow: none !important;
3590  }
3591  
3592  /* Reset all shadows and borders for print */
3593  * {
3594    box-shadow: none !important;
3595  }
3596  
3597  .pd-top-layout {
3598    display: grid !important;
3599    grid-template-columns: 280px 1fr !important;
3600    gap: 24px !important;
3601    margin: 0 0 24px 0 !important;
3602    padding: 0 !important;
3603    page-break-inside: avoid;
3604    align-items: start !important;
3605  }
3606  
3607  /* Print header with logo - centered and larger */
3608  .product-single::before {
3609    content: "" !important;
3610    display: block !important;
3611    width: 150px !important;
3612    height: 50px !important;
3613    background: url('/assets/img/logo.png') no-repeat center !important;
3614    background-size: contain !important;
3615    margin: 0 auto 20px !important;
3616    visibility: visible !important;
3617    opacity: 1 !important;
3618  }
3619  
3620  /* Media card - left column */
3621  .pd-media-card {
3622    border: none !important;
3623    padding: 0 !important;
3624    margin: 0 !important;
3625    text-align: center;
3626    page-break-inside: avoid;
3627  }
3628  
3629  /* Hide desktop image, use mobile slider for both desktop and mobile */
3630  .pd-main-image {
3631    display: none !important;
3632  }
3633  
3634  /* Mobile slider - show first image only in print */
3635  .pd-mobile-slider {
3636    display: block !important;
3637    overflow: visible !important;
3638  }
3639  
3640  .pd-mslide {
3641    display: none !important;
3642  }
3643  
3644  .pd-mslide:first-child {
3645    display: block !important;
3646    border: 1px solid #e2e8f0 !important;
3647    max-width: 280px !important;
3648    margin: 0 auto !important;
3649    border-radius: 8px !important;
3650  }
3651  
3652  .pd-mslide:first-child img {
3653    max-height: 280px !important;
3654    width: 100% !important;
3655    height: auto !important;
3656    object-fit: contain !important;
3657  }
3658  
3659  /* Info card - right column */
3660  .pd-info-card {
3661    border: none !important;
3662    border-top: none !important;
3663    border-bottom: none !important;
3664    padding: 0 !important;
3665    margin: 0 !important;
3666    background: white !important;
3667    box-shadow: none !important;
3668  }
3669  
3670  /* Title */
3671  .pd-title-row {
3672    display: block !important;
3673    margin-bottom: 10px !important;
3674    border: none !important;
3675    border-top: none !important;
3676    border-bottom: none !important;
3677    padding: 0 !important;
3678    box-shadow: none !important;
3679    text-decoration: none !important;
3680  }
3681  
3682  /* Hide share button in print */
3683  .pd-title-row .pd-share-btn,
3684  .pd-title-row #pd-share-btn {
3685    display: none !important;
3686  }
3687  
3688  .pd-title {
3689    font-size: 13pt !important;
3690    color: #000 !important;
3691    margin: 0 0 10px 0 !important;
3692    page-break-after: avoid;
3693    font-weight: 700 !important;
3694    line-height: 1.3 !important;
3695    border: none !important;
3696    border-top: none !important;
3697    border-bottom: none !important;
3698    padding-bottom: 0 !important;
3699    box-shadow: none !important;
3700    text-decoration: none !important;
3701  }
3702  
3703  /* Price and QR container - side by side */
3704  .pd-price-qr-container {
3705    display: flex !important;
3706    gap: 16px !important;
3707    align-items: flex-start !important;
3708    margin-bottom: 14px !important;
3709    box-shadow: none !important;
3710    border: none !important;
3711    text-decoration: none !important;
3712  }
3713  
3714  /* Price block - left side */
3715  .pd-price-block {
3716    flex: 1 !important;
3717    margin: 0 !important;
3718    padding-bottom: 0 !important;
3719    border-bottom: none !important;
3720    box-shadow: none !important;
3721  }
3722  
3723  .pd-price-row {
3724    margin-bottom: 8px !important;
3725    border: none !important;
3726    border-top: none !important;
3727    border-bottom: none !important;
3728    padding: 0 !important;
3729  }
3730  
3731  /* Hide all empty or unnecessary elements in price row */
3732  .pd-price-row > span:empty,
3733  .pd-price-row > .sold-inline,
3734  #pd-sold-inline {
3735    display: none !important;
3736  }
3737  
3738  .pd-price-current {
3739    font-size: 18pt !important;
3740    color: #0d6efd !important;
3741  }
3742  
3743  /* Show price range for variant products in print */
3744  .pd-price-current[data-price-range]::after {
3745    content: attr(data-price-range) !important;
3746    display: block !important;
3747    font-size: 11pt !important;
3748    color: #64748b !important;
3749    margin-top: 4px !important;
3750    font-weight: normal !important;
3751  }
3752  
3753  /* Hide 'Range:' prefix text */
3754  .pd-price-current[data-price-range]::after {
3755    content: attr(data-price-range) !important;
3756  }
3757  
3758  .pd-price-original {
3759    font-size: 12pt !important;
3760    color: #64748b !important;
3761  }
3762  
3763  .pd-discount-badge {
3764    font-size: 10pt !important;
3765    padding: 2px 6px !important;
3766  }
3767  
3768  /* QR Code section - right side beside price */
3769  .pd-qr-print {
3770    display: block !important;
3771    flex: 0 0 100px !important;
3772    margin: 0 !important;
3773    padding: 8px !important;
3774    background: #f8fafc !important;
3775    border-radius: 8px !important;
3776    text-align: center !important;
3777    page-break-inside: avoid !important;
3778    border: 1px solid #cbd5e1 !important;
3779  }
3780  
3781  .pd-qr-label {
3782    font-size: 6.5pt !important;
3783    color: #64748b !important;
3784    margin-bottom: 5px !important;
3785    line-height: 1.2 !important;
3786    font-weight: 600 !important;
3787  }
3788  
3789  .pd-qr-image {
3790    width: 80px !important;
3791    height: 80px !important;
3792    display: block !important;
3793    margin: 0 auto !important;
3794  }
3795  
3796  /* Meta info */
3797  .pd-meta-inline {
3798    display: flex !important;
3799    gap: 12px !important;
3800    margin-bottom: 0 !important;
3801    padding-bottom: 14px !important;
3802    font-size: 10pt !important;
3803    border: none !important;
3804    border-top: none !important;
3805    border-bottom: none !important;
3806    padding-top: 0 !important;
3807    box-shadow: none !important;
3808    text-decoration: none !important;
3809    background: transparent !important;
3810  }
3811  
3812  /* Hide stock badge in print */
3813  .stock-badge,
3814  .stock-info-btn {
3815    display: none !important;
3816  }
3817  
3818  .sku {
3819    color: #64748b !important;
3820  }
3821  
3822  /* Hide sold badge if empty/zero */
3823  .sold-inline {
3824    display: none !important;
3825  }
3826  
3827  /* Hide variant summary section in print */
3828  .pd-variant-combined,
3829  #pd-variant-combined,
3830  .pd-variant-summary,
3831  [data-role="sold-chip"] {
3832    display: none !important;
3833  }
3834  
3835  /* Hide compare section completely in print */
3836  .pd-compare-section,
3837  .pd-compare-btn,
3838  #pd-compare-btn,
3839  .pd-compare-info,
3840  .compare-note,
3841  .compare-text,
3842  .compare-remove {
3843    display: none !important;
3844    visibility: hidden !important;
3845    height: 0 !important;
3846    margin: 0 !important;
3847    padding: 0 !important;
3848    overflow: hidden !important;
3849  }
3850  
3851  /* Variants - show all options with selected highlighted */
3852  .pd-variants {
3853    margin: 0 !important;
3854    padding: 0 !important;
3855    page-break-inside: avoid !important;
3856    border: none !important;
3857    border-top: none !important;
3858    border-bottom: none !important;
3859    box-shadow: none !important;
3860    background: transparent !important;
3861  }
3862  
3863  /* Add 'Variant:' label before variant groups */
3864  .pd-variants::before {
3865    content: "Variant:" !important;
3866    display: block !important;
3867    font-size: 11pt !important;
3868    font-weight: 700 !important;
3869    color: #0f172a !important;
3870    margin-bottom: 10px !important;
3871    border: none !important;
3872    padding: 0 !important;
3873    border-top: none !important;
3874    border-bottom: none !important;
3875  }
3876  
3877  .v-group {
3878    display: flex !important;
3879    align-items: center !important;
3880    gap: 8px !important;
3881    margin-bottom: 8px !important;
3882    line-height: 1 !important;
3883    page-break-inside: avoid !important;
3884    border: none !important;
3885    padding: 0 !important;
3886  }
3887  
3888  .v-group:last-child {
3889    margin-bottom: 0 !important;
3890  }
3891  
3892  .v-label {
3893    font-weight: 700 !important;
3894    margin: 0 !important;
3895    font-size: 9pt !important;
3896    color: #475569 !important;
3897    min-width: 60px !important;
3898    flex-shrink: 0 !important;
3899    border: none !important;
3900    padding: 0 !important;
3901  }
3902  
3903  .v-options {
3904    display: flex !important;
3905    gap: 6px !important;
3906    flex-wrap: wrap !important;
3907    border: none !important;
3908    padding: 0 !important;
3909    margin: 0 !important;
3910  }
3911  
3912  .v-chip {
3913    border: 1px solid #cbd5e1 !important;
3914    padding: 3px 10px !important;
3915    font-size: 9pt !important;
3916    background: #f8fafc !important;
3917    border-radius: 6px !important;
3918    display: inline-flex !important;
3919    align-items: center !important;
3920    page-break-inside: avoid !important;
3921  }
3922  
3923  /* Highlight selected variant with blue background */
3924  .v-chip.selected {
3925    background: #eff6ff !important;
3926    border-color: #2563eb !important;
3927    font-weight: 600 !important;
3928  }
3929  
3930  /* Hide radio inputs */
3931  .v-chip input[type="radio"] {
3932    display: none !important;
3933  }
3934  
3935  /* Hide "Habis" badge in print */
3936  .v-chip .chip-badge {
3937    display: none !important;
3938  }
3939  
3940  /* Short description - HIDDEN in print */
3941  .pd-short-desc {
3942    display: none !important;
3943  }
3944  
3945  /* QR Code section - right side beside price */
3946  .pd-qr-print {
3947    display: block !important;
3948    flex: 0 0 100px !important;
3949    margin: 0 !important;
3950    padding: 8px !important;
3951    background: #f8fafc !important;
3952    border-radius: 8px !important;
3953    text-align: center !important;
3954    page-break-inside: avoid !important;
3955    border: 1px solid #cbd5e1 !important;
3956  }
3957  
3958  .pd-qr-label {
3959    font-size: 6.5pt !important;
3960    color: #64748b !important;
3961    margin-bottom: 5px !important;
3962    line-height: 1.2 !important;
3963    font-weight: 600 !important;
3964  }
3965  
3966  .pd-qr-image {
3967    width: 80px !important;
3968    height: 80px !important;
3969    display: block !important;
3970    margin: 0 auto !important;
3971  }
3972  
3973  /* Full description - below 2 column layout */
3974  .pd-full-description {
3975    border: none !important;
3976    padding: 0 !important;
3977    margin: 24px 0 0 0 !important;
3978    page-break-before: avoid;
3979    grid-column: 1 / -1 !important;
3980  }
3981  
3982  .pd-full-desc-title {
3983    font-size: 13pt !important;
3984    font-weight: 700 !important;
3985    margin: 0 0 12px 0 !important;
3986    color: #000 !important;
3987    border-top: none !important;
3988    border-bottom: none !important;
3989    padding-top: 16px !important;
3990  }
3991  
3992  .pd-full-desc-body {
3993    font-size: 10pt !important;
3994    line-height: 1.6 !important;
3995    color: #334155 !important;
3996  }
3997  
3998  .pd-full-desc-body img {
3999    max-height: 200px !important;
4000    width: auto !important;
4001    page-break-inside: avoid;
4002    display: none !important; /* Hide images in print to save space */
4003  }
4004  
4005  /* Also target #deskripsi img if exists */
4006  #deskripsi img {
4007    max-width: 350px !important;
4008    height: auto !important;
4009  }
4010  
4011  .pd-full-desc-body table {
4012    border-collapse: collapse !important;
4013    width: 100% !important;
4014    margin: 12px 0 !important;
4015    font-size: 9pt !important;
4016  }
4017  
4018  .pd-full-desc-body th,
4019  .pd-full-desc-body td {
4020    border: 1px solid #cbd5e1 !important;
4021    padding: 6px 8px !important;
4022  }
4023  
4024  /* Print footer */
4025  .product-single::after {
4026    content: "Plaza IT - plazait.co.id | Printed: " attr(data-print-date);
4027    display: block;
4028    margin-top: 30px;
4029    padding-top: 16px;
4030    border-top: 1px solid #e2e8f0;
4031    font-size: 9pt;
4032    color: #64748b;
4033    text-align: center;
4034  }
4035  
4036  /* Page breaks */
4037  .pd-media-card,
4038  .pd-price-block,
4039  .pd-full-desc-title {
4040    page-break-after: avoid;
4041  }
4042  
4043  .pd-full-description {
4044    page-break-before: auto;
4045  }
4046}
4047
4048/* === Breadcrumb (truncate last item) === */
4049.pd-breadcrumb{
4050  display:flex;
4051  flex-wrap:nowrap;
4052  align-items:center;
4053  gap:.5rem;
4054  font-size:.72rem;
4055  margin:.7rem 0 1.2rem .5rem;
4056  color:var(--pd-muted);
4057  overflow:hidden;
4058}
4059.pd-breadcrumb a{color:var(--pd-accent);text-decoration:none}
4060.pd-breadcrumb a:hover{text-decoration:underline}
4061.pd-breadcrumb a,
4062.pd-breadcrumb span{flex:0 0 auto}
4063.pd-breadcrumb .current{
4064  flex:1 1 auto;
4065  min-width:0;
4066  white-space:nowrap;
4067  overflow:hidden;
4068  text-overflow:ellipsis;
4069  font-weight:600;
4070  color:var(--pd-dark);
4071  padding-right:2px;
4072}
4073
4074/* === Top layout === */
4075.pd-top-layout{
4076  display:grid;
4077  gap:var(--pd-gap);
4078  grid-template-columns:minmax(260px,340px) minmax(460px,1fr) 300px;
4079  align-items:start;
4080  margin-bottom:2.4rem;
4081}
4082
4083/* === Media column === */
4084.pd-media-card{
4085  background:#fff;
4086  border:1px solid var(--pd-border);
4087  border-radius:var(--pd-radius);
4088  padding:1rem 1rem 1.2rem;
4089}
4090.pd-main-image{margin:0 0 .8rem;border:1px solid var(--pd-soft-border);border-radius:10px;overflow:hidden;background:#fff;position:relative}
4091.pd-main-image img{width:100%;display:block;object-fit:contain;aspect-ratio:1/1}
4092
4093/* Gallery Thumbnails */
4094.pd-thumb-gallery{margin-top:.6rem;display:flex;flex-wrap:wrap;gap:.55rem}
4095.pd-thumb-btn{
4096  border:1px solid var(--pd-soft-border);
4097  background:#fff;
4098  cursor:pointer;
4099  width:64px;height:64px;padding:2px;
4100  display:flex;align-items:center;justify-content:center;
4101  overflow:hidden;border-radius:8px;
4102  transition:border-color .18s ease, box-shadow .18s ease, transform .18s ease;
4103  position:relative;
4104}
4105.pd-thumb-btn img{max-width:100%;max-height:100%;object-fit:contain;display:block}
4106.pd-thumb-btn:hover{border-color:var(--pd-accent)}
4107.pd-thumb-btn.active{border-color:var(--pd-accent);box-shadow:0 0 0 2px rgba(13,110,253,.25)}
4108.pd-thumb-btn:focus-visible{outline:2px solid var(--pd-accent);outline-offset:2px}
4109.pd-thumb-btn:active{transform:scale(.96)}
4110
4111/* === Info column === */
4112.pd-info-card{
4113  background:#fff;border:1px solid var(--pd-border);border-radius:var(--pd-radius);
4114  padding:1.4rem 1.6rem 2rem;
4115}
4116.pd-title{margin:0 0 1rem;font-size:1.2rem;line-height:1.25;font-weight:600;color:var(--pd-dark)}
4117.pd-price-block{margin:0 0 1rem}
4118.pd-price-row{display:flex;flex-wrap:wrap;align-items:center;gap:.75rem;margin:0 0 .35rem}
4119.pd-price-current{font-size:2rem;font-weight:700;color:var(--pd-accent)}
4120.pd-price-original{font-size:.9rem;text-decoration:line-through;color:#94a3b8}
4121.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}
4122.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}
4123.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}
4124.stock-badge.in{background:var(--pd-success-bg);color:var(--pd-success-text)}
4125.stock-badge.out{background:var(--pd-danger-bg);color:var(--pd-danger-text)}
4126.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}
4127.sku{color:var(--pd-muted)}
4128.pd-short-desc{font-size:.85rem;line-height:1.5;margin:0 0 1.2rem;color:#334155;overflow-wrap:anywhere}
4129.pd-short-desc p{margin:0 0 .75rem}
4130.pd-short-desc ul,.pd-short-desc ol{margin:.5rem 0 .9rem 1.1rem;font-size:.85rem;line-height:1.45}
4131
4132/* Meta row for sold and rating badges */
4133.pd-meta-row {
4134  display: flex;
4135  align-items: center;
4136  flex-wrap: wrap;
4137  gap: 5px;
4138  margin-top: 8px;
4139  margin-bottom: 4px;
4140}
4141
4142/* Sales (Terjual) badge inline */
4143.sold-inline{
4144  display:inline-block;font-size:.65rem;font-weight:600;color:#475569;
4145  background:#f1f5f9;padding:3px 8px 4px;border-radius:6px;letter-spacing:.4px;margin-left:0;
4146}
4147
4148/* Price row divider */
4149.price-divider {
4150  display: inline-block;
4151  margin: 0 .4rem;
4152  color: #cbd5e1;
4153  font-weight: 400;
4154}
4155
4156/* Rating badge inline */
4157.rating-inline {
4158  display: inline-flex;
4159  align-items: center;
4160  gap: 4px;
4161  padding: 3px 8px 4px;
4162  background: #fff7ed;
4163  border-radius: 6px;
4164  font-size: .65rem;
4165  font-weight: 600;
4166  color: #ea580c;
4167  text-decoration: none;
4168  transition: all .2s ease;
4169  cursor: pointer;
4170  margin-left: .5rem;
4171  white-space: nowrap;
4172}
4173
4174.rating-inline:hover {
4175  background: #ffedd5;
4176  transform: translateY(-1px);
4177  box-shadow: 0 2px 4px rgba(234, 88, 12, 0.1);
4178}
4179
4180.rating-inline .star-icon {
4181  font-size: .75rem;
4182  color: #f59e0b;
4183  line-height: 1;
4184}
4185
4186.rating-inline .rating-value {
4187  color: #ea580c;
4188}
4189
4190.rating-inline .rating-badge-count {
4191  color: #9a3412;
4192  opacity: 0.85;
4193}
4194
4195@media (max-width: 640px) {
4196  .rating-inline {
4197    font-size: .65rem;
4198    padding: 3px 8px 4px;
4199  }
4200  .rating-inline .star-icon {
4201    font-size: .7rem;
4202  }
4203}
4204
4205/* Highlight animation for review section */
4206@keyframes highlightFlash {
4207  0%, 100% { background-color: transparent; }
4208  50% { background-color: rgba(251, 146, 60, 0.1); }
4209}
4210
4211.highlight-flash {
4212  animation: highlightFlash 1.5s ease;
4213}
4214
4215/* === Variant chips === */
4216.pd-variants{margin-top:14px}
4217.v-group{margin-bottom:12px}
4218.v-label{font-weight:600;margin-bottom:6px}
4219.v-options{display:flex;flex-wrap:wrap;gap:8px}
4220.v-chip{
4221  display:inline-flex;align-items:center;gap:8px;
4222  padding:6px 10px;border:1px solid #d6d9de;border-radius:999px;
4223  cursor:pointer;background:#fff;transition:.15s ease;user-select:none
4224}
4225.v-chip:hover{border-color:#9aa3af;box-shadow:0 1px 0 rgba(0,0,0,.04)}
4226.v-chip.selected{border-color:#2563eb;background:#eff6ff}
4227.v-chip.disabled{opacity:.45;cursor:not-allowed}
4228.v-chip.out{border-style:dashed}
4229.v-chip input{display:none}
4230.v-chip .dot{width:14px;
4230height:14px;border-radius:50%;background:var(--dot,#999);box-shadow:inset 0 0 0 1px rgba(0,0,0,.2)}
4231.v-chip .dot--neutral{background:#9aa3af}
4232.v-chip .txt{font-size:.95rem;white-space:nowrap}
4233.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}
4234.pd-variant-summary{margin-top:8px;color:#475569;font-size:.92rem}
4235.pd-variant-summary .vs-item{display:inline-flex;gap:4px;margin-right:10px}
4236
4237/* === Buy form (di sidebar kanan untuk desktop) === */
4238.pd-buy-form{display:flex;flex-wrap:wrap;align-items:flex-end;gap:1rem 1.4rem;margin:0}
4239.qty-label{display:block;font-size:.6rem;font-weight:600;letter-spacing:.7px;text-transform:uppercase;color:var(--pd-muted);margin-bottom:.35rem}
4240.pd-buy-form input[type=number]{width:90px;padding:.55rem .6rem;border:1px solid var(--pd-soft-border);border-radius:6px;font-size:.85rem}
4241.pd-actions{display:flex;gap:.8rem;flex-wrap:wrap}
4242
4243/* Tombol senada tema */
4244.btn-add-cart,.btn-buy,.btn-chat-icon{
4245  cursor:pointer;border:0;font-weight:700;border-radius:10px;font-size:.9rem;
4246  display:inline-flex;align-items:center;justify-content:center;gap:.4rem;
4247  min-height:44px;padding:0 1rem;transition:var(--pd-transition)
4248}
4249.btn-add-cart{background:var(--brand-500);color:#fff;box-shadow:0 2px 8px -2px rgba(13,110,253,.35)}
4250.btn-add-cart:hover{filter:brightness(.95)}
4251.btn-buy{background:#fff;color:var(--brand-500);border:2px solid var(--brand-500)}
4252.btn-buy:hover{background:rgba(13,110,253,.06)}
4253.btn-chat-icon{background:#fff;color:#fb923c;border:2px solid #fb923c;padding:0;width:44px;min-width:44px}
4254.btn-chat-icon:hover{background:rgba(251, 146, 60, 0.06);border-color:#f97316}
4255.btn-chat-icon svg{width:20px;height:20px;flex-shrink:0}
4256.btn-add-cart:disabled,.btn-buy:disabled,.btn-chat-icon:disabled{opacity:.5;cursor:not-allowed}
4257
4258/* Buy with Voucher Button - Cleaner, more compact design */
4259.btn-buy-voucher{
4260  cursor:pointer;border:0;font-weight:600;border-radius:10px;
4261  display:inline-flex;align-items:center;justify-content:space-between;gap:10px;
4262  min-height:44px;padding:0 14px 0 12px;transition:all 0.3s ease;
4263  background:linear-gradient(135deg, #7c3aed 0%, #9333ea 100%);
4264  color:#fff;
4265  box-shadow:0 3px 10px rgba(124,58,237,0.3);
4266  position:relative;
4267  overflow:hidden;
4268  flex:1 1 auto
4269}
4270.btn-buy-voucher:hover{
4271  transform:translateY(-2px);
4272  box-shadow:0 5px 16px rgba(124,58,237,0.4)
4273}
4274.btn-buy-voucher:active{
4275  transform:translateY(0);
4276  box-shadow:0 2px 6px rgba(124,58,237,0.3)
4277}
4278.btn-buy-voucher .bv-icon{
4279  font-size:20px;
4280  line-height:1;
4281  flex-shrink:0
4282}
4283.btn-buy-voucher .bv-text{
4284  display:flex;
4285  flex-direction:column;
4286  align-items:flex-start;
4287  gap:2px;
4288  flex:1;
4289  min-width:0;
4290  font-size:14px;
4291  line-height:1.2
4292}
4293.btn-buy-voucher .bv-text small{
4294  font-size:10px;
4295  opacity:0.85;
4296  font-weight:500;
4297  letter-spacing:0.3px;
4298  font-family:monospace
4299}
4300.btn-buy-voucher .bv-save{
4301  font-size:12px;
4302  font-weight:700;
4303  background:rgba(255,255,255,0.25);
4304  padding:4px 8px;
4305  border-radius:6px;
4306  white-space:nowrap;
4307  flex-shrink:0
4308}
4309
4310/* Extra meta (opsional) */
4311.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}
4312.pd-extra-meta span{font-weight:600;color:var(--pd-muted);margin-right:.3rem}
4313.pd-extra-meta a{color:var(--pd-accent);text-decoration:none}
4314.pd-extra-meta a:hover{text-decoration:underline}
4315
4316/* === Sidebar === */
4317.pd-sidebar{display:flex;flex-direction:column;gap:1.2rem}
4318.pd-side-card{background:#fff;border:1px solid var(--pd-border);border-radius:var(--pd-radius);padding:1rem 1.1rem 1.2rem}
4319.pd-side-card h3{margin:0 0 .7rem;font-size:.8rem;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:var(--pd-dark)}
4320.pd-side-card ul{list-style:none;margin:0;padding:0;display:grid;gap:.45rem;font-size:.7rem;color:#334155}
4321.pd-side-card li{position:relative;padding-left:13px;line-height:1.25}
4322.pd-side-card li:before{content:'';width:6px;height:6px;background:var(--pd-accent);border-radius:50%;position:absolute;
4322left:0;top:.45rem}
4323.pay-logos{display:flex;gap:.4rem;flex-wrap:wrap;margin:.1rem 0 .4rem}
4324.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}
4325
4326/* Buy card hanya desktop (mobile pakai FAB) */
4327.pd-buy-card{display:block}
4328@media (max-width:880px){ .pd-buy-card{display:none} .pd-title-row {padding-right: 0 !important;}}
4329
4330/* === Full Description === */
4331.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}
4332.pd-full-desc-title{margin:0 0 1.1rem;font-size:1.15rem;font-weight:600;color:var(--pd-dark)}
4333.pd-full-desc-body{font-size:.9rem;line-height:1.6;color:#334155;overflow-wrap:anywhere}
4334.pd-full-desc-body p{margin:0 0 1rem}
4335.pd-full-desc-body ul,.pd-full-desc-body ol{margin:.5rem 0 1rem 1.2rem}
4336.pd-full-desc-body table{width:100%;border-collapse:collapse;margin:1rem 0 1.2rem}
4337.pd-full-desc-body table,.pd-full-desc-body th,.pd-full-desc-body td{border-bottom:1px dotted #cbd5e1;vertical-align: top}
4338.pd-full-desc-body th,.pd-full-desc-body td{padding:.45rem .55rem;text-align:left}
4339.pd-full-desc-body img{max-width:100%;height:auto;display:block;}
4340
4341/* === Related (container) === */
4342.pd-related-full{
4343  background:#fff;border:1px solid var(--pd-border);
4344  border-radius:var(--pd-radius);padding:1.4rem 1.6rem 1.9rem;
4345  margin:0 0 2rem;
4346}
4347.pd-related-title{margin:0 0 1.1rem;font-size:1.05rem;font-weight:600;color:var(--pd-dark)}
4348
4349/* Related Grid (dirapikan: gabung selector agar tidak duplikat) */
4350.pd-related-grid,
4351.pd-related-grid--full{
4352  display:grid !important;
4353  gap:16px !important;
4354  grid-template-columns:repeat(auto-fill,minmax(170px,1fr)) !important;
4355  align-items:stretch;
4356}
4357
4358/* Fallback jika grid tidak didukung */
4359@supports not (display:grid){
4360  .pd-related-grid,
4361  .pd-related-grid--full{display:flex !important;flex-wrap:wrap}
4362  .pd-related-grid .pd-related-card,
4363  .pd-related-grid--full .pd-related-card{flex:0 0 calc(20% - 16px);max-width:calc(20% - 16px)}
4364  @media (max-width:1100px){
4365    .pd-related-grid .pd-related-card,
4366    .pd-related-grid--full .pd-related-card{flex:0 0 calc(25% - 16px);max-width:calc(25% - 16px)}
4367  }
4368  @media (max-width:820px){
4369    .pd-related-grid .pd-related-card,
4370    .pd-related-grid--full .pd-related-card{flex:0 0 calc(33.333% - 16px);max-width:calc(33.333% - 16px)}
4371  }
4372  @media (max-width:580px){
4373    .pd-related-grid .pd-related-card,
4374    .pd-related-grid--full .pd-related-card{flex:0 0 calc(50% - 16px);max-width:calc(50% - 16px)}
4375  }
4376}
4377
4378/* Related Card */
4379.pd-related-card{
4380  display:flex;flex-direction:column;border:1px solid var(--pd-soft-border);
4381  background:#fff;border-radius:10px;text-decoration:none;color:inherit;
4382  overflow:hidden;transition:var(--pd-transition);position:relative;min-width:0;
4383}
4384.pd-related-card:hover{transform:translateY(-4px);box-shadow:var(--pd-shadow)}
4385.pd-related-card .thumb{aspect-ratio:1/1;background:#f1f5f9;overflow:hidden;position:relative}
4386.pd-related-card img{width:100%;height:100%;object-fit:cover;display:block}
4387.mini-disc{position:absolute;top:6px;left:6px;background:#dcce77;color:#fff;font-size:.55rem;padding:3px 6px 2px;border-radius:4px;font-weight:600}
4388.pd-related-card .info{padding:.55rem .65rem .75rem;display:flex;flex-direction:column;gap:.25rem}
4389.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}
4390.pd-related-card .price{font-size:.7rem;font-weight:600;color:var(--pd-accent)}
4391.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;}
4392.pd-related-card .price .final {font-size: .82rem; font-weight: 700; color: #0d6efd;}
4393.pd-related-card .price .ori {font-size: .6rem; text-decoration: line-through; color: #94a3b8;font-weight: 500;}
4394
4395/* === Mobile slider (scroll-snap) — gambar utama tetap tampil === */
4396.pd-mobile-slider{display:none}
4397.pd-mobile-slider img{width:100%;display:block;object-fit:contain;aspect-ratio:1/1}
4398
4399/* Gallery counter badge */
4400.pd-gallery-counter{
4401  position:absolute;
4402  bottom:12px;
4403  left:12px;
4404  background:rgba(255,255,255,0.25);
4405  color:#9E9E9E;
4406  padding:4px 10px;
4407  border-radius:10px;
4408  font-size:12px;
4409  font-weight:600;
4410  z-index:100;
4411  backdrop-filter:blur(6px);
4412  -webkit-backdrop-filter:blur(6px);
4413  font-variant-numeric:tabular-nums;
4414  pointer-events:none;
4415  display:none;
4416  box-shadow:0 1px 4px rgba(0,0,0,.15);
4417  line-height:1;
4418  border:1px solid rgba(0,0,0,0.08);
4419}
4420
4421@media (max-width:640px){
4422  .pd-media-card{border: 0;padding: 0 1rem 0;}
4423  .pd-info-card {border: 0; padding: 0 1.3rem 0; }
4424  .pd-side-card {flex: 1 1 !important;}
4425  .pd-top-layout {gap: 0; margin-bottom: 0;}
4426  .pd-variants {margin-bottom: 1.5rem;}
4427  .pd-main-image{display:none}
4428  .pd-gallery-counter{display:block}
4429  .pd-mobile-slider-wrapper{
4430    position:relative;
4431    margin-bottom:.6rem;
4432  }
4433  .pd-mobile-slider{
4434    display:flex;gap:8px;overflow-x:auto;
4435    scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;
4436  }
4437  .pd-mslide{
4438    min-width:100%;flex:0 0 100%;scroll-snap-align:center;
4439    border:1px solid var(--pd-soft-border);border-radius:10px;overflow:hidden;background:#fff
4440  }
4441  .pd-thumb-gallery{display:none} /* hide thumbnails on mobile */
4442}
4443
4444/* === Mobile Floating Action Bar === */
4445.pd-mobile-fab{
4446  position:fixed;
4447  left:0;
4448  right:0;
4449  bottom:42px; /* Position above bottom-nav (60px min-height + 6px padding) */
4450  display:none;
4451  align-items:center;
4452  gap:.75rem;
4453  padding:12px 16px;
4454  background:rgba(255,255,255,0.95);
4455  backdrop-filter:blur(20px);
4456  -webkit-backdrop-filter:blur(20px);
4457  border-top:1px solid rgba(228,231,235,0.6);
4458  box-shadow:0 -4px 16px -4px rgba(0,0,0,0.12);
4459  z-index:999; /* Below bottom-nav (1000) */
4460  transition:bottom .3s cubic-bezier(0.4, 0.0, 0.2, 1);
4461}
4462
4463/* When bottom-nav is hidden, move FAB to bottom */
4464body:has(.bottom-nav.hidden) .pd-mobile-fab {
4465  bottom: 0;
4466}
4467.pd-mobile-fab .mf-actions{display:flex;gap:.5rem;width:100%}
4468.mf-btn{
4469  border-radius:10px;
4470  padding:.6rem 1rem;
4471  font-weight:600;
4472  font-size:14px;
4473  cursor:pointer;
4474  transition:all .2s;
4475  -webkit-tap-highlight-color:transparent;
4476  display:inline-flex;
4477  align-items:center;
4478  justify-content:center;
4479  gap:4px;
4480}
4481.mf-buy{
4482  background:#fb923c;
4483  color:#fff;
4484  border:0;
4485  box-shadow:0 2px 8px rgba(251, 146, 60, 0.3);
4486  flex:1;
4487  flex-direction:column;
4488  padding:.55rem 1rem;
4489  line-height:1;
4490  min-height:44px;
4491  font-family:inherit;
4492}
4493.mf-buy .mf-buy-text{
4494  font-size:11px;
4495  font-weight:600;
4496  opacity:0.95;
4497  font-family:inherit;
4498}
4499.mf-buy .mf-buy-price{
4500  font-size:13px;
4501  font-weight:800;
4502  letter-spacing:-0.3px;
4503  font-family:inherit;
4504  line-height:1;
4505}
4506.mf-buy:active{transform:scale(0.97)}
4507.mf-cart{
4508  background:#fff;
4509  color:#fb923c;
4510  border:2px solid #fb923c;
4511  width:44px;
4512  height:44px;
4513  min-height:44px;
4514  padding:0;
4515  flex-shrink:0;
4516  font-family:inherit;
4517}
4518.mf-cart:active{transform:scale(0.95)}
4519.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}
4520.mf-chat:active{transform:scale(0.95)}
4521.mf-chat svg{width:20px;height:20px;flex-shrink:0}
4522.mf-share{
4523  background:#fff;
4524  color:#fb923c;
4525  border:2px solid #fb923c;
4526  width:44px;
4527  height:44px;
4528  padding:0;
4529  flex-shrink:0;
4530}
4531.mf-share:active{transform:scale(0.95)}
4532.mf-btn:disabled{opacity:.5;cursor:not-allowed}
4533@media (max-width:640px){
4534  .pd-mobile-fab{display:flex}
4535  .product-single{padding-bottom:0;} /* Space for FAB + bottom-nav */
4536}
4537
4538/* === Installment cards === */
4539.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}
4540.pd-installment-card h3{margin:0 0 .2rem;font-size:.78rem;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:var(--pd-dark)}
4541
4542.inst-tabs{display:flex;gap:.4rem}
4543.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)}
4544.inst-tab.active,.inst-tab:hover{background:var(--pd-accent);color:#fff;border-color:var(--pd-accent)}
4545.inst-tab:focus{outline:2px solid var(--pd-accent);outline-offset:2px}
4546
4547.inst-tenors{display:flex;gap:.4rem;flex-wrap:wrap}
4548.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}
4549.tenor-btn.active,.tenor-btn:hover{background:#1d7df0;color:#fff;border-color:#1d7df0}
4550
4551.inst-result{display:flex;flex-direction:column;gap:.45rem;font-size:.63rem}
4552.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}
4553.inst-line.highlight{background:#1d7df0;color:#fff;border-color:#1d7df0;font-weight:600}
4554.inst-line .lbl{opacity:.8}
4555.inst-line .val{font-weight:600;letter-spacing:.3px}
4556.inst-footnote{font-size:.53rem;line-height:1.25;color:#64748b;margin-top:.2rem}
4557@media (max-width: 860px){
4558  .pd-installment-card{order:3}
4559  .inst-tabs .inst-tab,.inst-tenors .tenor-btn{font-size:.65rem}
4560  
4561  /* Voucher button responsive - make it simpler */
4562  .btn-buy-voucher{
4563    padding:0 12px;
4564    gap:8px
4565  }
4566  .btn-buy-voucher .bv-icon{
4567    font-size:18px
4568  }
4569  .btn-buy-voucher .bv-text{
4570    font-size:13px
4571  }
4572  .btn-buy-voucher .bv-text small{
4573    font-size:9px
4574  }
4575  .btn-buy-voucher .bv-save{
4576    font-size:11px;
4577    padding:3px 6px
4578  }
4579}
4580
4581/* Installment List v2 (eye-catching) */
4582.pd-installment-card2{
4583  background:linear-gradient(135deg,#ffffff 0%,#f1f7ff 65%,#e8f2ff 100%);
4584  border:1px solid var(--pd-border);border-radius:var(--pd-radius);
4585  padding:1rem 1.05rem 1.2rem;display:flex;flex-direction:column;gap:.5rem;
4586  position:relative;overflow:hidden
4587}
4588.pd-installment-card2:before{
4589  content:'';position:absolute;width:140px;height:140px;
4590  background:radial-gradient(circle at center,rgba(29,125,240,.18),rgba(29,125,240,0));
4591  top:-40px;right:-40px;pointer-events:none
4592}
4593.pd-installment-card2 h3{margin:0;font-size:.8rem;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:#0f2f54}
4594.pd-installment-card2 .inst-head{display:flex;align-items:center;justify-content:space-between;gap:.5rem}
4595.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)}
4596
4597.inst-bank-tabs{display:flex;gap:.45rem}
4598.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}
4599.bank-tab.active,.bank-tab:hover{background:#1d7df0;color:#fff;border-color:#1d7df0;box-shadow:0 3px 10px -4px rgba(29,125,240,.6)}
4600.bank-tab:focus{outline:2px solid #1d7df0;outline-offset:2px}
4601
4602.inst-month-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.55rem}
4603.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}
4604.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}
4605.inst-month-item:hover{transform:translateY(-2px);box-shadow:0 6px 14px -6px rgba(0,40,120,.25)}
4606.inst-month-item:hover:before{opacity:1}
4607.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)}
4608.amount{flex:1;font-size:.8rem;font-weight:600;color:#0f2f54;letter-spacing:.3px}
4609.tag{font-size:.55rem;font-weight:600;padding:.28rem .55rem;border-radius:30px;letter-spacing:.4px;text-transform:uppercase;white-space:nowrap}
4610.tag-zero{background:#16a34a;color:#fff;box-shadow:0 2px 8px -3px rgba(16,180,90,.55)}
4611
4612.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}
4613.inst-disclaimer{font-size:.52rem;line-height:1.25;color:#64748b;margin-top:-.2rem}
4614
4615@media (max-width:860px){
4616  .pd-installment-card2{order:3}
4617  .tenor-badge{min-width:38px;font-size:.75rem}
4618  .amount{font-size:.78rem}
4619}
4620
4621/* Alt Fintech Banner (compact) */
4622.inst-alt-banner--compact{
4623  background:linear-gradient(142deg,#1d7df0 0%,#3c98ff 55%,#67bdff 100%);
4624  color:#fff;border:0;padding:.95rem .95rem 1.05rem;border-radius:16px;
4625  box-shadow:0 6px 18px -6px rgba(0,70,160,.45);position:relative;overflow:hidden;min-height:auto;
4626}
4627.inst-alt-banner--compact:before,
4628.inst-alt-banner--compact:after{
4629  content:'';position:absolute;border-radius:50%;background:rgba(255,255,255,.15);filter:blur(2px);pointer-events:none;
4630}
4631.inst-alt-banner--compact:before{width:130px;height:130px;top:-50px;right:-35px}
4632.inst-alt-banner--compact:after{width:90px;height:90px;bottom:-40px;left:-30px}
4633.inst-alt-banner--compact .iab-top{margin:0 0 .55rem;display:flex;flex-direction:column;gap:.25rem}
4634.inst-alt-banner--compact .iab-title{font-size:.74rem;font-weight:700;letter-spacing:.7px;line-height:1.15}
4635.inst-alt-banner--compact .iab-sub{font-size:.55rem;font-weight:500;opacity:.95;letter-spacing:.4px}
4636
4637.iab-logos-compact{display:flex;gap:.55rem;align-items:center;margin:0 0 .6rem;flex-wrap:nowrap;justify-content:space-between}
4638.iab-logos-compact .logo-fin{width:30%}
4639.iab-logos-compact .logo{
4640  width:48px;height:48px;border-radius:14px;display:flex;align-items:center;justify-content:center;
4641  font-size:.62rem;font-weight:700;letter-spacing:.3px;position:relative;color:#fff;
4642  box-shadow:0 4px 10px -4px rgba(0,0,0,.35);background:rgba(255,255,255,.15);
4643  backdrop-filter:blur(4px);transition:.25s;text-shadow:0 1px 2px rgba(0,0,0,.4);user-select:none;
4644}
4645.iab-logos-compact .logo:before{
4646  content:'';position:absolute;inset:0;border-radius:inherit;
4647  background:linear-gradient(140deg,rgba(255,255,255,.35),rgba(255,255,255,0));mix-blend-mode:overlay;opacity:.55;pointer-events:none;
4648}
4649.iab-logos-compact .logo:hover{transform:translateY(-3px) scale(1.05);box-shadow:0 8px 18px -6px rgba(0,0,0,.45)}
4650
4651.iab-cta--compact{
4652  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;
4653}
4654.iab-cta--compact:hover{transform:translateY(-2px);box-shadow:0 8px 18px -7px rgba(0,0,0,.45)}
4655.iab-footnote--compact{margin-top:.45rem;font-size:.46rem;opacity:.85;letter-spacing:.25px}
4656@media (max-width:860px){
4657  .inst-alt-banner--compact{order:4;margin-top:.4rem;flex:1 1}
4658  .iab-logos-compact .logo{width:46px;height:46px}
4659}
4660
4661/* === Scroll To Top (baru) === */
4662.pd-scroll-top{
4663  position:fixed;
4664  right:20px;
4665  bottom:90px;
4666  width:48px;
4667  height:48px;
4668  border-radius:50%;
4669  background:linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
4670  color:#0f2f54;
4671  border:1px solid rgba(226, 232, 240, 0.8);
4672  box-shadow:0 8px 20px -8px rgba(0,0,0,.3), 0 2px 6px rgba(0,0,0,.1);
4673  display:flex;
4674  align-items:center;
4675  justify-content:center;
4676  cursor:pointer;
4677  z-index:1105;
4678  opacity:0;
4679  transform:translateY(12px) scale(.92);
4680  transition:all .3s cubic-bezier(0.4, 0, 0.2, 1);
4681  backdrop-filter:blur(8px);
4682  -webkit-backdrop-filter:blur(8px);
4683}
4684.pd-scroll-top:hover{
4685  box-shadow:0 12px 28px -10px rgba(0,0,0,.4), 0 4px 12px rgba(0,0,0,.15);
4686  transform:translateY(0) scale(1);
4687  background:linear-gradient(135deg, #ffffff 0%, #fefefe 100%);
4688  border-color:rgba(226, 232, 240, 1);
4689}
4690.pd-scroll-top:active{
4691  transform:translateY(0) scale(.96);
4692}
4693.pd-scroll-top:focus-visible{outline:2px solid var(--pd-accent);outline-offset:2px}
4694.pd-scroll-top.visible{opacity:1;transform:translateY(0) scale(1)}
4695@media (max-width:640px){
4696  /* Dynamic positioning via JavaScript */
4697  .pd-scroll-top{
4698    right:14px;
4699    bottom:var(--scroll-top-bottom, calc(68px + 18px + env(safe-area-inset-bottom)));
4700  }
4701}
4702
4703/* === Responsive layout tweaks === */
4704@media (max-width:1250px){
4705  .pd-top-layout{grid-template-columns:minmax(260px,330px) 1fr}
4706  .pd-sidebar{grid-column:1 / -1;flex-direction:row;flex-wrap:wrap;gap:1rem}
4707  /* Other cards 2-per-row; buy card always takes full row to avoid height-mismatch gaps */
4708  .pd-side-card{flex:1 1 calc(50% - .5rem)}
4709  .pd-buy-card{flex:0 0 100%}
4710}
4711@media (max-width:880px){
4712  .pd-top-layout{grid-template-columns:1fr}
4713  .pd-sidebar{flex-direction:column; padding: 0 1rem;}
4714  .pd-title{font-size:1.2rem}
4715  .pd-price-current{font-size:1.5rem}
4716}
4717@media (max-width:640px){
4718  /* Related: 2 kolom di mobile */
4719  .pd-related-full{padding:1.2rem;border:0;padding-top: .6rem;margin-bottom:.6rem;}
4720  .pd-related-grid,.pd-related-grid--full{grid-template-columns:repeat(2,1fr) !important;gap:12px !important}
4721}
4722@media (max-width:560px){
4723  .pd-actions{flex-direction:column}
4724  .btn-add-cart,.btn-buy,.btn-buy-voucher{width:100%}
4725  .pd-full-description{padding:1.2rem;border:0;padding-bottom:0;}
4726}
4727@media (max-width:380px){
4728  .pd-related-full{padding:.9rem .7rem 1.1rem}
4729  .pd-related-grid,.pd-related-grid--full{gap:8px !important}
4730  .pd-related-card .info{padding:.4rem .48rem .55rem}
4731}
4732
4733/* === Modern Countdown (Product Detail) === */
4734.pd-countdown-modern{
4735  --cd-bg1:#0ea5e9;          /* sky-500 */
4736  --cd-bg2:#6366f1;          /* indigo-500 */
4737  --cd-text:#0b1220;
4738  --cd-contrast:#ffffff;
4739  --cd-shadow: 0 8px 24px -10px rgba(0,0,0,.25);
4740  --cd-border: rgba(255,255,255,.25);
4741  --cd-box: rgba(255,255,255,.65);
4742  --cd-box-text:#0f172a;
4743  --cd-danger:#ef4444;
4744
4745  display:flex; gap:.75rem; align-items:center;
4746  background:linear-gradient(135deg,var(--cd-bg1),var(--cd-bg2));
4747  border:1px solid var(--cd-border);
4748  color:var(--cd-contrast);
4749  border-radius:14px;
4750  padding:.6rem .8rem;
4751  box-shadow:var(--cd-shadow);
4752  position:relative;
4753  overflow:hidden;
4754  margin-bottom: 1rem;
4755}
4756
4757.pd-countdown-modern .pd-cd-icon{
4758  width:28px;height:28px;flex:0 0 28px;
4759  display:grid;place-items:center;
4760  background:rgba(255,255,255,.22);
4761  border:1px solid var(--cd-border);
4762  border-radius:10px;
4763  box-shadow: inset 0 1px 0 rgba(255,255,255,.35);
4764}
4765.pd-countdown-modern .pd-cd-icon svg{width:18px;height:18px;fill:#fff;opacity:.95}
4766
4767.pd-countdown-modern .pd-cd-body{display:flex;flex-direction:column;gap:.35rem;min-width:0}
4768
4769.pd-countdown-modern .pd-cd-label{
4770  font-size:.72rem;font-weight:700;letter-spacing:.4px;opacity:.95;line-height:1;
4771  text-shadow:0 1px 0 rgba(0,0,0,.08);
4772}
4773
4774.pd-countdown-modern .pd-cd-time{
4775  display:flex; align-items:end; gap:.35rem; flex-wrap:wrap;
4776  line-height:1;
4777}
4778
4779.pd-countdown-modern .unit{
4780  display:flex; flex-direction:column; align-items:center; gap:.18rem;
4781  min-width:58px;
4782}
4783.pd-countdown-modern .num{
4784  display:inline-flex; align-items:center; justify-content:center;
4785  min-width:58px; height:40px; padding:0 .4rem;
4786  background:var(--cd-box);
4787  color:var(--cd-box-text);
4788  border:1px solid rgba(15,23,42,.08);
4789  border-radius:10px;
4790  font: 800 1.05rem ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
4791  letter-spacing:.5px;
4792  box-shadow: 0 4px 14px -6px rgba(0,0,0,.4), inset 0 1px 0 rgba(255,255,255,.55);
4793}
4794.pd-countdown-modern .unit-label{
4795  font-size:.62rem; font-weight:700; opacity:.9; text-shadow:0 1px 0 rgba(255,255,255,.15);
4796}
4797
4798.pd-countdown-modern .colon{
4799  font-weight:900; font-size:1.05rem; opacity:.95; transform: translateY(-2px);
4800  animation: cdBlink 1.2s infinite steps(2, jump-none);
4801}
4802@keyframes cdBlink { 0%{opacity:1} 50%{opacity:.25} 100%{opacity:1} }
4803
4804.pd-countdown-modern .pd-cd-progress{
4805  display:none;
4806  position:relative;
4807  height:6px; border-radius:999px; overflow:hidden;
4808  background:rgba(255,255,255,.25);
4809  border:1px solid rgba(255,255,255,.28);
4810}
4811.pd-countdown-modern .pd-cd-progress .bar{
4812  height:100%; width:0%; background:linear-gradient(90deg,#22c55e,#a3e635);
4813  box-shadow: 0 2px 8px rgba(0,0,0,.25) inset;
4814  transition:width .35s ease;
4815}
4816
4817/* State saat hampir habis */
4818.pd-countdown-modern.danger{
4819  --cd-bg1:#f97316; /* orange-500 */
4820  --cd-bg2:#ef4444; /* red-500   */
4821}
4822
4823/* Responsive */
4824@media (max-width:640px){
4825  .pd-countdown-modern{
4826    padding:.55rem .65rem; border-radius:12px; gap:.6rem;
4827  }
4828  .pd-countdown-modern .unit{min-width:52px}
4829  .pd-countdown-modern .num{min-width:52px;height:36px;font-size:.98rem}
4830}
4831
4832/* === Modern Countdown polish (shine + compact) === */
4833.pd-countdown-modern{
4834  position:relative;
4835}
4836.pd-countdown-modern::after{
4837  content:"";
4838  position:absolute; inset:-1px;
4839  background: linear-gradient(120deg, rgba(255,255,255,.12), rgba(255,255,255,0) 35%),
4840              radial-gradient(120% 120% at 0% 0%, rgba(255,255,255,.08), rgba(255,255,255,0) 60%);
4841  border-radius: inherit;
4842  pointer-events:none;
4843  mix-blend-mode: screen;
4844  animation: sheen 4.5s ease-in-out infinite;
4845}
4846@keyframes sheen {
4847  0%   { opacity:.35; transform: translateX(-10%) }
4848  50%  { opacity:.15; transform: translateX(4%) }
4849  100% { opacity:.35; transform: translateX(-10%) }
4850}
4851
4852/* Mode compact (aktifkan dengan data-mode="compact" di elemen) */
4853.pd-countdown-modern[data-mode="compact"]{
4854  gap:.55rem; padding:.5rem .6rem; border-radius:12px;
4855}
4856.pd-countdown-modern[data-mode="compact"] .unit{
4857  min-width:46px;
4858}
4859.pd-countdown-modern[data-mode="compact"] .num{
4860  min-width:46px; height:34px; font-size:.92rem;
4861}
4862.pd-countdown-modern[data-mode="compact"] .pd-cd-label{
4863  font-size:.66rem;
4864}
4865.pd-countdown-modern[data-mode="compact"] .pd-cd-icon{
4866  width:24px; height:24px; border-radius:8px;
4867}
4868.pd-countdown-modern[data-mode="compact"] .pd-cd-progress{
4869  height:5px;
4870}
4871
4872/* ============================================================
4873   Sidebar: shared refinements for medium + tablet (641px – 1250px)
4874   Buy card = form & paylater on the same row.
4875   Alt-fintech logos compact (no over-stretching).
4876   ============================================================ */
4877@media (min-width:641px) and (max-width:1250px) {
4878  /* Buy card: put qty/buy buttons and paylater promo inline */
4879  .pd-buy-card {
4880    display: flex !important;
4881    flex-direction: row;
4882    flex-wrap: wrap;
4883    align-items: center;
4884    gap: .6rem .75rem;
4885  }
4886  .pd-buy-card .pd-buy-form { flex: 1 1 0; min-width: 0; }
4887  .pd-buy-card .paylater-promo {
4888    flex: 0 0 auto;
4889    align-self: center;
4890    margin: 0;
4891    max-width: 44%;
4892  }
4893
4894  /* Alt-fintech banner: tighter logos */
4895  .iab-logos-compact {
4896    justify-content: center;
4897    gap: 1.25rem;
4898  }
4899  .iab-logos-compact .logo-fin {
4900    width: auto;
4901    max-height: 28px;
4902    object-fit: contain;
4903  }
4904}
4905
4906/* ============================================================
4907   Tablet layout (641px – 880px)
4908   Restores 2-column grid + horizontal sidebar for mid-size screens.
4909   Overrides the generic ≤880px mobile-collapse rules above.
4910   ============================================================ */
4911@media (min-width:641px) and (max-width:880px) {
4912  /* 2-column grid: image-card | info-card */
4913  .pd-top-layout {
4914    grid-template-columns: minmax(260px, 40%) 1fr;
4915    gap: 1rem;
4916  }
4917
4918  /* Sidebar: full-width horizontal flex row below the two main columns */
4919  .pd-sidebar {
4920    grid-column: 1 / -1;
4921    flex-direction: row;
4922    flex-wrap: wrap;
4923    gap: .75rem;
4924    padding: 0;
4925  }
4926  /* Banner cards fill 2-per-row */
4927  .pd-side-card { flex: 1 1 calc(50% - .375rem); }
4928  /* Buy card: full row (tallest card – avoids white-space gap if shared with shorter banners) */
4929  .pd-buy-card  { display: block !important; flex: 0 0 100%; }
4930
4931  /* Hide installment simulator + alt-fintech banner on tablet (too much vertical space) */
4932  .pd-installment-card2,
4933  .inst-alt-banner--compact { display: none !important; }
4934
4935  /* Restore sticky add-to-cart header (hidden at ≤880px by default) */
4936  .pd-sticky-header { display: flex !important; }
4937
4938  /* Typography – tighten slightly to fit the narrower info column */
4939  .pd-price-current { font-size: 1.65rem; }
4940  .pd-title         { font-size: 1.1rem;  }
4941  .pd-info-card     { padding: 1.1rem 1.2rem 1.5rem; }
4942}
4943</style>
4944  <style data-inlined="product-grid.css">
4945/* Product Grid (Firebird) */
4946
4947.product-archive {
4948  max-width: 1320px;
4949  margin-inline: auto;
4950  padding: 0 0 3rem;
4951  /*font-family: system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,sans-serif;*/
4952}
4953
4954.pa-toolbar {
4955  display: flex;
4956  flex-wrap: wrap;
4957  gap: 1rem;
4958  justify-content: space-between;
4959  align-items: flex-start;
4960}
4961
4962.pa-title {
4963  font-size: 1.4rem;
4964  margin: 0;
4965  font-weight: 600;
4966  line-height: 1.2;
4967  color: #0f172a;
4968}
4969
4970.pa-left {
4971  display: flex;
4972  flex-wrap: wrap;
4973  gap: .75rem;
4974  align-items: center;
4975}
4976
4977.pa-filter-badge {
4978  background: #f1f5f9;
4979  border: 1px solid #e2e8f0;
4980  padding: .45rem .6rem;
4981  border-radius: 8px;
4982  font-size: .7rem;
4983  display: flex;
4984  align-items: center;
4985  gap: .4rem;
4986  line-height: 1;
4987  color: #475569;
4988}
4989.pa-filter-badge strong { color: #0f172a; }
4990.pa-filter-badge .clear {
4991  display:inline-block;
4992  text-decoration:none;
4993  background:#e2e8f0;
4994  color:#475569;
4995  font-weight:600;
4996  padding:0 .35rem .05rem;
4997  border-radius:4px;
4998  transition:.2s;
4999  font-size:.75rem;
5000  line-height:1;
5001}
5002.pa-filter-badge .clear:hover { background:#cbd5e1; color:#1e293b; }
5003
5004.pa-right {
5005  display:flex;
5006  gap:1rem;
5007  flex-wrap:wrap;
5008  align-items:flex-start;
5009  margin-bottom: 0.5rem;
5010}
5011
5012.pa-search {
5013  display:flex;
5014  gap:.5rem;
5015  align-items:center;
5016  background:#fff;
5017  border:1px solid #e2e8f0;
5018  padding:.4rem .5rem;
5019  border-radius:8px;
5020}
5021.pa-search input[type=text] {
5022  border:0;
5023  outline:none;
5024  font-size:.8rem;
5025  padding:.35rem .4rem;
5026  min-width:200px;
5027}
5028.pa-search button {
5029  background:#0d6efd;
5030  border:0;
5031  color:#fff;
5032  font-size:.7rem;
5033  padding:.45rem .75rem;
5034  border-radius:6px;
5035  cursor:pointer;
5036  font-weight:600;
5037  letter-spacing:.3px;
5038  transition:.25s;
5039}
5040.pa-search button:hover { background:#0b5dc4; }
5041
5042.pa-sort {
5043  display:flex;
5044  gap:.4rem;
5045  align-items:center;
5046  font-size:.7rem;
5047  background:#fff;
5048  border:1px solid #e2e8f0;
5049  padding:.4rem .55rem;
5050  border-radius:8px;
5051}
5052.pa-sort select {
5053  font-size:.75rem;
5054  padding:.35rem .5rem;
5055  border:1px solid #cbd5e1;
5056  border-radius:6px;
5057  background:#fff;
5058  outline:none;
5059}
5060
5061.pa-empty {
5062  border:1px dashed #cbd5e1;
5063  padding:3rem 1rem;
5064  text-align:center;
5065  border-radius:12px;
5066  background:#fff;
5067}
5068.pa-empty p {
5069  margin:0 0 1rem;
5070  font-size:.9rem;
5071  color:#475569;
5072}
5073.btn-reset {
5074  display:inline-block;
5075  background:#0d6efd;
5076  color:#fff;
5077  font-size:.7rem;
5078  padding:.55rem 1rem;
5079  border-radius:8px;
5080  text-decoration:none;
5081  font-weight:600;
5082  letter-spacing:.4px;
5083  transition:.25s;
5084}
5085.btn-reset:hover { background:#0a58c7; }
5086
5087/* Grid */
5088.pa-grid {
5089  display:grid;
5090  gap:1rem;
5091  grid-template-columns:repeat(auto-fill, minmax(180px,1fr));
5092  align-items:stretch;
5093}
5094
5095.pa-card {
5096  background:#fff;
5097  border:1px solid #e2e8f0;
5098  border-radius:12px;
5099  display:flex;
5100  flex-direction:column;
5101  text-decoration:none;
5102  color:inherit;
5103  position:relative;
5104  overflow:hidden;
5105  transition:.28s cubic-bezier(.4,0,.2,1);
5106  min-width:0;
5107  padding: 0;
5108}
5109.pa-card:hover {
5110  transform:translateY(-4px);
5111  box-shadow:0 8px 22px -8px rgba(0,0,0,.15);
5112  border-color:#dce2ea;
5113}
5114
5115.pa-card .thumb {
5116  aspect-ratio:1/1;
5117  background:#f1f5f9;
5118  display:flex;
5119  align-items:center;
5120  justify-content:center;
5121  overflow:hidden;
5122  position:relative;
5123}
5124.pa-card img {
5125  width:100%;
5126  height:100%;
5127  object-fit:cover;
5128  display:block;
5129}
5130.badge-disc {
5131  position:absolute;
5132  top:8px;
5133  left:8px;
5134  background:#dcce77;
5135  color:#fff;
5136  font-size:.6rem;
5137  padding:4px 6px 3px;
5138  border-radius:6px;
5139  font-weight:700;
5140  letter-spacing:.4px;
5141}
5142
5143.pa-card .body {
5144  padding:.65rem .7rem .8rem;
5145  display:flex;
5146  flex-direction:column;
5147  gap:.45rem;
5148  min-height:90px;
5149}
5150
5151.pa-card .title {
5152  font-size:.75rem;
5153  line-height:1.25;
5154  margin:0;
5155  font-weight:600;
5156  color:#1e293b;
5157  height:2.4em;
5158  overflow:hidden;
5159}
5160
5161.pa-card .prices {
5162  display:flex;
5163  flex-wrap:wrap;
5164  align-items:baseline;
5165  gap:.4rem;
5166}
5167.pa-card .prices .final {
5168  font-size:.83rem;
5169  font-weight:700;
5170  color:#0d6efd;
5171}
5172.pa-card .prices .ori {
5173  font-size:.63rem;
5174  text-decoration:line-through;
5175  color:#94a3b8;
5176  font-weight:500;
5177}
5178
5179.pa-card .cat {
5180  margin-top:auto;
5181  font-size:.55rem;
5182  letter-spacing:.6px;
5183  text-transform:uppercase;
5184  background:#f1f5f9;
5185  border:1px solid #e2e8f0;
5186  padding:.28rem .45rem .25rem;
5187  border-radius:20px;
5188  font-weight:600;
5189  color:#475569;
5190  align-self:flex-start;
5191}
5192.pa-card .meta-line {
5193    margin-top:4px;
5194    font-size:.65rem;
5195    color:#475569;
5196}
5197.pa-card .meta-line .sold {
5198    display:inline-block;
5199    background:#f1f5f9;
5200    padding:2px 6px 3px;
5201    border-radius:4px;
5202    font-weight:600;
5203    letter-spacing:.3px;
5204    color:#334155;
5205}
5206
5207/* Pagination */
5208.pa-pagination {
5209  margin-top:2rem;
5210  display:flex;
5211  flex-wrap:wrap;
5212  gap:.4rem;
5213  justify-content:center;
5214  font-size:.7rem;
5215  align-items:center;
5216}
5217.pa-pagination a,
5218.pa-pagination span {
5219  min-width:34px;
5220  height:34px;
5221  display:flex;
5222  align-items:center;
5223  justify-content:center;
5224  border-radius:8px;
5225  text-decoration:none;
5226  font-weight:600;
5227  padding:0 .6rem;
5228  box-sizing:border-box;
5229  border:1px solid #e2e8f0;
5230  background:#fff;
5231  color:#334155;
5232  transition:.25s;
5233}
5234.pa-pagination a:hover {
5235  background:#f1f5f9;
5236  color:#0f172a;
5237}
5238.pa-pagination .current {
5239  background:#0d6efd;
5240  color:#fff;
5241  border-color:#0d6efd;
5242}
5243.pa-pagination .gap {
5244  border:none;
5245  background:transparent;
5246  font-weight:400;
5247  color:#94a3b8;
5248}
5249
5250/* =============== Mobile Filter Bar (Tokopedia-like) =============== */
5251.pa-mobile-filterbar{display:none}
5252@media (max-width: 640px){
5253  /* sembunyikan tombol lama */
5254  .pa-btn-filter{display:none}
5255
5256  .pa-mobile-filterbar{
5257    display:block;
5258    position:sticky;
5259    top:0; /* sesuaikan jika ada header fixed di atas */
5260    z-index:5;
5261    background:#fff;
5262    border-bottom:1px solid #e2e8f0;
5263    padding:.4rem .5rem;
5264    margin:-.4rem -1rem .6rem; /* full-bleed di container mobile */
5265  }
5266  .pa-mobile-filterbar .mf-scroll{
5267    display:flex;align-items:center;gap:.5rem;
5268    overflow-x:auto;
5269    -webkit-overflow-scrolling:touch;
5270    scrollbar-width:none;
5271  }
5272  .pa-mobile-filterbar .mf-scroll::-webkit-scrollbar{display:none}
5273
5274  .mf-chip{
5275    flex:0 0 auto;
5276    background:#fff;
5277    color:#334155;
5278    text-decoration:none;
5279    border:1px solid #e2e8f0;
5280    border-radius:999px;
5281    padding:.44rem .7rem;
5282    font-size:.78rem;
5283    font-weight:600;
5284    letter-spacing:.2px;
5285    white-space:nowrap;
5286  }
5287  .mf-chip:active{transform:scale(.98)}
5288  .mf-chip.active{
5289    background:#eaf2ff;
5290    border-color:#0d6efd;
5291    color:#0d6efd;
5292  }
5293
5294  .mf-filter-btn{
5295    position:relative;
5296    flex:0 0 auto;
5297    width:38px;height:38px;
5298    border-radius:50%;
5299    border:1.5px solid #0d6efd;
5300    background:#fff;
5301    display:inline-flex;align-items:center;justify-content:center;
5302    color:#0d6efd;
5303    cursor:pointer;
5304  }
5305  .mf-filter-btn:active{transform:scale(.96)}
5306  .mf-filter-btn svg{width:18px;height:18px;display:block}
5307  .mf-filter-btn .mf-badge{
5308    position:absolute;top:0;right:-4px;
5309    min-width:18px;height:18px;border-radius:9px;
5310    background:#ef4444;color:#fff;
5311    font-size:.65rem;font-weight:700;
5312    display:flex;align-items:center;justify-content:center;
5313    padding:0 4px;line-height:1;
5314    box-shadow:0 2px 6px -2px rgba(239,68,68,.6);
5315  }
5316}
5317
5318/* Responsive */
5319@media (max-width: 640px) {
5320  .product-archive { padding: 0 1rem 3rem; }
5321  .pa-grid { grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); }
5322  .pa-search input[type=text]{ min-width:140px; }
5323  .pa-toolbar { flex-direction:column; align-items:stretch; margin-bottom: 0; display: none;}
5324  .pa-right { display: none; }
5325  .pa-sort { width: 100%; justify-content:center; }
5326  .pa-mobile-filterbar { display: flex !important; }
5327}
5328
5329.pa-mobile-filterbar {
5330  display: none;
5331  align-items: center;
5332  gap: 8px;
5333  padding: 8px 0 8px 12px;
5334  background: #fff;
5335  width: 100vw;
5336}
5337.pa-mobile-filterbar .mf-scroll {
5338  display: flex;
5339  gap: 8px;
5340  overflow-x: auto;
5341  -webkit-overflow-scrolling: touch;
5342  scrollbar-width: none; /* Firefox */
5343  flex: 1 1 auto; /* takes remaining width */
5344}
5345.pa-mobile-filterbar .mf-scroll::-webkit-scrollbar { display: none; } /* WebKit */
5346.pa-mobile-filterbar .mf-chip {
5347  flex: 0 0 auto; /* prevent shrinking, keep width based on content */
5348  white-space: nowrap;
5349}
5350.pa-mobile-filterbar .mf-filter-btn {
5351  flex: 0 0 auto; /* do not shrink */
5352  position: relative; /* keep in place, not inside scroller */
5353  display: inline-flex;
5354  align-items: center;
5355  gap: 6px;
5356}
5357
5358/* Optional: ensure on small screens the button stays visible with min width */
5359@media (max-width: 768px) {
5360  .pa-mobile-filterbar .mf-filter-btn {
5361    padding: 6px 10px;
5362  }
5363}
5364
5365/* Product Specification Chips */
5366.pa-card .spec-chips {
5367  font-size: .6rem;
5368  color: #64748b;
5369  font-weight: 500;
5370  letter-spacing: .2px;
5371  margin-top: 2px;
5372  margin-bottom: 4px;
5373  line-height: 1.2;
5374  opacity: 0.9;
5375  max-width: 100%;
5376  overflow: hidden;
5377  text-overflow: ellipsis;
5378  white-space: nowrap;
5379}
5380
5381.pa-card .spec-chips .spec-divider {
5382  color: #94a3b8;
5383  font-weight: 400;
5384  margin: 0 1px;
5385}
5386</style>
5387  <style data-inlined="site-notice.css">
5388/**
5389 * Site Notice Banner - Reusable Notification System
5390 * Dismissible top bar for announcements, holidays, maintenance, etc.
5391 */
5392
5393.holiday-notice {
5394    position: fixed;
5395    top: 0;
5396    left: 0;
5397    right: 0;
5398    z-index: 9999;
5399    background: linear-gradient(135deg, #d32f2f 0%, #f57c00 100%);
5400    border-bottom: 2px solid rgba(255, 255, 255, 0.2);
5401    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
5402    animation: slideDown 0.4s cubic-bezier(0.4, 0, 0.2, 1);
5403    transform-origin: top;
5404}
5405
5406.holiday-notice.hidden {
5407    animation: slideUp 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;
5408}
5409
5410@keyframes slideDown {
5411    from {
5412        transform: translateY(-100%);
5413        opacity: 0;
5414    }
5415    to {
5416        transform: translateY(0);
5417        opacity: 1;
5418    }
5419}
5420
5421@keyframes slideUp {
5422    from {
5423        transform: translateY(0);
5424        opacity: 1;
5425    }
5426    to {
5427        transform: translateY(-100%);
5428        opacity: 0;
5429    }
5430}
5431
5432.holiday-notice-container {
5433    max-width: 1400px;
5434    margin: 0 auto;
5435    padding: 12px 20px;
5436    display: flex;
5437    align-items: center;
5438    justify-content: space-between;
5439    gap: 16px;
5440}
5441
5442.holiday-notice-content {
5443    display: flex;
5444    align-items: center;
5445    gap: 12px;
5446    flex: 1;
5447}
5448
5449.holiday-notice-icon {
5450    font-size: 20px;
5451    line-height: 1;
5452    flex-shrink: 0;
5453}
5454
5455.holiday-notice-text {
5456    color: #ffffff;
5457    font-size: 14px;
5458    font-weight: 500;
5459    line-height: 1.5;
5460    margin: 0;
5461    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
5462}
5463
5464.holiday-notice-emoji {
5465    display: inline-block;
5466    animation: bounce 2s ease-in-out infinite;
5467}
5468
5469@keyframes bounce {
5470    0%, 100% {
5471        transform: translateY(0);
5472    }
5473    50% {
5474        transform: translateY(-4px);
5475    }
5476}
5477
5478.holiday-notice-close {
5479    background: rgba(255, 255, 255, 0.2);
5480    border: 1px solid rgba(255, 255, 255, 0.3);
5481    border-radius: 6px;
5482    padding: 6px;
5483    cursor: pointer;
5484    color: #ffffff;
5485    transition: all 0.2s ease;
5486    flex-shrink: 0;
5487    display: flex;
5488    align-items: center;
5489    justify-content: center;
5490    width: 32px;
5491    height: 32px;
5492}
5493
5494.holiday-notice-close:hover {
5495    background: rgba(255, 255, 255, 0.3);
5496    transform: scale(1.05);
5497}
5498
5499.holiday-notice-close:active {
5500    transform: scale(0.95);
5501}
5502
5503.holiday-notice-close:focus {
5504    outline: 2px solid rgba(255, 255, 255, 0.5);
5505    outline-offset: 2px;
5506}
5507
5508/* Body padding adjustment when banner is visible */
5509body.has-holiday-notice {
5510    padding-top: 52px;
5511}
5512
5513/* Tablet Styles */
5514@media (max-width: 1024px) {
5515    .holiday-notice-container {
5516        padding: 10px 16px;
5517    }
5518    
5519    .holiday-notice-text {
5520        font-size: 13px;
5521    }
5522    
5523    .holiday-notice-icon {
5524        font-size: 18px;
5525    }
5526    
5527    body.has-holiday-notice {
5528        padding-top: 48px;
5529    }
5530}
5531
5532/* Mobile Styles */
5533@media (max-width: 640px) {
5534    .holiday-notice-container {
5535        padding: 10px 12px;
5536        gap: 8px;
5537    }
5538    
5539    .holiday-notice-content {
5540        gap: 8px;
5541    }
5542    
5543    .holiday-notice-text {
5544        font-size: 12px;
5545        line-height: 1.4;
5546    }
5547    
5548    .holiday-notice-icon {
5549        font-size: 16px;
5550    }
5551    
5552    .holiday-notice-close {
5553        width: 28px;
5554        height: 28px;
5555    }
5556    
5557    .holiday-notice-close svg {
5558        width: 16px;
5559        height: 16px;
5560    }
5561    
5562    body.has-holiday-notice {
5563        padding-top: 44px;
5564    }
5565}
5566
5567/* Ensure header doesn't get covered - removed z-index as it conflicts with dropdown stacking */
5568/* .header,
5569.navbar,
5570header {
5571    position: relative;
5572    z-index: 100;
5573} */
5574
5575</style>
5576        <style data-inlined="benchmark.css">
5577/**
5578 * Benchmark Section Styles
5579 * For product detail page benchmark display
5580 */
5581
5582.pd-benchmark-section {
5583    background: #fff;
5584    border-radius: 12px;
5585    padding: 24px;
5586    margin: 32px 0;
5587    box-shadow: 0 2px 8px rgba(0,0,0,0.06);
5588    border: 1px solid #e5e7eb;
5589}
5590
5591.benchmark-header {
5592    margin-bottom: 20px;
5593}
5594
5595.benchmark-title {
5596    font-size: 22px;
5597    font-weight: 700;
5598    color: #1a1a1a;
5599    margin: 0 0 12px 0;
5600    display: flex;
5601    align-items: center;
5602    gap: 8px;
5603}
5604
5605.benchmark-hint {
5606    font-size: 14px;
5607    color: #6b7280;
5608    margin: 0;
5609}
5610
5611/* Preview Badges */
5612.benchmark-preview-badges {
5613    display: flex;
5614    flex-wrap: wrap;
5615    gap: 10px;
5616    margin-top: 12px;
5617}
5618
5619.benchmark-badge {
5620    display: inline-flex;
5621    align-items: center;
5622    gap: 6px;
5623    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
5624    color: #fff;
5625    padding: 8px 16px;
5626    border-radius: 20px;
5627    font-size: 13px;
5628    font-weight: 500;
5629    box-shadow: 0 2px 6px rgba(102, 126, 234, 0.25);
5630    transition: all 0.2s ease;
5631}
5632
5633.benchmark-badge:hover {
5634    transform: translateY(-2px);
5635    box-shadow: 0 4px 12px rgba(102, 126, 234, 0.35);
5636}
5637
5638.benchmark-badge strong {
5639    font-weight: 600;
5640    opacity: 0.9;
5641}
5642
5643/* Load Button */
5644.btn-load-benchmark {
5645    display: flex;
5646    align-items: center;
5647    justify-content: center;
5648    gap: 10px;
5649    width: 100%;
5650    max-width: 420px;
5651    padding: 14px 24px;
5652    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
5653    color: #fff;
5654    border: none;
5655    border-radius: 8px;
5656    font-size: 15px;
5657    font-weight: 600;
5658    cursor: pointer;
5659    transition: all 0.3s ease;
5660    box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
5661    margin: 0 auto;
5662}
5663
5664.btn-load-benchmark:hover:not(:disabled) {
5665    transform: translateY(-2px);
5666    box-shadow: 0 6px 16px rgba(102, 126, 234, 0.4);
5667}
5668
5669.btn-load-benchmark:disabled {
5670    opacity: 0.6;
5671    cursor: not-allowed;
5672}
5673
5674.btn-load-benchmark .btn-icon {
5675    font-size: 20px;
5676}
5677
5678.btn-load-benchmark.loading .btn-icon {
5679    animation: spin 1s linear infinite;
5680}
5681
5682@keyframes spin {
5683    from { transform: rotate(0deg); }
5684    to { transform: rotate(360deg); }
5685}
5686
5687/* Benchmark Content */
5688.benchmark-content {
5689    margin-top: 24px;
5690    padding-top: 24px;
5691    border-top: 2px solid #f3f4f6;
5692}
5693
5694.benchmark-loading {
5695    text-align: center;
5696    padding: 40px 20px;
5697    color: #6b7280;
5698}
5699
5700.benchmark-loading-spinner {
5701    width: 48px;
5702    height: 48px;
5703    margin: 0 auto 16px;
5704    border: 4px solid #f3f4f6;
5705    border-top-color: #667eea;
5706    border-radius: 50%;
5707    animation: spin 1s linear infinite;
5708}
5709
5710.benchmark-error {
5711    text-align: center;
5712    padding: 32px 20px;
5713    color: #ef4444;
5714    background: #fee2e2;
5715    border-radius: 8px;
5716}
5717
5718/* Benchmark Data Display */
5719.benchmark-data-grid {
5720    display: grid;
5721    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
5722    gap: 16px;
5723    margin-bottom: 24px;
5724}
5725
5726.benchmark-card {
5727    background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%);
5728    border: 1px solid #e5e7eb;
5729    border-radius: 10px;
5730    padding: 18px;
5731    text-align: center;
5732    transition: all 0.2s ease;
5733}
5734
5735.benchmark-card:hover {
5736    transform: translateY(-3px);
5737    box-shadow: 0 6px 16px rgba(102, 126, 234, 0.15);
5738}
5739
5740.benchmark-card-label {
5741    font-size: 13px;
5742    color: #6b7280;
5743    font-weight: 500;
5744    margin-bottom: 8px;
5745    text-transform: uppercase;
5746    letter-spacing: 0.5px;
5747}
5748
5749.benchmark-card-value {
5750    font-size: 24px;
5751    font-weight: 700;
5752    color: #1a1a1a;
5753    line-height: 1.2;
5754}
5755
5756.benchmark-card-value.large {
5757    font-size: 28px;
5758}
5759
5760.benchmark-card-subtitle {
5761    font-size: 12px;
5762    color: #9ca3af;
5763    margin-top: 4px;
5764}
5765
5766/* Spec Details */
5767.benchmark-specs {
5768    background: #f9fafb;
5769    border-radius: 10px;
5770    padding: 20px;
5771    margin-top: 20px;
5772}
5773
5774.benchmark-specs-title {
5775    font-size: 16px;
5776    font-weight: 600;
5777    color: #1a1a1a;
5778    margin: 0 0 16px 0;
5779}
5780
5781.benchmark-spec-row {
5782    display: flex;
5783    justify-content: space-between;
5784    padding: 10px 0;
5785    border-bottom: 1px solid #e5e7eb;
5786}
5787
5788.benchmark-spec-row:last-child {
5789    border-bottom: none;
5790}
5791
5792.benchmark-spec-label {
5793    font-size: 14px;
5794    color: #6b7280;
5795    font-weight: 500;
5796}
5797
5798.benchmark-spec-value {
5799    font-size: 14px;
5800    color: #1a1a1a;
5801    font-weight: 600;
5802    text-align: right;
5803}
5804
5805/* Score Badges */
5806.benchmark-score-badge {
5807    display: inline-block;
5808    padding: 6px 14px;
5809    border-radius: 20px;
5810    font-size: 13px;
5811    font-weight: 600;
5812    margin-top: 8px;
5813}
5814
5815.benchmark-score-badge.excellent {
5816    background: #10b981;
5817    color: #fff;
5818}
5819
5820.benchmark-score-badge.good {
5821    background: #3b82f6;
5822    color: #fff;
5823}
5824
5825.benchmark-score-badge.average {
5826    background: #f59e0b;
5827    color: #fff;
5828}
5829
5830.benchmark-score-badge.poor {
5831    background: #ef4444;
5832    color: #fff;
5833}
5834
5835/* Attribution */
5836.benchmark-attribution {
5837    text-align: center;
5838    margin-top: 24px;
5839    padding-top: 20px;
5840    border-top: 1px solid #e5e7eb;
5841    font-size: 13px;
5842    color: #9ca3af;
5843}
5844
5845.benchmark-attribution a {
5846    color: #667eea;
5847    text-decoration: none;
5848    font-weight: 500;
5849}
5850
5851.benchmark-attribution a:hover {
5852    text-decoration: underline;
5853}
5854
5855/* Responsive */
5856@media (max-width: 768px) {
5857    .pd-benchmark-section {
5858        padding: 16px;
5859        margin: 20px 0;
5860    }
5861    
5862    .benchmark-title {
5863        font-size: 18px;
5864    }
5865    
5866    .benchmark-preview-badges {
5867        flex-direction: column;
5868        gap: 8px;
5869    }
5870    
5871    .benchmark-badge {
5872        justify-content: center;
5873    }
5874    
5875    .btn-load-benchmark {
5876        font-size: 14px;
5877        padding: 12px 20px;
5878    }
5879    
5880    .benchmark-data-grid {
5881        grid-template-columns: 1fr 1fr;
5882        gap: 12px;
5883    }
5884    
5885    .benchmark-card {
5886        padding: 14px;
5887    }
5888    
5889    .benchmark-card-value {
5890        font-size: 20px;
5891    }
5892    
5893    .benchmark-spec-row {
5894        flex-direction: column;
5895        gap: 4px;
5896    }
5897    
5898    .benchmark-spec-value {
5899        text-align: left;
5900    }
5901}
5902
5903@media (max-width: 480px) {
5904    .benchmark-data-grid {
5905        grid-template-columns: 1fr;
5906    }
5907}
5908
5909</style>
5910    <style data-inlined="product-filters.css">
5911/* =========================================================
5912   Product Filters (Desktop + Mobile Drawer)
5913   Light Only + FIX:
5914   - Desktop: f-actions selalu tampil (static bottom)
5915   - Mobile: konten filter dapat discroll
5916   ========================================================= */
5917
5918/* ========== Layout wrapper (tidak mengubah grid produk) ========== */
5919.product-archive.with-filters .pa-layout {
5920  display:flex;
5921  gap:24px;
5922  align-items:flex-start;
5923}
5924.pa-main { flex:1; min-width:0; }
5925
5926/* =========================================================
5927   DESKTOP PANEL
5928   ========================================================= */
5929.pa-filters {
5930  width:300px;
5931  background:linear-gradient(145deg,#ffffff 0%,#f8fafc 55%,#f1f5f9 100%);
5932  border:1px solid #e2e8f0;
5933  padding:18px 18px 12px; /* bottom dikurangi karena f-actions punya padding sendiri */
5934  border-radius:18px;
5935  position:sticky;
5936  top:80px;
5937  height:calc(100vh - 100px); /* tinggi stabil untuk inner scroll */
5938  font-size:14px;
5939  box-shadow:0 1px 2px rgba(0,0,0,.04),0 4px 14px rgba(15,23,42,.08);
5940  backdrop-filter:blur(3px);
5941  -webkit-backdrop-filter:blur(3px);
5942  transition:box-shadow .3s, background .4s;
5943  display:flex;
5944  flex-direction:column;
5945  overflow:hidden; /* cegah double scroll, inner yang scroll */
5946}
5947.pa-filters::before {
5948  content:"";
5949  position:absolute;
5950  inset:0 auto 0 0;
5951  width:7px;
5952  border-radius:18px 0 0 18px;
5953  background:linear-gradient(180deg,#f59e0b 0%,#ffb53a 35%,#1d6ce0 100%);
5954}
5955.pa-filters.active { box-shadow:0 8px 32px -4px rgba(15,23,42,.35); }
5956
5957/* Scroll area (desktop/mode shared) */
5958.filters-scroll {
5959  flex:1;
5960  min-height:0;          /* wajib agar overflow terbaca dalam flex container */
5961  overflow:auto;
5962  padding-right:6px;
5963  margin-right:-6px;      /* kompensasi scrollbar */
5964  scrollbar-width:thin;
5965  overscroll-behavior:contain;
5966}
5967.filters-scroll::-webkit-scrollbar { width:8px; }
5968.filters-scroll::-webkit-scrollbar-thumb {
5969  background:#cbd5e1;
5970  border-radius:20px;
5971  border:2px solid #f1f5f9;
5972}
5973.filters-scroll::-webkit-scrollbar-thumb:hover { background:#94a3b8; }
5974
5975/* Heading */
5976.pa-filters h2 {
5977  margin:0 0 14px;
5978  font-size:20px;
5979  font-weight:700;
5980  letter-spacing:.4px;
5981  line-height:1.15;
5982  background:linear-gradient(90deg,#0f172a,#1d6ce0 60%,#0f172a);
5983  -webkit-background-clip:text;
5984  color:transparent;
5985  background-size:200% 100%;
5986  animation:titleSheen 8s linear infinite;
5987}
5988@keyframes titleSheen {
5989  0%{background-position:0 0}
5990  50%{background-position:100% 0}
5991  100%{background-position:0 0}
5992}
5993
5994/* Form groups */
5995.pa-filters .f-group { margin-bottom:18px; position:relative; }
5996.pa-filters label {
5997  display:block;
5998  font-weight:600;
5999  margin:0 0 6px;
6000  font-size:13px;
6001  color:#0f172a;
6002  letter-spacing:.3px;
6003}
6004
6005/* Inputs / selects */
6006.pa-filters input[type=text],
6007.pa-filters input[type=number],
6008.pa-filters select {
6009  width:100%;
6010  padding:7px 10px;
6011  border:1px solid #cbd5e1;
6012  border-radius:8px;
6013  font-size:13px;
6014  min-height:34px;
6015  background:#ffffff;
6016  box-sizing:border-box;
6017  color:#0f172a;
6018  transition:border-color .25s, box-shadow .25s, background .25s;
6019  -webkit-appearance:none;
6020  -moz-appearance:none;
6021  appearance:none;
6022}
6023.pa-filters select {
6024  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");
6025  background-repeat:no-repeat;
6026  background-position:right 10px center;
6027  background-size:12px;
6028  padding-right:32px;
6029}
6030.pa-filters input[type=text]:hover,
6031.pa-filters input[type=number]:hover,
6032.pa-filters select:hover { border-color:#94b5ec; }
6033.pa-filters input[type=text]:focus,
6034.pa-filters input[type=number]:focus,
6035.pa-filters select:focus {
6036  outline:none;
6037  border-color:#1d6ce0;
6038  box-shadow:0 0 0 3px rgba(29,108,224,.25);
6039  background:#f8fbff;
6040}
6041.pa-filters .range { display:flex; gap:10px; }
6042.pa-filters .range input { flex:1; }
6043.pa-filters .hint { font-size:11px; color:#64748b; margin-top:6px; }
6044
6045/* Spec blocks */
6046.f-specs { margin-top:4px; }
6047.spec-attr {
6048  margin:0 0 14px;
6049  padding:12px 12px 10px;
6050  border:1px solid #e2e8f0;
6051  border-radius:14px;
6052  background:linear-gradient(130deg,#ffffff 0%,#f7faff 85%);
6053  position:relative;
6054  transition:border-color .3s, box-shadow .3s, background .4s;
6055}
6056.spec-attr.collapsed { padding-bottom:8px; }
6057.spec-attr.has-selected {
6058  border-color:#1d6ce0;
6059  box-shadow:0 0 0 2px rgba(29,108,224,.15),0 2px 8px -2px rgba(29,108,224,.35);
6060  background:linear-gradient(135deg,#ffffff 0%,#e9f2ff 100%);
6061}
6062.spec-attr-title {
6063  font-weight:600;
6064  margin:0 0 8px;
6065  font-size:13px;
6066  display:flex;
6067  justify-content:space-between;
6068  align-items:center;
6069  cursor:pointer;
6070  color:#0f172a;
6071  user-select:none;
6072}
6073.spec-attr-title .chevron {
6074  font-size:10px;
6075  transition:transform .25s, opacity .25s;
6076  opacity:.65;
6077}
6078.spec-attr.collapsed .spec-attr-title .chevron {
6079  transform:rotate(-90deg);
6080  opacity:.5;
6081}
6082
6083/* Spec options */
6084.spec-attr-options { display:flex; flex-wrap:wrap; gap:6px; }
6085.spec-attr-options label {
6086  display:inline-flex;
6087  align-items:center;
6088  gap:4px;
6089  font-weight:500;
6090  border:1px solid #d4dde7;
6091  padding:6px 9px;
6092  border-radius:30px;
6093  cursor:pointer;
6094  background:#f3f6fa;
6095  font-size:12px;
6096  line-height:1.1;
6097  transition:background .25s, border-color .25s, color .25s, box-shadow .25s;
6098  position:relative;
6099}
6100.spec-attr-options label:hover { background:#e8f1ff; border-color:#b1cff3; }
6101.spec-attr-options input {
6102  margin:0;
6103  position:absolute;
6104  inset:0;
6105  opacity:0;
6106  cursor:pointer;
6107}
6108.spec-attr-options label:has(input:checked) {
6109  background:linear-gradient(135deg,#1d6ce0 0%,#155ab8 100%);
6110  border-color:#1d6ce0;
6111  color:#ffffff;
6112  box-shadow:0 2px 6px -1px rgba(29,108,224,.55);
6113}
6114.spec-attr-options label:has(input:checked)::after {
6115  content:"";
6116  position:absolute;
6117  right:6px;
6118  top:6px;
6119  width:6px;
6120  height:6px;
6121  background:#fff;
6122  border-radius:50%;
6123  box-shadow:0 0 0 4px rgba(255,255,255,.25),0 0 0 7px rgba(29,108,224,.5);
6124  animation:pulseDot 1.9s ease-out infinite;
6125}
6126@keyframes pulseDot {
6127  0%{transform:scale(.8);opacity:.9}
6128  60%{transform:scale(1.4);opacity:.15}
6129  100%{transform:scale(.8);opacity:.9}
6130}
6131
6132/* Badges */
6133.pa-filter-badges { margin:0 0 18px; display:flex; flex-wrap:wrap; gap:6px; }
6134.pa-filter-badge {
6135  display:inline-flex;
6136  gap:6px;
6137  align-items:center;
6138  background:linear-gradient(135deg,#eef4ff 0%,#e0edff 100%);
6139  border:1px solid #b4cff3;
6140  padding:5px 10px 5px 12px;
6141  border-radius:22px;
6142  margin:2px 4px 0 0;
6143  font-size:11px;
6144  font-weight:500;
6145  line-height:1;
6146  color:#0f172a;
6147  position:relative;
6148  overflow:hidden;
6149}
6150.pa-filter-badge::before {
6151  content:"";
6152  position:absolute;
6153  inset:0;
6154  background:linear-gradient(90deg,rgba(255,255,255,0),rgba(255,255,255,.35),rgba(255,255,255,0));
6155  transform:translateX(-100%);
6156  animation:badgeSheen 6s linear infinite;
6157}
6158@keyframes badgeSheen {
6159  0%{transform:translateX(-100%)}
6160  55%{transform:translateX(120%)}
6161  100%{transform:translateX(120%)}
6162}
6163.pa-filter-badge a.clear {
6164  text-decoration:none;
6165  color:#c02828;
6166  font-weight:700;
6167  padding:0 3px;
6168  display:inline-flex;
6169  align-items:center;
6170  justify-content:center;
6171  border-radius:50%;
6172  line-height:1;
6173  transition:background .25s, color .25s;
6174}
6175.pa-filter-badge a.clear:hover { background:#ffeded; color:#a40000; }
6176.pa-btn-filter { width: 100%;}
6177
6178/* =========================================================
6179   ACTIONS (DESKTOP) - STATIC BOTTOM
6180   ========================================================= */
6181.f-actions {
6182  margin-top:10px;
6183  display:flex;
6184  gap:12px;
6185  flex-wrap:wrap;
6186  padding:16px 0 4px;
6187  border-top:1px solid #e2e8f0;
6188  background:linear-gradient(180deg,rgba(255,255,255,0) 0%,#f1f5f9 70%);
6189  flex-shrink:0;
6190}
6191.f-actions button {
6192  background:linear-gradient(135deg,#1d6ce0 0%,#155ab8 100%);
6193  color:#fff;
6194  border:none;
6195  padding:10px 20px;
6196  border-radius:12px;
6197  cursor:pointer;
6198  font-size:14px;
6199  font-weight:600;
6200  letter-spacing:.3px;
6201  display:inline-flex;
6202  align-items:center;
6203  gap:6px;
6204  box-shadow:0 4px 14px -2px rgba(29,108,224,.45);
6205  transition:transform .25s, box-shadow .3s;
6206}
6207.f-actions button:hover { transform:translateY(-3px); box-shadow:0 8px 26px -4px rgba(29,108,224,.6); }
6208.f-actions button:active { transform:translateY(-1px) scale(.98); box-shadow:0 4px 14px -4px rgba(29,108,224,.55); }
6209.f-actions .reset {
6210  font-size:12px;
6211  text-decoration:none;
6212  color:#d92d2d;
6213  align-self:center;
6214  font-weight:600;
6215  padding:4px 6px;
6216  border-radius:8px;
6217  transition:background .25s, color .25s;
6218}
6219.f-actions .reset:hover { background:#ffe6e6; color:#b80000; text-decoration:underline; }
6220.pa-filters .close-drawer { display: none; }
6221
6222/* Form wrapper: flex container agar inner .filters-scroll bisa scroll di desktop */
6223.filter-form {
6224  flex: 1;
6225  min-height: 0;
6226  display: flex;
6227  flex-direction: column;
6228  overflow: hidden;
6229}
6230
6231/* =========================================================
6232   FILTER TOGGLE (MOBILE TRIGGER)
6233   ========================================================= */
6234.filter-toggle {
6235  display:none;
6236  margin:0 0 14px;
6237  background:linear-gradient(135deg,#1d6ce0 0%,#155ab8 100%);
6238  color:#fff;
6239  border:none;
6240  padding:11px 18px;
6241  font-size:14px;
6242  font-weight:600;
6243  border-radius:14px;
6244  cursor:pointer;
6245  letter-spacing:.4px;
6246  box-shadow:0 4px 14px -2px rgba(29,108,224,.55);
6247  position:relative;
6248  z-index:100;
6249  transition:box-shadow .3s, transform .25s;
6250}
6251.filter-toggle:hover { box-shadow:0 8px 26px -4px rgba(29,108,224,.6); transform:translateY(-2px); }
6252.filter-toggle:active { transform:translateY(0); box-shadow:0 4px 14px -4px rgba(29,108,224,.55); }
6253.filter-toggle .count-badge {
6254  display:inline-block;
6255  background:linear-gradient(135deg,#f59e0b 0%,#ffb43d 55%,#f59e0b 100%);
6256  color:#1d2b44;
6257  font-size:11px;
6258  font-weight:700;
6259  padding:3px 7px 4px;
6260  border-radius:30px;
6261  margin-left:8px;
6262  box-shadow:0 0 0 2px #ffffff,0 0 0 4px #1d6ce0;
6263}
6264
6265/* Overlay (mobile) */
6266.pa-filters-overlay { display:none; }
6267
6268/* =========================================================
6269   MOBILE DRAWER
6270   ========================================================= */
6271@media (max-width:768px){
6272  .product-archive.with-filters .pa-layout { flex-direction:column; gap:12px; }
6273  .filter-toggle { display:inline-flex; align-items:center; gap:6px; }
6274
6275  .pa-filters-overlay {
6276    position:fixed;
6277    inset:0;
6278    background:rgba(0,20,45,.55);
6279    z-index:998;
6280    opacity:0;
6281    transition:opacity .35s;
6282    display:block;
6283    pointer-events:none;
6284    backdrop-filter:blur(2px);
6285  }
6286  .pa-filters-overlay.active { opacity:1; pointer-events:auto; }
6287
6288  .pa-filters {
6289    position:fixed;
6290    top:auto;
6291    left:0; right:0; bottom:0;
6292    max-height:92vh;
6293    height:auto;
6294    width:100%;
6295    border-radius:30px 30px 0 0;
6296    border:1px solid #dbe4ef;
6297    padding:26px 22px 0;
6298    z-index:10000; /* di atas bottom-nav (9998) */
6299    transform:translateY(102%);
6300    transition:transform .55s cubic-bezier(.68,-0.35,.25,1);
6301    box-shadow:0 -4px 40px -6px rgba(15,23,42,.45);
6302    overflow:hidden; /* outer hidden, inner scroll */
6303  }
6304  .pa-filters.active { transform:translateY(0); }
6305
6306  /* Drag handle indicator */
6307  .pa-filters::after {
6308    content:"";
6309    display:block;
6310    position:absolute;
6311    top:10px;
6312    left:50%;
6313    transform:translateX(-50%);
6314    width:36px;
6315    height:4px;
6316    background:#cbd5e1;
6317    border-radius:4px;
6318  }
6319  body.filter-open { overflow:hidden; touch-action:none; }
6320
6321  /* Restore mobile-friendly input sizes (prevent iOS zoom on focus) */
6322  .pa-filters input[type=text],
6323  .pa-filters input[type=number],
6324  .pa-filters select {
6325    font-size:16px;
6326    min-height:44px;
6327    padding:10px 12px;
6328    border-radius:10px;
6329  }
6330  .pa-filters select { padding-right:32px; }
6331
6332  .filters-scroll {
6333    flex:1;
6334    overflow:auto;
6335    padding:0 0 120px; /* ruang di atas bar actions */
6336    margin:0;
6337    -webkit-overflow-scrolling:touch;
6338  }
6339
6340  .pa-filters h2 { font-size:19px; margin-bottom:10px; }
6341
6342  .pa-filters .close-drawer {
6343    display: block;
6344    position:absolute;
6345    right:14px; top:12px;
6346    background:linear-gradient(135deg,#dcce77 0%,#dcce77 100%);
6347    color:#fff;
6348    padding:7px 14px;
6349    border:none;
6350    border-radius:10px;
6351    font-size:12px;
6352    font-weight:600;
6353    cursor:pointer;
6354    box-shadow:0 3px 10px -2px rgba(220,38,38,.55);
6355    transition:transform .25s, box-shadow .3s;
6356    z-index: 99;
6357  }
6358  .pa-filters .close-drawer:hover { transform:translateY(-2px); box-shadow:0 6px 18px -4px rgba(220,38,38,.6); }
6359
6360  .spec-attr { border-radius:18px; }
6361  .spec-attr-options label { font-size:11px; padding:5px 9px; }
6362
6363  /* Actions fixed di viewport bawah */
6364  .f-actions {
6365    position:fixed;
6366    left:0; right:0; bottom:0;
6367    margin:0;
6368    border-top:1px solid #d6dee8;
6369    padding:14px 16px calc(16px + env(safe-area-inset-bottom));
6370    background:#ffffffea;
6371    backdrop-filter:blur(6px);
6372    box-shadow:0 -4px 22px -6px rgba(15,23,42,.25);
6373    justify-content:space-between;
6374    z-index:10001; /* di atas pa-filters (10000) dan bottom-nav (9998) */
6375  }
6376
6377  /* Sembunyikan bottom-nav saat filter drawer terbuka */
6378  body.filter-open .bottom-nav {
6379    display: none !important;
6380  }
6381  .f-actions button {
6382    flex:1 1 auto;
6383    justify-content:center;
6384    padding:14px 14px;
6385    font-size:15px;
6386    border-radius:14px;
6387  }
6388  .f-actions .reset {
6389    order:-1;
6390    font-size:12px;
6391    padding:2px 8px;
6392  }
6393}
6394
6395/* Scrollbar (outer panel desktop only kept for compatibility) */
6396.pa-filters::-webkit-scrollbar { width:0; } /* disembunyikan karena inner yang scroll */
6397
6398/* Utilities */
6399.hidden { display:none !important; }
6400.gradient-text {
6401  background:linear-gradient(90deg,#1d6ce0,#155ab8,#1d6ce0);
6402  -webkit-background-clip:text;
6403  color:transparent;
6404  animation:titleSheen 10s linear infinite;
6405}
6406.glow-focus:focus {
6407  outline:none !important;
6408  box-shadow:0 0 0 3px rgba(29,108,224,.4) !important;
6409  border-color:#1d6ce0 !important;
6410}
6411
6412/* Simple fade (opsional) */
6413@keyframes fadeIn {
6414  from { opacity:0; transform:translateY(8px); }
6415  to { opacity:1; transform:translateY(0); }
6416}
6417</style>
6418  <style data-inlined="mega-menu-tiles.css">
6419/* Tile Mega Menu — Modern E-commerce Enhanced (2025) */
6420
6421/* Design Tokens */
6422:root {
6423  --mm2-bg: #ffffff;
6424  --mm2-surface: #ffffff;
6425  --mm2-surface-elevated: rgba(255,255,255,0.98);
6426  --mm2-muted: #f8fafc;
6427  --mm2-border: #e2e8f0;
6428  --mm2-border-strong: #cbd5e1;
6429  --mm2-accent: #6366f1;
6430  --mm2-accent-hover: #4f46e5;
6431  --mm2-accent-10: rgba(99,102,241,.10);
6432  --mm2-accent-20: rgba(99,102,241,.20);
6433  --mm2-fg: #0f172a;
6434  --mm2-fg-muted: #64748b;
6435  --mm2-fg-light: #94a3b8;
6436  
6437  /* Enhanced Shadows - Multi-layer elevation */
6438  --mm2-shadow-sm: 0 2px 8px rgba(15,23,42,.06), 0 1px 2px rgba(15,23,42,.04);
6439  --mm2-shadow-md: 0 8px 24px rgba(15,23,42,.10), 0 2px 8px rgba(15,23,42,.06);
6440  --mm2-shadow-lg: 0 20px 60px rgba(15,23,42,.14), 0 8px 24px rgba(15,23,42,.08);
6441  --mm2-shadow-xl: 0 32px 80px rgba(15,23,42,.18), 0 12px 32px rgba(15,23,42,.10);
6442  --mm2-shadow-hover: 0 16px 40px rgba(15,23,42,.16), 0 6px 16px rgba(15,23,42,.08);
6443  
6444  /* Border Radius */
6445  --mm2-radius-xl: 20px;
6446  --mm2-radius-lg: 16px;
6447  --mm2-radius-md: 12px;
6448  --mm2-radius-sm: 10px;
6449  
6450  --mm2-z: 60;
6451  
6452  /* Animation Timing */
6453  --mm2-transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
6454  --mm2-transition-base: 250ms cubic-bezier(0.4, 0, 0.2, 1);
6455  --mm2-transition-slow: 350ms cubic-bezier(0.4, 0, 0.2, 1);
6456}
6457
6458.navbar { position: relative; z-index: var(--mm2-z); }
6459
6460/* Trigger Button - Enhanced */
6461.mm2-trigger {
6462  display: inline-flex; align-items: center; gap: 8px;
6463  padding: 10px 14px; border-radius: var(--mm2-radius-md);
6464  border: 1.5px solid var(--mm2-border);
6465  background: var(--mm2-surface); color: var(--mm2-fg);
6466  cursor: pointer; user-select: none;
6467  transition: all var(--mm2-transition-fast);
6468  text-decoration: none;
6469  font-weight: 600;
6470  box-shadow: var(--mm2-shadow-sm);
6471}
6472.mm2-trigger:hover { 
6473  border-color: var(--mm2-accent); 
6474  background: var(--mm2-muted);
6475  box-shadow: var(--mm2-shadow-md);
6476  transform: translateY(-1px);
6477}
6478.mm2-trigger:active { transform: translateY(0); }
6479.mm2-trigger svg {max-width: unset !important; transition: transform var(--mm2-transition-fast); }
6480.mm2-trigger:hover svg { transform: scale(1.05); }
6481
6482/* Backdrop - Enhanced Glassmorphism */
6483.mm2-backdrop {
6484  position: fixed; 
6485  inset: 0;
6486  top: 0;
6487  left: 0;
6488  right: 0;
6489  bottom: 0;
6490  background: rgba(15,23,42,.40);
6491  backdrop-filter: blur(8px) saturate(1.2);
6492  -webkit-backdrop-filter: blur(8px) saturate(1.2);
6493  opacity: 0; 
6494  pointer-events: none;
6495  z-index: 499;
6496  transition: opacity var(--mm2-transition-base);
6497}
6498.mm2-open .mm2-backdrop { opacity: 1; pointer-events: auto; }
6499
6500/* Panel - Glassmorphism Enhanced */
6501.mm2-panel {
6502  position: fixed;
6503  left: 50%;
6504  top: 120px;
6505  transform: translateX(-50%) translateY(-16px);
6506  width: min(1200px, 95vw);
6507  background: var(--mm2-surface-elevated);
6508  backdrop-filter: blur(20px) saturate(1.8);
6509  -webkit-backdrop-filter: blur(20px) saturate(1.8);
6510  border: 1.5px solid var(--mm2-border-strong);
6511  border-radius: var(--mm2-radius-xl);
6512  box-shadow: var(--mm2-shadow-xl);
6513  display: none;
6514  overflow: hidden;
6515  opacity: 0;
6516  z-index: 500;
6517  transition: transform var(--mm2-transition-slow), opacity var(--mm2-transition-base);
6518}
6519.mm2-open .mm2-panel { 
6520  display: block; 
6521  opacity: 1; 
6522  transform: translateX(-50%) translateY(0);
6523}
6524
6525/* View switcher */
6526.mm2-view { display: none; }
6527.mm2-open .mm2-view.is-active { display: block; }
6528
6529/* Top grid (level-1 tiles) - Enhanced */
6530.mm2-top {
6531  padding: 24px;
6532  max-height: 75vh; overflow: auto;
6533}
6534.mm2-grid {
6535  display: grid;
6536  grid-template-columns: repeat(4, minmax(0,1fr));
6537  gap: 14px;
6538}
6539@media (max-width: 1200px) { .mm2-grid { grid-template-columns: repeat(3, minmax(0,1fr)); } }
6540@media (max-width: 960px)  { .mm2-grid { grid-template-columns: repeat(2, minmax(0,1fr)); gap: 12px; } }
6541
6542/* Stagger animation for cards */
6543.mm2-open .mm2-card {
6544  animation: slideInUp 0.3s cubic-bezier(0.4, 0, 0.2, 1) backwards;
6545}
6546.mm2-open .mm2-card:nth-child(1)  { animation-delay: 0.02s; }
6547.mm2-open .mm2-card:nth-child(2)  { animation-delay: 0.04s; }
6548.mm2-open .mm2-card:nth-child(3)  { animation-delay: 0.06s; }
6549.mm2-open .mm2-card:nth-child(4)  { animation-delay: 0.08s; }
6550.mm2-open .mm2-card:nth-child(5)  { animation-delay: 0.10s; }
6551.mm2-open .mm2-card:nth-child(6)  { animation-delay: 0.12s; }
6552.mm2-open .mm2-card:nth-child(7)  { animation-delay: 0.14s; }
6553.mm2-open .mm2-card:nth-child(8)  { animation-delay: 0.16s; }
6554.mm2-open .mm2-card:nth-child(9)  { animation-delay: 0.18s; }
6555.mm2-open .mm2-card:nth-child(10) { animation-delay: 0.20s; }
6556.mm2-open .mm2-card:nth-child(11) { animation-delay: 0.22s; }
6557.mm2-open .mm2-card:nth-child(12) { animation-delay: 0.24s; }
6558.mm2-open .mm2-card:nth-child(n+13) { animation-delay: 0.26s; }
6559
6560@keyframes slideInUp {
6561  from {
6562    opacity: 0;
6563    transform: translateY(12px);
6564  }
6565  to {
6566    opacity: 1;
6567    transform: translateY(0);
6568  }
6569}
6570
6571.mm2-card {
6572  position: relative;
6573  display: flex; gap: 14px; align-items: center;
6574  padding: 16px; border-radius: var(--mm2-radius-md);
6575  background: var(--mm2-surface);
6576  border: 1.5px solid var(--mm2-border);
6577  text-decoration: none; color: var(--mm2-fg);
6578  transition: all var(--mm2-transition-fast);
6579  min-height: 72px;
6580  box-shadow: var(--mm2-shadow-sm);
6581}
6582.mm2-card:hover { 
6583  border-color: var(--mm2-accent);
6584  background: linear-gradient(135deg, rgba(255,255,255,1), rgba(248,250,252,1));
6585  box-shadow: var(--mm2-shadow-hover);
6586  transform: translateY(-3px);
6587}
6588.mm2-card:active { transform: translateY(-1px); }
6589
6590/* Icon with category-specific gradients */
6591.mm2-card .mm2-ico {
6592  width: 48px; height: 48px; border-radius: 12px;
6593  display: inline-flex; align-items: center; justify-content: center;
6594  background: linear-gradient(135deg, #e0e7ff, #fff);
6595  border: 1.5px solid #c7d2fe;
6596  color: #4f46e5;
6597  flex-shrink: 0;
6598  transition: all var(--mm2-transition-fast);
6599  box-shadow: 0 2px 8px rgba(99,102,241,0.1);
6600}
6601.mm2-card:hover .mm2-ico {
6602  transform: scale(1.08) rotate(2deg);
6603  box-shadow: 0 4px 16px rgba(99,102,241,0.2);
6604}
6605.mm2-card .mm2-ico svg { transition: transform var(--mm2-transition-fast); }
6606.mm2-card:hover .mm2-ico svg { transform: scale(1.1); }
6607
6608.mm2-card .mm2-title { 
6609  font-weight: 700; 
6610  font-size: 15px;
6611  letter-spacing: -0.01em;
6612  line-height: 1.3;
6613}
6614.mm2-card .mm2-sub { 
6615  color: var(--mm2-fg-muted); 
6616  font-size: 12.5px; 
6617  margin-top: 3px;
6618  display: flex;
6619  align-items: center;
6620  gap: 4px;
6621  white-space: nowrap;
6622  overflow: hidden;
6623  text-overflow: ellipsis;
6624}
6625.mm2-card .mm2-count {
6626  display: inline-flex;
6627  padding: 2px 8px;
6628  border-radius: 6px;
6629  background: var(--mm2-accent-10);
6630  color: var(--mm2-accent);
6631  font-weight: 600;
6632  font-size: 11px;
6633  margin-left: 4px;
6634}
6635
6636/* Detail view (level-2 + level-3) - Enhanced */
6637.mm2-detail {
6638  position: relative;
6639  padding: 16px 24px 24px;
6640  max-height: 70vh; overflow: auto;
6641}
6642.mm2-detail-header {
6643  display: flex; align-items: center; gap: 14px;
6644  padding: 10px 0 16px; border-bottom: 2px solid var(--mm2-muted); margin-bottom: 16px;
6645}
6646.mm2-back {
6647  display: inline-flex; align-items: center; justify-content: center;
6648  width: 40px; height: 40px; border-radius: 12px;
6649  border: 1.5px solid var(--mm2-border); background: #fff; color: var(--mm2-fg);
6650  cursor: pointer;
6651  transition: all var(--mm2-transition-fast);
6652  box-shadow: var(--mm2-shadow-sm);
6653}
6654.mm2-back:hover {
6655  border-color: var(--mm2-accent);
6656  background: var(--mm2-muted);
6657  transform: translateX(-3px);
6658  box-shadow: var(--mm2-shadow-md);
6659}
6660.mm2-detail-title { 
6661  font-weight: 800; 
6662  font-size: 18px;
6663  letter-spacing: -0.02em;
6664}
6665
6666/* Two-pane layout (desktop) - Enhanced */
6667.mm2-detail-layout {
6668  display: grid;
6669  grid-template-columns: 280px 1fr;
6670  gap: 20px;
6671}
6672
6673/* Left: subnav (level-2 list) - Enhanced */
6674.mm2-subnav {
6675  position: sticky; top: 8px;
6676  align-self: start;
6677  max-height: calc(70vh - 56px);
6678  overflow: auto;
6679  padding-right: 6px;
6680}
6681.mm2-subnav::-webkit-scrollbar { width: 8px; height: 8px; }
6682.mm2-subnav::-webkit-scrollbar-track { background: transparent; }
6683.mm2-subnav::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 8px; }
6684.mm2-subnav::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
6685
6686.mm2-subitem {
6687  display: flex; align-items: center; justify-content: flex-start; gap: 10px;
6688  width: 100%;
6689  padding: 12px 14px;
6690  border-radius: 12px;
6691  border: 1.5px solid transparent;
6692  background: transparent;
6693  color: var(--mm2-fg);
6694  cursor: pointer;
6695  text-align: left;
6696  transition: all var(--mm2-transition-fast);
6697  margin-bottom: 6px;
6698  font-weight: 600;
6699  font-size: 14px;
6700  position: relative;
6701}
6702.mm2-subitem::before {
6703  content: '';
6704  position: absolute;
6705  left: 0;
6706  top: 50%;
6707  transform: translateY(-50%);
6708  width: 3px;
6709  height: 0;
6710  background: var(--mm2-accent);
6711  border-radius: 0 2px 2px 0;
6712  transition: height var(--mm2-transition-fast);
6713}
6714.mm2-subitem:hover { 
6715  background: var(--mm2-muted);
6716  color: var(--mm2-accent-hover);
6717}
6718.mm2-subitem.active {
6719  background: linear-gradient(135deg, rgba(99,102,241,0.08), rgba(99,102,241,0.04));
6720  border-color: var(--mm2-accent-20);
6721  color: var(--mm2-accent-hover);
6722  box-shadow: inset 0 0 0 1px rgba(99,102,241,0.1);
6723}
6724.mm2-subitem.active::before {
6725  height: 24px;
6726}
6727
6728/* Right: subcontent (level-3 grid) - Enhanced */
6729.mm2-subcontent { min-height: 240px; }
6730.mm2-links {
6731  display: grid; gap: 12px 14px;
6732  grid-template-columns: repeat(3, minmax(0,1fr));
6733}
6734@media (max-width: 1200px) { .mm2-links { grid-template-columns: repeat(2, minmax(0,1fr)); } }
6735
6736.mm2-link {
6737  display: flex; align-items: center; gap: 12px;
6738  padding: 14px; border: 1.5px solid var(--mm2-border); border-radius: var(--mm2-radius-md);
6739  background: var(--mm2-surface); color: var(--mm2-fg); text-decoration: none;
6740  min-height: 52px;
6741  font-weight: 500;
6742  font-size: 14px;
6743  transition: all var(--mm2-transition-fast);
6744  box-shadow: var(--mm2-shadow-sm);
6745  position: relative;
6746  justify-content: space-between;
6747}
6748.mm2-link:hover { 
6749  background: var(--mm2-muted); 
6750  border-color: var(--mm2-accent);
6751  transform: translateX(4px);
6752  box-shadow: var(--mm2-shadow-md);
6753}
6754.mm2-link-text {
6755  flex: 1;
6756}
6757
6758/* Product Thumbnails */
6759.mm2-thumbs {
6760  display: flex;
6761  gap: 6px;
6762  align-items: center;
6763  margin-left: auto;
6764  flex-shrink: 0;
6765}
6766.mm2-thumb {
6767  width: 36px;
6768  height: 36px;
6769  border-radius: 8px;
6770  object-fit: cover;
6771  border: 1.5px solid var(--mm2-border);
6772  box-shadow: 0 2px 6px rgba(0,0,0,0.08);
6773  transition: all var(--mm2-transition-fast);
6774  background: var(--mm2-muted);
6775}
6776.mm2-link:hover .mm2-thumb {
6777  border-color: var(--mm2-accent);
6778  box-shadow: 0 4px 10px rgba(99,102,241,0.15);
6779  transform: scale(1.08);
6780}
6781
6782/* Thumbnail Skeleton Loader */
6783.mm2-thumb-skeleton {
6784  width: 36px;
6785  height: 36px;
6786  border-radius: 8px;
6787  background: linear-gradient(90deg, #f1f5f9 25%, #e2e8f0 50%, #f1f5f9 75%);
6788  background-size: 200% 100%;
6789  animation: shimmer 1.5s infinite;
6790  border: 1.5px solid var(--mm2-border);
6791}
6792
6793.mm2-badge {
6794  font-size: 11px; padding: 3px 10px; margin-left: auto;
6795  border-radius: 12px; background: var(--mm2-accent-10); color: var(--mm2-accent);
6796  font-weight: 700;
6797  border: 1px solid var(--mm2-accent-20);
6798}
6799
6800/* "See all" highlight card - Enhanced */
6801.mm2-seeall {
6802  border: 2px dashed var(--mm2-accent);
6803  background: linear-gradient(135deg, rgba(99,102,241,.08), rgba(99,102,241,.02));
6804  font-weight: 700;
6805}
6806.mm2-seeall:hover {
6807  background: linear-gradient(135deg, rgba(99,102,241,.14), rgba(99,102,241,.06));
6808  border-style: solid;
6809  transform: translateX(6px) scale(1.02);
6810}
6811
6812/* Mobile panel styles - Enhanced with drag handle & better touch targets */
6813@media (max-width: 960px) {
6814  .mm2-panel {
6815    position: fixed;
6816    left: 0;
6817    right: 0;
6818    bottom: 0;
6819    top: auto;
6820    width: 100%;
6821    max-height: 88vh;
6822    transform: translateX(0) translateY(100%);
6823    border-radius: var(--mm2-radius-xl) var(--mm2-radius-xl) 0 0;
6824    border-bottom: none;
6825  }
6826  .mm2-open .mm2-panel {
6827    transform: translateX(0) translateY(0);
6827 z-index: 9999;
6828  }
6829  
6830  /* Enhanced backdrop for mobile */
6831  .mm2-backdrop {
6832    backdrop-filter: blur(6px) saturate(1.2);
6833    -webkit-backdrop-filter: blur(6px) saturate(1.2);
6834  }
6835  
6836  /* Mobile close button - Soft glassmorphism */
6837  .mm2-mobile-close {
6838    position: absolute;
6839    top: 4px;
6840    right: 8px;
6841    width: 28px;
6842    height: 28px;
6843    border-radius: 50%;
6844    border: 1px solid rgba(226, 232, 240, 0.6);
6845    background: rgba(255, 255, 255, 0.85);
6846    backdrop-filter: blur(12px) saturate(1.8);
6847    -webkit-backdrop-filter: blur(12px) saturate(1.8);
6848    color: var(--mm2-fg-muted);
6849    display: inline-flex;
6850    align-items: center;
6851    justify-content: center;
6852    cursor: pointer;
6853    transition: all var(--mm2-transition-fast);
6854    box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08);
6855    z-index: 20;
6856  }
6857  .mm2-mobile-close:hover {
6858    background: rgba(254, 242, 242, 0.95);
6859    border-color: rgba(252, 165, 165, 0.8);
6860    color: #dcce77;
6861    box-shadow: 0 4px 12px rgba(220, 38, 38, 0.15);
6862  }
6863  .mm2-mobile-close:active {
6864    transform: scale(0.92);
6865  }
6866  
6867  /* Drag handle indicator */
6868  .mm2-top::before,
6869  .mm2-detail::before {
6870    content: '';
6871    position: sticky;
6872    top: 0;
6873    left: 50%;
6874    transform: translateX(-50%);
6875    width: 40px;
6876    height: 4px;
6877    background: var(--mm2-border-strong);
6878    border-radius: 999px;
6879    margin: 12px auto 20px;
6880    display: block;
6881    z-index: 10;
6882    transition: background var(--mm2-transition-fast);
6883  }
6884  .mm2-panel:active .mm2-top::before,
6885  .mm2-panel:active .mm2-detail::before {
6886    background: #94a3b8;
6887  }
6888  
6889  .mm2-top {
6890    padding: 10px;  
6891  }
6892  
6893  .mm2-top, .mm2-detail { 
6894    max-height: min(82vh, 720px);
6895    padding-top: 8px;
6896  }
6897
6898  /* Two-pane collapses: subnav becomes GRID (no horizontal scroll) */
6899  .mm2-detail-layout { display: block; }
6900  .mm2-subnav {
6901    position: static; max-height: none; overflow: visible;
6902    display: grid; gap: 10px; padding: 10px 4px 16px; margin-bottom: 16px;
6903    grid-template-columns: repeat(2, minmax(0,1fr));
6904    border-bottom: 2px solid var(--mm2-muted);
6905  }
6906  .mm2-subitem {
6907    margin: 0; width: 100%; text-align: center;
6908    border-radius: 14px;
6909    background: #fff; border: 1.5px solid var(--mm2-border);
6910    white-space: normal; line-height: 1.3; min-height: 48px;
6911    justify-content: center;
6912    padding: 12px 10px;
6913    font-size: 13.5px;
6914    box-shadow: var(--mm2-shadow-sm);
6915  }
6916  .mm2-subitem::before { display: none; }
6917  .mm2-subitem:hover {
6918    transform: scale(1.02);
6919  }
6920  .mm2-subitem.active {
6921    background: linear-gradient(135deg, var(--mm2-accent), var(--mm2-accent-hover));
6922    border-color: var(--mm2-accent-hover);
6923    color: white;
6924    box-shadow: 0 4px 12px rgba(99,102,241,0.3);
6925    font-weight: 700;
6926  }
6927
6928  /* Level-3 grid tetap 2 kolom dengan spacing lebih baik */
6929  .mm2-links { 
6930    grid-template-columns: repeat(2, minmax(0,1fr));
6931    gap: 10px;
6932  }
6933  
6934  /* Mobile: hide thumbnails untuk save space */
6935  .mm2-thumbs {
6936    display: none;
6937  }
6938  
6939  /* Larger touch targets for mobile */
6940  .mm2-card {
6941    min-height: 80px;
6942    padding: 18px 14px;
6943  }
6944  .mm2-card .mm2-ico {
6945    width: 50px;
6946    height: 50px;
6947  }
6948  .mm2-card .mm2-title {
6949    font-size: 14px;
6950  }
6951  .mm2-card .mm2-sub {
6952    font-size: 11.5px;
6953  }
6954  .mm2-link {
6955    min-height: 56px;
6956    padding: 16px;
6957  }
6958  .mm2-back {
6959    width: 44px;
6960    height: 44px;
6961  }
6962  
6963  /* Grid spacing */
6964  .mm2-grid {
6965    gap: 10px;
6966  }
6967}
6968</style>
6969  <style data-inlined="product-compare.css">
6970/* Product Compare Feature Styles */
6971
6972/* Compare Button in Product Detail - Modern Chip Style */
6973.pd-compare-section {
6974  margin: 0.5rem 0;
6975  display: flex;
6976  align-items: center;
6977  gap: 0.5rem;
6978}
6979
6980.pd-compare-btn {
6981  display: inline-flex;
6982  align-items: center;
6983  gap: 0.4rem;
6984  padding: 0.5rem 0.85rem;
6985  border: 1px solid #cbd5e1;
6986  border-radius: 20px;
6987  background: #fff;
6988  color: #64748b;
6989  font-weight: 600;
6990  font-size: 0.8rem;
6991  cursor: pointer;
6992  transition: all 0.2s ease;
6993  position: relative;
6994  white-space: nowrap;
6995}
6996
6997.pd-compare-btn:hover {
6998  border-color: #3b82f6;
6999  color: #3b82f6;
7000  background: #f0f9ff;
7001  transform: translateY(-1px);
7002  box-shadow: 0 2px 6px rgba(59, 130, 246, 0.15);
7003}
7004
7005.pd-compare-btn.added {
7006  border-color: #16a34a;
7007  color: #16a34a;
7008  background: #f0fdf4;
7009}
7010
7011.pd-compare-btn .compare-remove {
7012  display: none;
7013}
7014
7015.pd-compare-btn.added .compare-text {
7016  display: none;
7017}
7018
7019.pd-compare-btn.added .compare-remove {
7020  display: flex !important;
7021  align-items: center;
7022  gap: 0.25rem;
7023  font-weight: 600;
7024  font-size: 0.9rem;
7025  color: #dcce77 !important;
7026  opacity: 1 !important;
7027  visibility: visible !important;
7028  z-index: 1;
7029  position: relative;
7030}
7031
7032.pd-compare-btn.added .compare-remove[hidden] {
7033  display: flex !important;
7034}
7035
7036/* Force visibility for added state */
7037.pd-compare-btn.added .compare-remove {
7038  display: flex !important;
7039}
7040
7041[hidden].compare-remove {
7042  display: none !important;
7043}
7044
7045.pd-compare-btn.added [hidden].compare-remove {
7046  display: flex !important;
7047}
7048
7049/* Ensure the added state text is always visible */
7050.pd-compare-btn.added {
7051  border-color: #dcce77 !important;
7052  color: #dcce77 !important;
7053  background: #fef2f2 !important;
7054}
7055
7056.pd-compare-btn.added .compare-remove svg {
7057  color: #dcce77 !important;
7058}
7059
7060.pd-compare-btn.added:hover {
7061  border-color: #15803d;
7062  color: #15803d;
7063  background: #dcfce7;
7064  transform: translateY(-1px);
7065  box-shadow: 0 2px 6px rgba(22, 163, 74, 0.2);
7066}
7067
7068.pd-compare-btn .compare-icon {
7069  width: 16px;
7070  height: 16px;
7071  flex-shrink: 0;
7072}
7073
7074.pd-compare-info {
7075  display: inline-flex;
7076  align-items: center;
7077}
7078
7079.compare-note {
7080  color: #94a3b8;
7081  font-size: 0.7rem;
7082  font-weight: 500;
7083  transition: color 0.2s ease;
7084}
7085
7086.pd-compare-btn.added + .pd-compare-info .compare-note {
7087  color: #16a34a;
7088  font-weight: 600;
7089}
7090
7091/* Compare Badge in Navigation */
7092.compare-indicator {
7093  position: relative;
7094}
7095
7096.compare-indicator .badge {
7097  position: absolute;
7098  top: -8px;
7099  right: -8px;
7100  min-width: 18px;
7101  height: 18px;
7102  padding: 0 5px;
7103  background: #ef4444;
7104  color: white;
7105  border-radius: 9px;
7106  font-size: 11px;
7107 
7108
7109/* Floating Compare Badge (Sticky) */
7110.compare-floating-badge {
7111  position: fixed;
7112  right: 16px;
7113  bottom: calc(20px + 60px); /* Above scroll-to-top button */
7114  width: auto;
7115  min-width: 56px;
7116  height: 56px;
7117  padding: 0 16px;
7118  border-radius: 28px;
7119  background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
7120  color: #fff;
7121  box-shadow: 0 8px 24px -8px rgba(59, 130, 246, 0.5);
7122  cursor: pointer;
7123  z-index: 1104;
7124  opacity: 0;
7125  transform: translateY(8px) scale(0.9);
7126  transition: opacity 0.3s ease, transform 0.3s ease, box-shadow 0.2s ease;
7127  display: flex;
7128  align-items: center;
7129  justify-content: center;
7130  gap: 8px;
7131  font-weight: 600;
7132  font-size: 0.85rem;
7133  pointer-events: none;
7134}
7135
7136.compare-floating-badge.visible {
7137  opacity: 1;
7138  transform: translateY(0) scale(1);
7139  pointer-events: auto;
7140}
7141
7142.compare-floating-badge:hover {
7143  box-shadow: 0 12px 32px -10px rgba(59, 130, 246, 0.6);
7144  transform: translateY(-2px) scale(1.02);
7145}
7146
7147.compare-floating-badge:active {
7148  transform: translateY(0) scale(0.98);
7149}
7150
7151.compare-floating-badge .cfb-icon {
7152  width: 20px;
7153  height: 20px;
7154  flex-shrink: 0;
7155}
7156
7157.compare-floating-badge .cfb-count {
7158  font-size: 0.9rem;
7159  font-weight: 700;
7160  min-width: 20px;
7161  text-align: center;
7162  background: rgba(255, 255, 255, 0.25);
7163  padding: 2px 8px;
7164  border-radius: 12px;
7165  line-height: 1;
7166  display: flex;
7167  align-items: center;
7168  justify-content: center;
7169  height: 24px;
7170}
7171
7172.compare-floating-badge .cfb-text {
7173  display: none;
7174}
7175
7176@media (min-width: 641px) {
7177  .compare-floating-badge .cfb-text {
7178    display: block;
7179    white-space: nowrap;
7180  }
7181}
7182
7183@media (max-width: 640px) {
7184  .compare-floating-badge {
7185    right: 14px;
7186    bottom: calc(var(--scroll-top-bottom, calc(68px + 18px + env(safe-area-inset-bottom))) + 60px);
7187    min-width: 48px;
7188    height: 48px;
7189    padding: 0 12px;
7190    border-radius: 24px;
7191  }
7192  
7193  .compare-floating-badge .cfb-icon {
7194    width: 18px;
7195    height: 18px;
7196  }
7197  
7198  .compare-floating-badge .cfb-count {
7199    font-size: 0.8rem;
7200    padding: 2px 6px;
7201    height: 20px;
7202  }
7203} font-weight: 600;
7204  display: flex;
7205  align-items: center;
7206  justify-content: center;
7207  transform: scale(0);
7208  transition: transform 0.2s ease;
7209}
7210
7211.compare-indicator .badge.active {
7212  transform: scale(1);
7213}
7214
7215/* Compare Sidebar */
7216.compare-sidebar {
7217  position: fixed;
7218  top: 0;
7219  right: -400px;
7220  width: 400px;
7221  height: 100vh;
7222  background: #ffffff;
7223  box-shadow: -4px 0 20px rgba(0, 0, 0, 0.1);
7224  z-index: 9999;
7225  display: flex;
7226  flex-direction: column;
7227  transition: right 0.3s ease;
7228}
7229
7230.compare-sidebar.open {
7231  right: 0;
7232}
7233
7234.compare-overlay {
7235  position: fixed;
7236  top: 0;
7237  left: 0;
7238  width: 100%;
7239  height: 100%;
7240  background: rgba(0, 0, 0, 0.5);
7241  z-index: 9998;
7242  opacity: 0;
7243  visibility: hidden;
7244  transition: all 0.3s ease;
7245}
7246
7247.compare-overlay.active {
7248  opacity: 1;
7249  visibility: visible;
7250}
7251
7252.compare-header {
7253  display: flex;
7254  align-items: center;
7255  justify-content: space-between;
7256  padding: 1.25rem;
7257  border-bottom: 1px solid #e2e8f0;
7258  background: #f8fafc;
7259}
7260
7261.compare-title {
7262  display: flex;
7263  align-items: center;
7264  gap: 0.5rem;
7265  font-size: 1rem;
7266  font-weight: 600;
7267  color: #1e293b;
7268  margin: 0;
7269}
7270
7271.compare-count {
7272  color: #64748b;
7273  font-weight: 400;
7274}
7275
7276.compare-close {
7277  display: flex;
7278  align-items: center;
7279  justify-content: center;
7280  width: 32px;
7281  height: 32px;
7282  border: none;
7283  border-radius: 6px;
7284  background: transparent;
7285  color: #64748b;
7286  cursor: pointer;
7287  transition: all 0.2s ease;
7288}
7289
7290.compare-close:hover {
7291  background: #e2e8f0;
7292  color: #374151;
7293}
7294
7295.compare-content {
7296  flex: 1;
7297  overflow-y: auto;
7298  padding: 1rem;
7299}
7300
7301.compare-empty {
7302  display: flex;
7303  flex-direction: column;
7304  align-items: center;
7305  justify-content: center;
7306  height: 100%;
7307  text-align: center;
7308  padding: 2rem 1rem;
7309}
7310
7311.compare-empty h5 {
7312  margin: 1rem 0 0.5rem;
7313  color: #374151;
7314}
7315
7316.compare-empty p {
7317  color: #64748b;
7318  font-size: 0.875rem;
7319  line-height: 1.5;
7320}
7321
7322.compare-list {
7323  display: flex;
7324  flex-direction: column;
7325  gap: 0.75rem;
7326}
7327
7328.compare-item {
7329  display: flex;
7330  align-items: center;
7331  gap: 0.75rem;
7332  padding: 0.75rem;
7333  border: 1px solid #e2e8f0;
7334  border-radius: 8px;
7335  background: #ffffff;
7336  transition: all 0.2s ease;
7337}
7338
7339.compare-item:hover {
7340  border-color: #cbd5e1;
7341  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
7342}
7343
7344.compare-item-image {
7345  flex-shrink: 0;
7346  width: 48px;
7347  height: 48px;
7348  border-radius: 6px;
7349  overflow: hidden;
7350  border: 1px solid #e2e8f0;
7351}
7352
7353.compare-item-image img {
7354  width: 100%;
7355  height: 100%;
7356  object-fit: cover;
7357}
7358
7359.compare-item-info {
7360  flex: 1;
7361  min-width: 0;
7362}
7363
7364.compare-item-name {
7365  font-weight: 500;
7366  font-size: 0.875rem;
7367  color: #1e293b;
7368  margin-bottom: 0.25rem;
7369  line-height: 1.3;
7370  display: -webkit-box;
7371  -webkit-line-clamp: 2;
7372  -webkit-box-orient: vertical;
7373  overflow: hidden;
7374}
7375
7376.compare-item-price {
7377  font-weight: 600;
7378  font-size: 0.8rem;
7379  color: #059669;
7380}
7381
7382.compare-item-remove {
7383  flex-shrink: 0;
7384  display: flex;
7385  align-items: center;
7386  justify-content: center;
7387  width: 28px;
7388  height: 28px;
7389  border: none;
7390  border-radius: 4px;
7391  background: transparent;
7392  color: #dcce77;
7393  cursor: pointer;
7394  transition: all 0.2s ease;
7395}
7396
7397.compare-item-remove:hover {
7398  background: #fef2f2;
7399  color: #991b1b;
7400}
7401
7402.compare-actions {
7403  padding: 1rem;
7404  border-top: 1px solid #e2e8f0;
7405  display: flex;
7406  gap: 0.75rem;
7407}
7408
7409.compare-actions .btn {
7410  flex: 1;
7411  padding: 0.75rem 1rem;
7412  border-radius: 6px;
7413  font-weight: 600;
7414  font-size: 0.875rem;
7415  display: flex;
7416  align-items: center;
7417  justify-content: center;
7418  gap: 0.5rem;
7419  cursor: pointer;
7420  transition: all 0.2s ease;
7421}
7422
7423.btn-outline-danger {
7424  border: 1px solid #dcce77;
7425  background: transparent;
7426  color: #dcce77;
7427}
7428
7429.btn-outline-danger:hover {
7430  background: #dcce77;
7431  color: white;
7432}
7433
7434.btn-primary {
7435  border: 1px solid #3b82f6;
7436  background: #3b82f6;
7437  color: white;
7438}
7439
7440.btn-primary:hover {
7441  background: #2563eb;
7442  border-color: #2563eb;
7443}
7444
7445.btn-primary:disabled {
7446  opacity: 0.5;
7447  cursor: not-allowed;
7448}
7449
7450.btn-primary:disabled:hover {
7451  background: #3b82f6;
7452  border-color: #3b82f6;
7453}
7454
7455/* AI Comparison Modal */
7456.ai-compare-modal {
7457  position: fixed;
7458  top: 0;
7459  left: 0;
7460  width: 100%;
7461  height: 100%;
7462  z-index: 10000;
7463  display: none;
7464  align-items: center;
7465  justify-content: center;
7466}
7467
7468.ai-compare-modal.open {
7469  display: flex;
7470}
7471
7472.ai-compare-modal .modal-backdrop {
7473  position: absolute;
7474  top: 0;
7475  left: 0;
7476  width: 100%;
7477  height: 100%;
7478  background: rgba(0, 0, 0, 0.6);
7479  backdrop-filter: blur(4px);
7480}
7481
7482.modal-container {
7483  position: relative;
7484  width: 90%;
7485  max-width: 1000px;
7486  max-height: 90vh;
7487  background: #ffffff;
7488  border-radius: 12px;
7489  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
7490  display: flex;
7491  flex-direction: column;
7492  overflow: hidden;
7493}
7494
7495.modal-header {
7496  display: flex;
7497  align-items: center;
7498  justify-content: space-between;
7499  padding: 1.5rem 2rem;
7500  border-bottom: 1px solid #e2e8f0;
7501  background: #f8fafc;
7502}
7503
7504.modal-title {
7505  display: flex;
7506  align-items: center;
7507  gap: 0.75rem;
7508  font-size: 1.25rem;
7509  font-weight: 600;
7510  color: #1e293b;
7511  margin: 0;
7512}
7513
7514.modal-close {
7515  display: flex;
7516  align-items: center;
7517  justify-content: center;
7518  width: 36px;
7519  height: 36px;
7520  border: none;
7521  border-radius: 8px;
7522  background: transparent;
7523  color: #64748b;
7524  cursor: pointer;
7525  transition: all 0.2s ease;
7526}
7527
7528.modal-close:hover {
7529  background: #e2e8f0;
7530  color: #374151;
7531}
7532
7533.modal-content {
7534  flex: 1;
7535  overflow-y: auto;
7536  padding: 2rem;
7537}
7538
7539/* AI Loading State */
7540.ai-loading {
7541  display: flex;
7542  flex-direction: column;
7543  align-items: center;
7544  justify-content: center;
7545  padding: 3rem 2rem;
7546  text-align: center;
7547}
7548
7549.loading-animation {
7550  margin-bottom: 1rem;
7551}
7552
7553.typing-indicator {
7554  display: flex;
7555  align-items: center;
7556  justify-content: center;
7557  gap: 4px;
7558  margin-bottom: 1rem;
7559}
7560
7561.typing-indicator span {
7562  width: 8px;
7563  height: 8px;
7564  background: #3b82f6;
7565  border-radius: 50%;
7566  animation: typing 1.4s infinite ease-in-out;
7567}
7568
7569.typing-indicator span:nth-child(2) {
7570  animation-delay: 0.2s;
7571}
7572
7573.typing-indicator span:nth-child(3) {
7574  animation-delay: 0.4s;
7575}
7576
7577@keyframes typing {
7578  0%, 80%, 100% {
7579    opacity: 0.3;
7580    transform: scale(0.8);
7581  }
7582  40% {
7583    opacity: 1;
7584    transform: scale(1);
7585  }
7586}
7587
7588.ai-loading p {
7589  font-weight: 500;
7590  color: #1e293b;
7591  margin-bottom: 0.5rem;
7592}
7593
7594.ai-loading small {
7595  color: #64748b;
7596}
7597
7598/* AI Error State */
7599.ai-error {
7600  display: flex;
7601  flex-direction: column;
7602  align-items: center;
7603  justify-content: center;
7604  padding: 3rem 2rem;
7605  text-align: center;
7606}
7607
7608.ai-error h4 {
7609  margin: 1rem 0 0.5rem;
7610  color: #dcce77;
7611}
7612
7613.ai-error p {
7614  color: #64748b;
7615  margin-bottom: 1.5rem;
7616}
7617
7618/* AI Results */
7619.ai-results > div {
7620  margin-bottom: 2rem;
7621}
7622
7623.ai-summary-card,
7624.ai-comparison-table,
7625.ai-detailed-analysis,
7626.ai-recommendations,
7627.ai-tips {
7628  background: #f8fafc;
7629  border: 1px solid #e2e8f0;
7630  border-radius: 12px;
7631  padding: 1.5rem;
7632}
7633
7634.ai-results h4 {
7635  display: flex;
7636  align-items: center;
7637  gap: 0.5rem;
7638  font-size: 1.1rem;
7639  font-weight: 600;
7640  color: #1e293b;
7641  margin-bottom: 1rem;
7642}
7643
7644.summary-text {
7645  font-size: 1rem;
7646  line-height: 1.6;
7647  color: #374151;
7648  margin-bottom: 1rem;
7649}
7650
7651.winner-badge {
7652  padding: 1rem;
7653  background: linear-gradient(135deg, #10b981 0%, #059669 100%);
7654  color: white;
7655  border-radius: 8px;
7656}
7657
7658.winner-badge strong {
7659  display: block;
7660  margin-bottom: 0.25rem;
7661}
7662
7663.winner-badge small {
7664  opacity: 0.9;
7665  font-size: 0.875rem;
7666}
7667
7668/* Comparison Table */
7669.comparison-grid {
7670  display: grid;
7671  gap: 1rem;
7672  margin-top: 1rem;
7673}
7674
7675.comparison-category {
7676  background: white;
7677  border: 1px solid #e2e8f0;
7678  border-radius: 8px;
7679  overflow: hidden;
7680}
7681
7682.category-header {
7683  background: #f1f5f9;
7684  padding: 0.75rem 1rem;
7685  font-weight: 600;
7686  color: #1e293b;
7687  border-bottom: 1px solid #e2e8f0;
7688}
7689
7690.category-products {
7691  display: grid;
7692  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
7693  gap: 1px;
7694  background: #e2e8f0;
7695}
7696
7697.product-score {
7698  background: white;
7699  padding: 1rem;
7700  text-align: center;
7701}
7702
7703.score-value {
7704  display: inline-flex;
7705  align-items: center;
7706  gap: 0.25rem;
7707  margin-bottom: 0.5rem;
7708}
7709
7710.score-stars {
7711  color: #fbbf24;
7712}
7713
7714.score-note {
7715  font-size: 0.8rem;
7716  color: #64748b;
7717  line-height: 1.3;
7718}
7719
7720/* Product name header in comparison table */
7721.product-name-header {
7722  margin-bottom: 0.5rem;
7723}
7724
7725.product-name-header strong {
7726  font-size: .8rem; /* h5 size (18px) */
7727  font-weight: 600;
7728  color: #1e293b;
7729  line-height: 1.3;
7730  display: block;
7731  word-break: break-word;
7732}
7733
7734.product-price {
7735  font-size: 0.8rem;
7736  font-weight: 600;
7737  color: #059669;
7738}
7739
7740/* Detailed Analysis */
7741.products-analysis {
7742  display: grid;
7743  gap: 1rem;
7744  margin-top: 1rem;
7745}
7746
7747.product-analysis {
7748  background: white;
7749  border: 1px solid #e2e8f0;
7750  border-radius: 8px;
7751  padding: 1rem;
7752}
7753
7754.product-analysis h5 {
7755  color: #1e293b;
7756  margin-bottom: 0.75rem;
7757  font-weight: 600;
7758}
7759
7760.analysis-section {
7761  margin-bottom: 0.75rem;
7762}
7763
7764.analysis-section:last-child {
7765  margin-bottom: 0;
7766}
7767
7768.analysis-section h6 {
7769  font-size: 0.875rem;
7770  font-weight: 600;
7771  margin-bottom: 0.5rem;
7772}
7773
7774.analysis-section h6.strengths {
7775  color: #059669;
7776}
7777
7778.analysis-section h6.weaknesses {
7779  color: #dcce77;
7780}
7781
7782.analysis-section h6.best-for {
7783  color: #7c3aed;
7784}
7785
7786.analysis-section ul {
7787  list-style: none;
7788  padding: 0;
7789  margin: 0;
7790}
7791
7792.analysis-section li {
7793  display: flex;
7794  align-items: flex-start;
7795  gap: 0.5rem;
7796  margin-bottom: 0.25rem;
7797  font-size: 0.875rem;
7798  line-height: 1.4;
7799}
7800
7801.analysis-section li:before {
7802  content: "•";
7803  color: #64748b;
7804  font-weight: bold;
7805  margin-top: 0.1rem;
7806}
7807
7808.best-for-text {
7809  font-size: 0.875rem;
7810  color: #374151;
7811  font-style: italic;
7812}
7813
7814/* Recommendations */
7815.recommendations-grid {
7816  display: grid;
7817  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
7818  gap: 1rem;
7819  margin-top: 1rem;
7820}
7821
7822.recommendation-card {
7823  background: white;
7824  border: 1px solid #e2e8f0;
7825  border-radius: 8px;
7826  padding: 1rem;
7827  text-align: center;
7828}
7829
7830.recommendation-card h5 {
7831  font-size: 1rem;
7832  margin-bottom: 0.75rem;
7833  color: #1e293b;
7834}
7835
7836.recommendation-card p {
7837  font-size: 0.875rem;
7838  color: #374151;
7839  line-height: 1.5;
7840}
7841
7842/* Tips */
7843.tips-list {
7844  list-style: none;
7845  padding: 0;
7846  margin: 1rem 0 0 0;
7847}
7848
7849.tips-list li {
7850  display: flex;
7851  align-items: flex-start;
7852  gap: 0.75rem;
7853  margin-bottom: 0.75rem;
7854  font-size: 0.9rem;
7855  line-height: 1.5;
7856  color: #374151;
7857}
7858
7859.tips-list li:before {
7860  content: "💡";
7861  font-size: 1rem;
7862  margin-top: 0.1rem;
7863}
7864
7865/* Modal Footer */
7866.modal-footer {
7867  display: flex;
7868  justify-content: space-between;
7869  align-items: center;
7870  padding: 1.5rem 2rem;
7871  border-top: 1px solid #e2e8f0;
7872  background: #f8fafc;
7873}
7874
7875.btn-outline-secondary {
7876  border: 1px solid #64748b;
7877  background: transparent;
7878  color: #64748b;
7879  padding: 0.75rem 1.5rem;
7880  border-radius: 6px;
7881  font-weight: 600;
7882  display: flex;
7883  align-items: center;
7884  gap: 0.5rem;
7885  cursor: pointer;
7886  transition: all 0.2s ease;
7887}
7888
7889.btn-outline-secondary:hover {
7890  background: #64748b;
7891  color: white;
7892}
7893
7894/* Responsive for chip button */
7895@media (max-width: 880px) {
7896  .pd-compare-section {
7897    margin: 1rem 0;
7898    display: block;
7899  }
7900
7901  .pd-compare-btn {
7902    width: 100%;
7903    font-size: 0.85rem;
7904    padding: 0.65rem 1rem;
7905    border-radius: 10px;
7906    justify-content: center;
7907    min-height: 44px;
7908  }
7909  
7910  .pd-compare-btn .compare-icon {
7911    width: 16px;
7912    height: 16px;
7913  }
7914
7915  .pd-compare-info {
7916    margin-top: 0.5rem;
7917    display: block;
7918    text-align: center;
7919  }
7920
7921  .compare-note {
7922    font-size: 0.7rem;
7923    display: block;
7924  }
7925}
7926
7927/* Responsive Design */
7928@media (max-width: 768px) {
7929  .compare-sidebar {
7930    width: 100%;
7931    right: -100%;
7932    /* Fix for mobile viewport height changes */
7933    height: 100vh;
7934    height: 100dvh; /* Dynamic viewport height for modern browsers */
7935  }
7936  
7937  .compare-actions {
7938    /* Ensure actions are always visible */
7939    position: sticky;
7940    bottom: 0;
7941    background: white;
7942    border-top: 2px solid #e2e8f0;
7943    box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
7944    z-index: 10;
7945  }
7946  
7947  .compare-actions .btn {
7948    /* Larger touch targets for mobile */
7949    min-height: 48px;
7950    font-size: 0.9rem;
7951    font-weight: 700;
7952  }
7953  
7954  .modal-container {
7955    width: 95%;
7956    max-height: 95vh;
7957    max-height: 95dvh; /* Dynamic viewport height */
7958  }
7959  
7960  .modal-header,
7961  .modal-content,
7962  .modal-footer {
7963    padding: 1rem;
7964  }
7965  
7966  .recommendations-grid {
7967    grid-template-columns: 1fr;
7968  }
7969  
7970  .category-products {
7971    grid-template-columns: 1fr;
7972  }
7973  
7974  .modal-footer {
7975    flex-direction: column;
7976    gap: 0.75rem;
7977  }
7978  
7979  .modal-footer .btn {
7980    width: 100%;
7981  }
7982  
7983  /* Ensure compare button is visible on mobile */
7984  .pd-compare-section {
7985    display: block !important;
7986    visibility: visible !important;
7987    opacity: 1 !important;
7988    margin: 1rem 0;
7989    padding: 0;
7990    position: relative !important;
7991    z-index: 1 !important;
7992    max-width: 100%;
7993    overflow: hidden;
7994  }
7995  
7996  .pd-compare-btn {
7997    display: flex !important;
7998    visibility: visible !important;
7999    opacity: 1 !important;
8000    width: 100%;
8001    padding: 0.75rem 1rem;
8002    font-size: 0.9rem;
8003    min-height: 48px; /* Minimum touch target for mobile */
8004    border: 2px solid #e2e8f0 !important;
8005    background: #fff !important;
8006    position: relative !important;
8007    z-index: 2 !important;
8008    box-sizing: border-box;
8009    white-space: normal !important; /* Allow text wrapping on mobile */
8010    flex-wrap: wrap;
8011    gap: 0.5rem;
8012  }
8013  
8014  .pd-compare-btn .compare-text {
8015    display: inline !important;
8016    visibility: visible !important;
8017    font-weight: 600;
8018    color: #475569 !important;
8019  }
8020  
8021  .pd-compare-btn .compare-remove {
8022    display: none !important; /* Hidden by default */
8023  }
8024  
8025  .pd-compare-btn.added .compare-remove {
8026    display: flex !important; /* Only show when added */
8027    flex-wrap: wrap;
8028    align-items: center;
8029    gap: 0.4rem;
8030    white-space: normal !important;
8031    word-break: break-word;
8032  }
8033  
8034  .pd-compare-btn .compare-icon {
8035    display: inline-block !important;
8036    width: 18px;
8037    height: 18px;
8038    flex-shrink: 0;
8039  }
8040  
8041  .pd-compare-info {
8042    margin-top: 0.5rem;
8043  }
8044  
8045  .compare-note {
8046    font-size: 0.85rem !important;
8047    display: block !important;
8048    visibility: visible !important;
8049    color: #64748b !important;
8050  }
8051}
8052
8053/* iOS Safari specific fixes */
8054@media (max-width: 480px) {
8055  #compareBtn { 
8056    width:46px;
8057    height:46px;
8058  }
8059  
8060  .pd-compare-section {
8061    -webkit-transform: translateZ(0);
8062    transform: translateZ(0);
8063  }
8064  
8065  .pd-compare-btn {
8066    -webkit-appearance: none !important;
8067    appearance: none !important;
8068    -webkit-tap-highlight-color: transparent;
8069    touch-action: manipulation;
8070  }
8071  
8072  /* Fix sidebar height for iOS address bar */
8073  .compare-sidebar {
8074    /* Use fixed positioning that accounts for iOS address bar */
8075    height: 100vh;
8076    height: -webkit-fill-available; /* iOS Safari compatibility */
8077    min-height: 100vh;
8078    min-height: -webkit-fill-available;
8079  }
8080  
8081  /* Ensure compare actions stay at bottom */
8082  .compare-actions {
8083    position: sticky;
8084    bottom: 0;
8085    background: white;
8086    border-top: 2px solid #e2e8f0;
8087    box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.15);
8088    padding: 1.25rem 1rem;
8089    z-index: 20;
8090  }
8091  
8092  .compare-actions .btn {
8093    min-height: 52px; /* Larger for easier tapping */
8094    font-size: 1rem;
8095    font-weight: 700;
8096    border-radius: 8px;
8097  }
8098  
8099  /* Adjust content padding to account for sticky actions */
8100  .compare-content {
8101    padding-bottom: 6rem; /* Extra space for sticky actions */
8102  }
8103}
8104
8105/* High-resolution mobile devices (iPhone 12 Pro, etc.) */
8106@media (-webkit-min-device-pixel-ratio: 2) and (max-width: 430px) {
8107  .pd-compare-section {
8108    display: block !important;
8109  }
8110  
8111  .pd-compare-btn {
8112    display: flex !important;
8113    min-height: 44px;
8114    font-size: 16px; /* Prevent zoom on iOS */
8115  }
8116  
8117  /* Enhanced fixes for iPhone 12 Pro and similar devices */
8118  .compare-sidebar {
8119    /* Ensure full height coverage */
8120    height: 100vh;
8121    height: 100dvh; /* Dynamic viewport height */
8122    height: -webkit-fill-available; /* iOS fallback */
8123    max-height: 100vh;
8124    max-height: 100dvh;
8125    max-height: -webkit-fill-available;
8126    overflow: hidden; /* Prevent scroll issues */
8127  }
8128  
8129  .compare-content {
8130    /* Flexible content area */
8131    flex: 1;
8132    overflow-y: auto;
8133    -webkit-overflow-scrolling: touch;
8134    padding: 1rem 1rem 8rem 1rem; /* Extra bottom padding */
8135  }
8136  
8137  .compare-actions {
8138    /* Force actions to stay visible */
8139    position: fixed;
8140    bottom: 0;
8141    left: 0;
8142    right: 0;
8143    width: 100%;
8144    background: white;
8145    border-top: 2px solid #e2e8f0;
8146    box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.2);
8147    padding: 1rem;
8148    z-index: 9999;
8149    display: flex !important;
8150    gap: 0.75rem;
8151  }
8152  
8153  .compare-actions .btn {
8154    flex: 1;
8155    min-height: 30px; /* Extra large for high-DPI screens */
8156    font-size: .8rem;
8157    font-weight: 600;
8158    border-radius: 12px;
8159    display: flex !important;
8160    align-items: center;
8161    justify-content: center;
8162    gap: 0.5rem;
8163  }
8164  
8165  /* Ensure AI analyze button is always visible and prominent */
8166  #compareAnalyze {
8167    background: #3b82f6 !important;
8168    border-color: #3b82f6 !important;
8169    color: white !important;
8170    box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
8171  }
8172  
8173  #compareAnalyze:not(:disabled) {
8174    background: #2563eb !important;
8175    border-color: #2563eb !important;
8176    transform: scale(1.02);
8177  }
8178  
8179  #compareAnalyze:disabled {
8180    opacity: 0.6 !important;
8181    background: #9ca3af !important;
8182    border-color: #9ca3af !important;
8183  }
8184}
8185
8186/* AI Disclaimer Styles */
8187.ai-disclaimer {
8188  display: flex;
8189  align-items: flex-start;
8190  gap: 0.75rem;
8191  padding: 1rem;
8192  background: #fef3c7;
8193  border: 1px solid #fbbf24;
8194  border-radius: 8px;
8195  margin: 0;
8196}
8197
8198.disclaimer-icon {
8199  font-size: 1.25rem;
8200  flex-shrink: 0;
8201  margin-top: 0.125rem;
8202}
8203
8204.disclaimer-text {
8205  font-size: 0.85rem;
8206  line-height: 1.4;
8207  color: #92400e;
8208}
8209
8210.disclaimer-text strong {
8211  color: #78350f;
8212  font-weight: 600;
8213}
8214
8215/* Compare Disclaimer in Sidebar */
8216.compare-disclaimer {
8217  margin-top: 1rem;
8218  padding: 1rem;
8219  background: #f0f9ff;
8220  border: 1px solid #0ea5e9;
8221  border-radius: 8px;
8222}
8223
8224.compare-disclaimer h6 {
8225  display: flex;
8226  align-items: center;
8227  gap: 0.5rem;
8228  font-size: 0.875rem;
8229  font-weight: 600;
8230  color: #0c4a6e;
8231  margin-bottom: 0.5rem;
8232}
8233
8234.compare-disclaimer p {
8235  font-size: 0.8rem;
8236  line-height: 1.4;
8237  color: #075985;
8238  margin-bottom: 0.5rem;
8239}
8240
8241.compare-disclaimer p:last-child {
8242  margin-bottom: 0;
8243}
8244
8245.compare-disclaimer ul {
8246  list-style: none;
8247  padding: 0;
8248  margin: 0.5rem 0 0 0;
8249}
8250
8251.compare-disclaimer li {
8252  display: flex;
8253  align-items: flex-start;
8254  gap: 0.5rem;
8255  margin-bottom: 0.25rem;
8256  font-size: 0.75rem;
8257  line-height: 1.3;
8258  color: #075985;
8259}
8260
8261.compare-disclaimer li:before {
8262  content: "•";
8263  color: #0ea5e9;
8264  font-weight: bold;
8265  margin-top: 0.1rem;
8266  flex-shrink: 0;
8267}
8268
8269/* Mobile Browser Address Bar Fixes */
8270@supports (height: 100dvh) {
8271  /* Modern browsers with dynamic viewport support */
8272  @media (max-width: 768px) {
8273    .compare-sidebar {
8274      height: 100dvh !important;
8275      max-height: 100dvh !important;
8276    }
8277    
8278    .modal-container {
8279      max-height: 95dvh !important;
8280    }
8281  }
8282}
8283
8284@supports (height: -webkit-fill-available) {
8285  /* iOS Safari specific support */
8286  @media (max-width: 768px) {
8287    .compare-sidebar {
8288      height: -webkit-fill-available !important;
8289      max-height: -webkit-fill-available !important;
8290    }
8291  }
8292}
8293
8294/* Fallback for older browsers */
8295@media (max-width: 768px) and (orientation: portrait) {
8296  .compare-sidebar {
8297    /* Fixed height that works across different mobile browsers */
8298    min-height: 600px;
8299    height: calc(100vh - env(keyboard-inset-height, 0px));
8300    height: calc(100vh - env(safe-area-inset-bottom, 0px));
8301  }
8302  
8303  /* Ensure actions are always accessible */
8304  .compare-actions {
8305    position: sticky !important;
8306    bottom: env(safe-area-inset-bottom, 0px) !important;
8307    margin-bottom: 0 !important;
8308    padding-bottom: calc(1rem + env(safe-area-inset-bottom, 0px)) !important;
8309  }
8310}
8311
8312/* PWA and fullscreen app support */
8313@media (display-mode: standalone) {
8314  .compare-sidebar {
8315    height: 100vh !important;
8316    height: 100dvh !important;
8317  }
8318}
8319
8320/* Utilities */
8321.btn-sm {
8322  padding: 0.5rem 0.75rem;
8323  font-size: 0.8rem;
8324}
8325
8326[hidden] {
8327  display: none !important;
8328}
8329
8330/* ===== UX ENHANCEMENTS ===== */
8331
8332/* Compare Button Highlight Animation */
8333.pulse-highlight {
8334  animation: pulse-glow 2s ease-in-out 3;
8335  box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7);
8336}
8337
8338@keyframes pulse-glow {
8339  0% {
8340    box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7);
8341  }
8342  50% {
8343    box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
8344  }
8345  100% {
8346    box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
8347  }
8348}
8349
8350/* Flying Product Animation */
8351.flying-product {
8352  position: fixed;
8353  z-index: 9999;
8354  pointer-events: none;
8355}
8356
8357.flying-product img {
8358  border-radius: 8px;
8359  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
8360}
8361
8362/* Sticky Compare Bar - REMOVED (using only fly-to-navbar animation) */
8363
8364/* Inline Compare Preview - REMOVED (keeping it simple and clean) */
8365</style>
8366  <style data-inlined="product-card-actions.css">
8367/**
8368 * Product Card Quick Actions - 3 Dot Menu + Bottom Sheet
8369 * Tokopedia-inspired design
8370 */
8371
8372/* ===== 3 Dot Button on Product Card ===== */
8373.prod-card {
8374    position: relative;
8375}
8376
8377.prod-card-actions-btn {
8378    position: absolute;
8379    top: 8px;
8380    right: 8px;
8381    width: 32px;
8382    height: 32px;
8383    border-radius: 50%;
8384    background: rgba(255, 255, 255, 0.95);
8385    border: 1px solid rgba(0, 0, 0, 0.08);
8386    display: inline-flex;
8387    align-items: center;
8388    justify-content: center;
8389    cursor: pointer;
8390    z-index: 3;
8391    transition: all 0.2s ease;
8392    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
8393    -webkit-tap-highlight-color: transparent;
8394}
8395
8396.prod-card-actions-btn:hover {
8397    background: #fff;
8398    border-color: rgba(0, 0, 0, 0.15);
8399    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
8400    transform: scale(1.05);
8401}
8402
8403.prod-card-actions-btn:active {
8404    transform: scale(0.95);
8405}
8406
8407.prod-card-actions-btn svg {
8408    width: 20px;
8409    height: 20px;
8410    color: #64748b;
8411    display: block;
8412}
8413
8414/* Desktop: Show on hover */
8415@media (min-width: 641px) {
8416    .prod-card-actions-btn {
8417        opacity: 0;
8418        pointer-events: none;
8419    }
8420    
8421    .prod-card:hover .prod-card-actions-btn {
8422        opacity: 1;
8423        pointer-events: auto;
8424    }
8425}
8426
8427/* Mobile: Always visible but smaller */
8428@media (max-width: 640px) {
8429    .prod-card-actions-btn {
8430        width: 28px;
8431        height: 28px;
8432        top: 6px;
8433        right: 6px;
8434        opacity: 95%;
8435    }
8436    
8437    .prod-card-actions-btn svg {
8438        width: 18px;
8439        height: 18px;
8440    }
8441}
8442
8443/* ===== Bottom Sheet Overlay ===== */
8444.pca-overlay {
8445    position: fixed;
8446    inset: 0;
8447    background: rgba(15, 23, 42, 0.6);
8448    backdrop-filter: blur(4px);
8449    z-index: 99998;
8450    opacity: 0;
8451    visibility: hidden;
8452    transition: all 0.3s ease;
8453}
8454
8455.pca-overlay.active {
8456    opacity: 1;
8457    visibility: visible;
8458}
8459
8460/* ===== Bottom Sheet Container ===== */
8461.pca-bottom-sheet {
8462    position: fixed;
8463    bottom: 0;
8464    left: 0;
8465    right: 0;
8466    background: #fff;
8467    border-radius: 20px 20px 0 0;
8468    box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.15);
8469    z-index: 99999;
8470    max-width: 600px;
8471    margin: 0 auto;
8472    transform: translateY(100%);
8473    transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
8474}
8475
8476.pca-bottom-sheet.active {
8477    transform: translateY(0);
8478}
8479
8480/* Drag Handle */
8481.pca-sheet-handle {
8482    width: 40px;
8483    height: 4px;
8484    background: #cbd5e1;
8485    border-radius: 999px;
8486    margin: 12px auto 8px;
8487}
8488
8489/* Product Preview */
8490.pca-sheet-product {
8491    display: flex;
8492    gap: 12px;
8493    padding: 12px 20px;
8494    border-bottom: 1px solid #f1f5f9;
8495}
8496
8497.pca-product-image {
8498    width: 60px;
8499    height: 60px;
8500    border-radius: 8px;
8501    object-fit: cover;
8502    flex-shrink: 0;
8503    border: 1px solid #e2e8f0;
8504}
8505
8506.pca-product-info {
8507    flex: 1;
8508    min-width: 0;
8509}
8510
8511.pca-product-name {
8512    font-size: 14px;
8513    font-weight: 600;
8514    color: #0f172a;
8515    margin: 0 0 4px 0;
8516    display: -webkit-box;
8517    -webkit-line-clamp: 2;
8518    -webkit-box-orient: vertical;
8519    overflow: hidden;
8520    line-height: 1.3;
8521}
8522
8523.pca-product-price {
8524    font-size: 15px;
8525    font-weight: 700;
8526    color: #0ea5e9;
8527    margin: 0;
8528}
8529
8530.pca-product-stock {
8531    font-size: 12px;
8532    color: #64748b;
8533    margin: 2px 0 0 0;
8534}
8535
8536.pca-product-stock.in {
8537    color: #16a34a;
8538}
8539
8540.pca-product-stock.low {
8541    color: #ea580c;
8542}
8543
8544.pca-product-stock.out {
8545    color: #dcce77;
8546}
8547
8548/* Action Menu */
8549.pca-sheet-actions {
8550    padding: 8px 0 20px;
8551}
8552
8553.pca-action-item {
8554    display: flex;
8555    align-items: center;
8556    gap: 16px;
8557    padding: 16px 20px;
8558    background: transparent;
8559    border: 0;
8560    width: 100%;
8561    cursor: pointer;
8562    transition: background-color 0.15s ease;
8563    -webkit-tap-highlight-color: transparent;
8564}
8565
8566.pca-action-item:hover {
8567    background: #f8fafc;
8568}
8569
8570.pca-action-item:active {
8571    background: #f1f5f9;
8572}
8573
8574.pca-action-item:disabled {
8575    opacity: 0.4;
8576    cursor: not-allowed;
8577}
8578
8579.pca-action-item:disabled:hover {
8580    background: transparent;
8581}
8582
8583.pca-action-icon {
8584    width: 24px;
8585    height: 24px;
8586    color: #475569;
8587    flex-shrink: 0;
8588}
8589
8590.pca-action-content {
8591    flex: 1;
8592    text-align: left;
8593}
8594
8595.pca-action-label {
8596    font-size: 15px;
8597    font-weight: 600;
8598    color: #0f172a;
8599    margin: 0 0 2px 0;
8600}
8601
8602.pca-action-desc {
8603    font-size: 12px;
8604    color: #64748b;
8605    margin: 0;
8606}
8607
8608.pca-action-badge {
8609    display: inline-flex;
8610    align-items: center;
8611    justify-content: center;
8612    min-width: 20px;
8613    height: 20px;
8614    padding: 0 6px;
8615    background: #0ea5e9;
8616    color: #fff;
8617    font-size: 11px;
8618    font-weight: 700;
8619    border-radius: 999px;
8620    margin-left: auto;
8621}
8622
8623.pca-action-badge.compare {
8624    background: #8b5cf6;
8625}
8626
8627/* Action Item Colors */
8628.pca-action-item.cart .pca-action-icon {
8629    color: #0ea5e9;
8630}
8631
8632.pca-action-item.compare .pca-action-icon {
8633    color: #8b5cf6;
8634}
8635
8636.pca-action-item.compare.active {
8637    background: rgba(139, 92, 246, 0.05);
8638}
8639
8640.pca-action-item.compare.active .pca-action-label {
8641    color: #8b5cf6;
8642}
8643
8644.pca-action-item.compare.active .pca-action-icon {
8645    color: #8b5cf6;
8646}
8647
8648.pca-action-item.share .pca-action-icon {
8649    color: #64748b;
8650}
8651
8652/* Loading State */
8653.pca-action-item.loading {
8654    pointer-events: none;
8655}
8656
8657.pca-action-item.loading .pca-action-icon {
8658    animation: spin 1s linear infinite;
8659}
8660
8661@keyframes spin {
8662    from { transform: rotate(0deg); }
8663    to { transform: rotate(360deg); }
8664}
8665
8666/* Success State Animation */
8667.pca-action-item.success .pca-action-icon {
8668    animation: checkmark 0.5s ease;
8669}
8670
8671@keyframes checkmark {
8672    0% { transform: scale(0); }
8673    50% { transform: scale(1.2); }
8674    100% { transform: scale(1); }
8675}
8676
8677/* ===== Safe Area for Mobile Devices ===== */
8678@supports (padding-bottom: env(safe-area-inset-bottom)) {
8679    .pca-sheet-actions {
8680        padding-bottom: calc(20px + env(safe-area-inset-bottom));
8681    }
8682}
8683
8684/* ===== Responsive Tweaks ===== */
8685@media (max-width: 640px) {
8686    .pca-bottom-sheet {
8687        border-radius: 16px 16px 0 0;
8688    }
8689    
8690    .pca-sheet-product {
8691        padding: 10px 16px;
8692    }
8693    
8694    .pca-product-image {
8695        width: 50px;
8696        height: 50px;
8697    }
8698    
8699    .pca-product-name {
8700        font-size: 13px;
8701    }
8702    
8703    .pca-product-price {
8704        font-size: 14px;
8705    }
8706    
8707    .pca-action-item {
8708        padding: 14px 16px;
8709        gap: 12px;
8710    }
8711    
8712    .pca-action-icon {
8713        width: 22px;
8714        height: 22px;
8715    }
8716    
8717    .pca-action-label {
8718        font-size: 14px;
8719    }
8720}
8721
8722/* ===== Animation for Flying Product ===== */
8723.flying-product {
8724    position: fixed;
8725    pointer-events: none;
8726    z-index: 99999;
8727    animation: fadeOut 0.8s ease forwards;
8728}
8729
8730@keyframes fadeOut {
8731    to {
8732        opacity: 0;
8733        transform: scale(0.5);
8734    }
8735}
8736
8737/* ===== Desktop Dropdown (Alternative) ===== */
8738@media (min-width: 1024px) {
8739    /* Optional: Could add desktop dropdown menu here */
8740    /* For now, using bottom sheet for consistency */
8741}
8742
8743/* ===== Print Styles ===== */
8744@media print {
8745    .prod-card-actions-btn,
8746    .pca-overlay,
8747    .pca-bottom-sheet {
8748        display: none !important;
8749    }
8750}
8751
8752</style>
8753  <style data-inlined="modal.css">
8754/* Modern Modal Dialog for Cart Operations */
8755.modal-overlay {
8756  position: fixed;
8757  top: 0;
8758  left: 0;
8759  right: 0;
8760  bottom: 0;
8761  background: rgba(0, 0, 0, 0.5);
8762  backdrop-filter: blur(4px);
8763  display: flex;
8764  align-items: center;
8765  justify-content: center;
8766  z-index: 9999;
8767  opacity: 0;
8768  visibility: hidden;
8769  transition: all 0.2s ease;
8770}
8771
8772.modal-overlay.show {
8773  opacity: 1;
8774  visibility: visible;
8775}
8776
8777.modal-dialog {
8778  background: white;
8779  border-radius: 16px;
8780  box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15);
8781  max-width: 400px;
8782  width: 90%;
8783  max-height: 90vh;
8784  overflow-y: auto;
8785  transform: scale(0.95);
8786  transition: transform 0.2s ease;
8787}
8788
8789.modal-overlay.show .modal-dialog {
8790  transform: scale(1);
8791}
8792
8793.modal-header {
8794  padding: 24px 24px 16px;
8795  border-bottom: 1px solid #f1f5f9;
8796}
8797
8798.modal-title {
8799  font-size: 20px;
8800  font-weight: 700;
8801  color: #0f172a;
8802  margin: 0;
8803  display: flex;
8804  align-items: center;
8805  gap: 12px;
8806}
8807
8808.modal-body {
8809  padding: 16px 24px 24px;
8810}
8811
8812.modal-message {
8813  font-size: 16px;
8814  line-height: 1.5;
8815  color: #475569;
8816  margin: 0;
8817}
8818
8819.modal-actions {
8820  display: flex;
8821  gap: 12px;
8822  margin-top: 24px;
8823}
8824
8825.modal-btn {
8826  flex: 1;
8827  padding: 12px 16px;
8828  border: none;
8829  border-radius: 8px;
8830  font-size: 14px;
8831  font-weight: 600;
8832  cursor: pointer;
8833  transition: all 0.2s ease;
8834  text-align: center;
8835}
8836
8837.modal-btn-secondary {
8838  background: #f8fafc;
8839  color: #64748b;
8840  border: 1px solid #e2e8f0;
8841}
8842
8843.modal-btn-secondary:hover {
8844  background: #f1f5f9;
8845  border-color: #cbd5e1;
8846}
8847
8848.modal-btn-danger {
8849  background: #ef4444;
8850  color: white;
8851}
8852
8853.modal-btn-danger:hover {
8854  background: #dcce77;
8855}
8856
8857.modal-btn-primary {
8858  background: #3b82f6;
8859  color: white;
8860}
8861
8862.modal-btn-primary:hover {
8863  background: #2563eb;
8864}
8865
8866.modal-icon {
8867  font-size: 24px;
8868  width: 32px;
8869  height: 32px;
8870  display: flex;
8871  align-items: center;
8872  justify-content: center;
8873  border-radius: 8px;
8874}
8875
8876.modal-icon.danger {
8877  background: #fef2f2;
8878  color: #ef4444;
8879}
8880
8881.modal-icon.warning {
8882  background: #fffbeb;
8883  color: #f59e0b;
8884}
8885
8886.modal-icon.success {
8887  background: #f0fdf4;
8888  color: #22c55e;
8889}
8890
8891.modal-icon.info {
8892  background: #eff6ff;
8893  color: #3b82f6;
8894}
8895
8896/* Mobile responsive */
8897@media (max-width: 640px) {
8898  .modal-dialog {
8899    margin: 16px;
8900    max-width: none;
8901    width: auto;
8902  }
8903  
8904  .modal-header,
8905  .modal-body {
8906    padding-left: 20px;
8907    padding-right: 20px;
8908  }
8909  
8910  .modal-actions {
8911    flex-direction: column;
8912  }
8913  
8914  .modal-btn {
8915    flex: none;
8916  }
8917}
8918</style>
8919      
8920        <style data-inlined="reviews-points.css">
8921/**
8922 * Reviews & Points System - Styles
8923 * Product reviews, rating display, and point redemption UI
8924 */
8925
8926/* ===================================
8927   PRODUCT REVIEWS
8928   =================================== */
8929
8930.product-tabs {
8931  display: flex;
8932  border-bottom: 2px solid #e5e7eb;
8933  margin: 24px 0 16px;
8934  gap: 8px;
8935}
8936
8937.tab-btn {
8938  padding: 12px 20px;
8939  border: none;
8940  background: none;
8941  cursor: pointer;
8942  font-size: 15px;
8943  font-weight: 500;
8944  color: #6b7280;
8945  border-bottom: 3px solid transparent;
8946  transition: all 0.2s ease;
8947  position: relative;
8948  top: 2px;
8949}
8950
8951.tab-btn:hover {
8952  color: #111827;
8953}
8954
8955.tab-btn.active {
8956  color: #2563eb;
8957  border-bottom-color: #2563eb;
8958}
8959
8960.tab-content {
8961  padding: 20px 0;
8962}
8963
8964/* Review Statistics */
8965.review-stats {
8966  display: grid;
8967  grid-template-columns: 200px 1fr;
8968  gap: 32px;
8969  padding: 24px;
8970  background: #f9fafb;
8971  border-radius: 12px;
8972  margin-bottom: 24px;
8973}
8974
8975.rating-summary {
8976  text-align: center;
8977}
8978
8979.avg-rating {
8980  display: block;
8981  font-size: 48px;
8982  font-weight: 700;
8983  color: #111827;
8984  line-height: 1;
8985}
8986
8987.rating-summary .stars {
8988  font-size: 24px;
8989  color: #fbbf24;
8990  margin: 8px 0;
8991}
8992
8993.total-reviews {
8994  display: block;
8995  font-size: 14px;
8996  color: #6b7280;
8997  margin-top: 4px;
8998}
8999
9000.rating-breakdown {
9001  display: flex;
9002  flex-direction: column;
9003  gap: 8px;
9004}
9005
9006.rating-bar {
9007  display: flex;
9008  align-items: center;
9009  gap: 12px;
9010}
9011
9012.rating-label {
9013  font-size: 14px;
9014  color: #374151;
9015  width: 32px;
9016  text-align: right;
9017}
9018
9019.bar-container {
9020  flex: 1;
9021  height: 8px;
9022  background: #e5e7eb;
9023  border-radius: 4px;
9024  overflow: hidden;
9025}
9026
9027.bar-fill {
9028  height: 100%;
9029  background: linear-gradient(90deg, #fbbf24 0%, #f59e0b 100%);
9030  transition: width 0.3s ease;
9031}
9032
9033.rating-count {
9034  font-size: 14px;
9035  color: #6b7280;
9036  width: 40px;
9037  text-align: right;
9038}
9039
9040/* Review Filters */
9041.review-filters {
9042  display: flex;
9043  justify-content: space-between;
9044  align-items: center;
9045  margin-bottom: 20px;
9046}
9047
9048.review-sort {
9049  padding: 8px 12px;
9050  border: 1px solid #d1d5db;
9051  border-radius: 8px;
9052  font-size: 14px;
9053  background: white;
9054}
9055
9056.write-review-btn {
9057  padding: 10px 20px;
9058  background: #2563eb;
9059  color: white;
9060  border: none;
9061  border-radius: 8px;
9062  font-weight: 500;
9063  cursor: pointer;
9064  transition: background 0.2s;
9065}
9066
9067.write-review-btn:hover {
9068  background: #1d4ed8;
9069}
9070
9071/* Review List */
9072.reviews-list {
9073  display: flex;
9074  flex-direction: column;
9075  gap: 16px;
9076}
9077
9078.review-item {
9079  padding: 20px;
9080  background: white;
9081  border: 1px solid #e5e7eb;
9082  border-radius: 12px;
9083  transition: box-shadow 0.2s;
9084}
9085
9086.review-item:hover {
9087  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
9088}
9089
9090.review-header {
9091  display: flex;
9092  justify-content: space-between;
9093  align-items: flex-start;
9094  margin-bottom: 8px;
9095}
9096
9097.review-author {
9098  display: flex;
9099  align-items: center;
9100  gap: 8px;
9101}
9102
9103.review-author strong {
9104  font-size: 15px;
9105  color: #111827;
9106}
9107
9108.verified-badge {
9109  display: inline-flex;
9110  align-items: center;
9111  gap: 4px;
9112  font-size: 12px;
9113  color: #059669;
9114  background: #d1fae5;
9115  padding: 2px 8px;
9116  border-radius: 4px;
9117  font-weight: 500;
9118}
9119
9120.anonymous-badge {
9121  display: inline-flex;
9122  align-items: center;
9123  font-size: 12px;
9124  color: #6b7280;
9125  background: #f3f4f6;
9126  padding: 2px 6px;
9127  border-radius: 4px;
9128  margin-left: 4px;
9129  cursor: help;
9130}
9131
9132.review-rating .stars {
9133  font-size: 16px;
9134  color: #fbbf24;
9135}
9136
9137.review-date {
9138  font-size: 13px;
9139  color: #9ca3af;
9140  margin-bottom: 12px;
9141}
9142
9143.review-text {
9144  font-size: 15px;
9145  line-height: 1.6;
9146  color: #374151;
9147  margin-bottom: 12px;
9148}
9149
9150.review-images {
9151  display: flex;
9152  gap: 8px;
9153  margin-bottom: 12px;
9154  flex-wrap: wrap;
9155}
9156
9157.review-img {
9158  width: 80px;
9159  height: 80px;
9160  object-fit: cover;
9161  border-radius: 8px;
9162  cursor: pointer;
9163  transition: transform 0.2s;
9164}
9165
9166.review-img:hover {
9167  transform: scale(1.05);
9168}
9169
9170.review-actions {
9171  display: flex;
9172  gap: 12px;
9173  padding-top: 12px;
9174  border-top: 1px solid #f3f4f6;
9175}
9176
9177.btn-helpful {
9178  display: flex;
9179  align-items: center;
9180  gap: 6px;
9181  padding: 6px 12px;
9182  background: #f9fafb;
9183  border: 1px solid #e5e7eb;
9184  border-radius: 6px;
9185  font-size: 13px;
9186  color: #6b7280;
9187  cursor: pointer;
9188  transition: all 0.2s;
9189}
9190
9191.btn-helpful:hover {
9192  background: #f3f4f6;
9193  color: #111827;
9194  border-color: #d1d5db;
9195}
9196
9197.btn-helpful.voted {
9198  background: #dbeafe;
9199  color: #1d4ed8;
9200  border-color: #93c5fd;
9201}
9202
9203.helpful-icon {
9204  font-size: 14px;
9205}
9206
9207.no-reviews {
9208  text-align: center;
9209  padding: 60px 20px;
9210  color: #9ca3af;
9211  font-size: 15px;
9212}
9213
9214.load-more-btn {
9215  display: block;
9216  margin: 24px auto 0;
9217  padding: 12px 32px;
9218  background: white;
9219  border: 1px solid #d1d5db;
9220  border-radius: 8px;
9221  font-size: 14px;
9222  font-weight: 500;
9223  color: #374151;
9224  cursor: pointer;
9225  transition: all 0.2s;
9226}
9227
9228.load-more-btn:hover {
9229  background: #f9fafb;
9230  border-color: #9ca3af;
9231}
9232
9233/* ===================================
9234   POINT BALANCE WIDGET (LEGACY - kept for backward compatibility)
9235   =================================== */
9236
9237.point-balance-card {
9238  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
9239  color: white;
9240  padding: 24px;
9241  border-radius: 16px;
9242  margin-bottom: 24px;
9243  box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3);
9244}
9245
9246.point-header {
9247  display: flex;
9248  justify-content: space-between;
9249  align-items: center;
9250  margin-bottom: 12px;
9251}
9252
9253.point-header h3 {
9254  font-size: 18px;
9255  font-weight: 600;
9256  margin: 0;
9257  display: flex;
9258  align-items: center;
9259  gap: 8px;
9260}
9261
9262.point-header .balance {
9263  font-size: 32px;
9264  font-weight: 700;
9265}
9266
9267.point-value {
9268  font-size: 14px;
9269  opacity: 0.9;
9270  margin: 8px 0 16px;
9271}
9272
9273.point-stats {
9274  display: flex;
9275  gap: 20px;
9276  font-size: 13px;
9277  opacity: 0.85;
9278  margin-bottom: 16px;
9279}
9280
9281.btn-view-history {
9282  display: inline-block;
9283  padding: 10px 20px;
9284  background: rgba(255, 255, 255, 0.2);
9285  color: white;
9286  text-decoration: none;
9287  border-radius: 8px;
9288  font-size: 14px;
9289  font-weight: 500;
9290  transition: background 0.2s;
9291}
9292
9293.btn-view-history:hover {
9294  background: rgba(255, 255, 255, 0.3);
9295}
9296
9297/* ===================================
9298   CHECKOUT POINT REDEMPTION
9299   =================================== */
9300
9301.checkout-points {
9302  padding: 16px;
9303  background: #fef3c7;
9304  border: 2px solid #fbbf24;
9305  border-radius: 12px;
9306  margin-bottom: 16px;
9307}
9308
9309.points-header {
9310  display: flex;
9311  align-items: center;
9312  gap: 10px;
9313  margin-bottom: 12px;
9314}
9315
9316.points-header input[type="checkbox"] {
9317  width: 18px;
9318  height: 18px;
9319  cursor: pointer;
9320}
9321
9322.points-header label {
9323  font-size: 15px;
9324  color: #78350f;
9325  cursor: pointer;
9326  margin: 0;
9327}
9328
9329#points-slider {
9330  padding: 16px;
9331  background: white;
9332  border-radius: 8px;
9333  margin-top: 12px;
9334}
9335
9336#points-slider label {
9337  display: block;
9338  font-size: 14px;
9339  color: #374151;
9340  margin-bottom: 8px;
9341  font-weight: 500;
9342}
9343
9344#points-amount {
9345  width: 100%;
9346  margin-bottom: 8px;
9347}
9348
9349#points-display {
9350  display: flex;
9351  justify-content: space-between;
9352  font-size: 14px;
9353  color: #111827;
9354  margin-top: 8px;
9355}
9356
9357#points-display .discount-amount {
9358  color: #059669;
9359  font-weight: 600;
9360}
9361
9362.price-row.point-discount {
9363  color: #059669;
9364  font-weight: 500;
9365}
9366
9367/* ===================================
9368   REVIEWABLE ORDERS
9369   =================================== */
9370
9371.reviewable-orders {
9372  background: white;
9373  border: 1px solid #e5e7eb;
9374  border-radius: 12px;
9375  padding: 20px;
9376  margin-bottom: 24px;
9377}
9378
9379.reviewable-orders h3 {
9380  font-size: 18px;
9381  margin-bottom: 16px;
9382  color: #111827;
9383}
9384
9385.order-card {
9386  display: flex;
9387  gap: 16px;
9388  padding: 16px;
9389  background: #f9fafb;
9390  border-radius: 8px;
9391  margin-bottom: 12px;
9392}
9393
9394.order-card:last-child {
9395  margin-bottom: 0;
9396}
9397
9398.order-card img {
9399  width: 80px;
9400  height: 80px;
9401  object-fit: cover;
9402  border-radius: 8px;
9403}
9404
9405.order-info {
9406  flex: 1;
9407}
9408
9409.order-info h4 {
9410  font-size: 15px;
9411  margin: 0 0 4px;
9412  color: #111827;
9413}
9414
9415.order-info p {
9416  font-size: 13px;
9417  color: #6b7280;
9418  margin: 0 0 12px;
9419}
9420
9421.btn-review {
9422  padding: 8px 16px;
9423  background: #059669;
9424  color: white;
9425  border: none;
9426  border-radius: 6px;
9427  font-size: 14px;
9428  font-weight: 500;
9429  cursor: pointer;
9430  transition: background 0.2s;
9431}
9432
9433.btn-review:hover {
9434  background: #047857;
9435}
9436
9437/* ===================================
9438   TOAST NOTIFICATIONS
9439   =================================== */
9440
9441.review-toast {
9442  position: fixed;
9443  bottom: 24px;
9444  right: 24px;
9445  padding: 14px 20px;
9446  background: #111827;
9447  color: white;
9448  border-radius: 8px;
9449  font-size: 14px;
9450  line-height: 1.4;
9451  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
9452  transform: translateY(100px);
9453  opacity: 0;
9454  transition: all 0.3s ease;
9455  z-index: 10000;
9456  max-width: 400px;
9457  word-wrap: break-word;
9458}
9459
9460.review-toast.show {
9461  transform: translateY(0);
9462  opacity: 1;
9463}
9464
9465/* Mobile: Position above bottom-nav */
9466@media (max-width: 768px) {
9467  .review-toast {
9468    bottom: 80px; /* Above bottom-nav (50px height + padding + safe margin) */
9469    right: 16px;
9470    left: 16px;
9471    max-width: calc(100% - 32px);
9472  }
9473}
9474
9475.review-toast.toast-success {
9476  background: #059669;
9477}
9478
9479.review-toast.toast-error {
9480  background: #dcce77;
9481}
9482
9483.review-toast.toast-info {
9484  background: #2563eb;
9485}
9486
9487/* ===================================
9488   RESPONSIVE
9489   =================================== */
9490
9491@media (max-width: 768px) {
9492  .point-balance-card {
9493    border-radius: 0;
9494  }
9495  
9496  .review-stats {
9497    grid-template-columns: 70px 1fr;
9498    gap: 20px;
9499    padding: 16px;
9500  }
9501
9502  .rating-summary {
9503    text-align: center;
9504  }
9505  
9506  .rating-summary .avg-rating {
9507    font-size: 32px;
9508  }
9509  
9510  .rating-summary .stars {
9511    font-size: 18px;
9512    margin: 4px 0;
9513  }
9514  
9515  .total-reviews {
9516    font-size: 11px;
9517  }
9518  
9519  /* Rating breakdown - more compact */
9520  .rating-breakdown {
9521    gap: 2px;
9522  }
9523  
9524  .rating-breakdown .rating-bar {
9525    gap: 4px;
9526  }
9527  
9528  .rating-breakdown .rating-label {
9529    font-size: 11px;
9530    width: 16px;
9531    min-width: 16px;
9532    text-align: left;
9533  }
9534  
9535  .rating-breakdown .bar-container {
9536    height: 6px;
9537  }
9538  
9539  .rating-breakdown .rating-count {
9540    font-size: 11px;
9541    width: 16px;
9542    min-width: 16px;
9543  }
9544  
9545  .review-header {
9546    flex-direction: column;
9547    gap: 8px;
9548    align-items: flex-start;
9549  }
9550  
9551  .review-rating {
9552    order: -1;
9553    margin-bottom: 4px;
9554  }
9555  
9556  .review-rating .stars {
9557    font-size: 14px;
9558  }
9559
9560  .review-filters {
9561    flex-direction: column;
9562    gap: 12px;
9563    align-items: stretch;
9564  }
9565
9566  .review-sort {
9567    width: 100%;
9568  }
9569
9570  .point-header .balance {
9571    font-size: 24px;
9572  }
9573
9574  .point-stats {
9575    flex-direction: column;
9576    gap: 8px;
9577  }
9578
9579  .order-card {
9580    flex-direction: column;
9581  }
9582
9583  .order-card img {
9584    width: 100%;
9585    height: 200px;
9586  }
9587}
9588
9589/* ===================================
9590   LOADING & ERROR STATES
9591   =================================== */
9592
9593.review-loading {
9594  text-align: center;
9595  padding: 40px;
9596  color: #9ca3af;
9597  font-size: 14px;
9598}
9599
9600.review-error {
9601  text-align: center;
9602  padding: 40px;
9603  color: #dcce77;
9604  font-size: 14px;
9605  background: #fee2e2;
9606  border-radius: 8px;
9607}
9608
9609.review-empty {
9610  text-align: center;
9611  padding: 10px;
9612  color: #6b7280;
9613  font-size: 14px;
9614}
9615
9616/* ===================================
9617   REVIEWABLE ORDERS LIST
9618   =================================== */
9619
9620.reviewable-orders-list {
9621  display: flex;
9622  flex-direction: column;
9623  gap: 16px;
9624}
9625
9626.reviewable-order-item {
9627  display: grid;
9628  grid-template-columns: 80px 1fr auto;
9629  gap: 16px;
9630  padding: 16px;
9631  background: #fff;
9632  border: 1px solid #e5e7eb;
9633  border-radius: 12px;
9634  align-items: center;
9635  transition: box-shadow 0.2s;
9636}
9637
9638.reviewable-order-item:hover {
9639  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
9640}
9641
9642.reviewable-order-item .roi-image {
9643  width: 80px;
9644  height: 80px;
9645  object-fit: cover;
9646  border-radius: 8px;
9647  border: 1px solid #e5e7eb;
9648}
9649
9650.reviewable-order-item .roi-info {
9651  display: flex;
9652  flex-direction: column;
9653  gap: 4px;
9654}
9655
9656.reviewable-order-item .roi-info h4 {
9657  margin: 0;
9658  font-size: 15px;
9659  font-weight: 600;
9660  color: #111827;
9661  line-height: 1.4;
9662}
9663
9664.reviewable-order-item .roi-meta {
9665  margin: 0;
9666  font-size: 13px;
9667  color: #6b7280;
9668}
9669
9670.reviewable-order-item .btn-write-review {
9671  white-space: nowrap;
9672  padding: 10px 20px;
9673  font-size: 14px;
9674}
9675
9676@media (max-width: 640px) {
9677  .reviewable-order-item {
9678    grid-template-columns: 60px 1fr;
9679    gap: 12px;
9680  }
9681  
9682  .reviewable-order-item .roi-image {
9683    width: 60px;
9684    height: 60px;
9685  }
9686  
9687  .reviewable-order-item .btn-write-review {
9688    grid-column: 1 / -1;
9689    width: 100%;
9690  }
9691}
9692
9693/* ===================================
9694   REVIEW SUBMISSION MODAL
9695   =================================== */
9696
9697/* Modal Overlay Background */
9698#review-modal-overlay {
9699  position: fixed;
9700  top: 0;
9701  left: 0;
9702  right: 0;
9703  bottom: 0;
9704  background: rgba(0, 0, 0, 0.6) !important;
9705  backdrop-filter: blur(8px);
9706  display: flex;
9707  align-items: center;
9708  justify-content: center;
9709  z-index: 99999 !important;
9710  padding: 20px;
9711}
9712
9713.review-modal {
9714  max-width: 600px !important;
9715  width: 90% !important;
9716  background: white;
9717  border-radius: 16px !important;
9718  box-shadow: 0 25px 50px rgba(0, 0, 0, 0.25);
9719  max-height: 90vh;
9720  display: flex;
9721  flex-direction: column;
9722  overflow: hidden !important;
9723}
9724
9725#review-modal-overlay .modal-content,
9726.review-modal .modal-content {
9727  padding: 0 !important;
9728  flex: initial !important;
9729  overflow-y: visible !important;
9730}
9731
9732.review-modal .modal-header {
9733  display: flex;
9734  justify-content: space-between;
9735  align-items: center;
9736  padding: 20px 24px;
9737  border-bottom: 1px solid #e5e7eb;
9738}
9739
9740.review-modal .modal-title {
9741  margin: 0;
9742  font-size: 20px;
9743  font-weight: 700;
9744  color: #111827;
9745}
9746
9747.review-modal .modal-close {
9748  background: none;
9749  border: none;
9750  cursor: pointer;
9751  padding: 4px;
9752  color: #6b7280;
9753  transition: color 0.2s;
9754  display: flex;
9755  align-items: center;
9756  justify-content: center;
9757}
9758
9759.review-modal .modal-close:hover {
9760  color: #111827;
9761}
9762
9763.review-modal .modal-body {
9764  padding: 24px;
9765  max-height: calc(90vh - 140px);
9766  overflow-y: auto;
9767}
9768
9769/* Custom Scrollbar for Modal */
9770.review-modal .modal-body::-webkit-scrollbar {
9771  width: 8px;
9772}
9773
9774.review-modal .modal-body::-webkit-scrollbar-track {
9775  background: #f1f5f9;
9776  border-radius: 4px;
9777}
9778
9779.review-modal .modal-body::-webkit-scrollbar-thumb {
9780  background: linear-gradient(180deg, #3b82f6 0%, #2563eb 100%);
9781  border-radius: 4px;
9782}
9783
9784.review-modal .modal-body::-webkit-scrollbar-thumb:hover {
9785  background: linear-gradient(180deg, #2563eb 0%, #1d4ed8 100%);
9786}
9787
9788/* Firefox Scrollbar */
9789.review-modal .modal-body {
9790  scrollbar-width: thin;
9791  scrollbar-color: #3b82f6 #f1f5f9;
9792}
9793
9794.review-modal .modal-footer {
9795  display: flex;
9796  gap: 12px;
9797  margin-top: 24px;
9798  padding-top: 20px;
9799  border-top: 1px solid #f1f5f9;
9800}
9801
9802.review-product-info {
9803  display: flex;
9804  gap: 16px;
9805  padding: 16px;
9806  background: #f9fafb;
9807  border-radius: 8px;
9808  margin-bottom: 24px;
9809}
9810
9811.review-product-thumb {
9812  width: 80px;
9813  height: 80px;
9814  object-fit: cover;
9815  border-radius: 6px;
9816  border: 1px solid #e5e7eb;
9817}
9818
9819.review-product-details h4 {
9820  margin: 0 0 8px 0;
9821  font-size: 16px;
9822  font-weight: 600;
9823  color: #111827;
9824}
9825
9826.review-product-details p {
9827  margin: 0;
9828  font-size: 13px;
9829  color: #6b7280;
9830}
9831
9832/* Star Rating Input */
9833.star-rating-input {
9834  display: inline-flex;
9835  gap: 8px;
9836  margin-right: 12px;
9837  vertical-align: middle;
9838}
9839
9840.star-btn {
9841  background: none;
9842  border: none;
9843  font-size: 32px;
9844  color: #d1d5db;
9845  cursor: pointer;
9846  transition: all 0.2s;
9847  padding: 0;
9848  line-height: 1;
9849}
9850
9851.star-btn:hover,
9852.star-btn.active {
9853  color: #fbbf24;
9854  transform: scale(1.1);
9855}
9856
9857.rating-label {
9858  display: inline-block;
9859  font-size: 15px;
9860  color: #6b7280;
9861  margin: 0;
9862  vertical-align: middle;
9863  min-width: 150px;
9864}
9865
9866/* Form Elements */
9867.review-form .form-group {
9868  margin-bottom: 20px;
9869}
9870
9871.review-form .form-label {
9872  display: block;
9873  font-size: 14px;
9874  font-weight: 600;
9875  color: #374151;
9876  margin-bottom: 8px;
9877}
9878
9879.review-form .form-control {
9880  width: 100%;
9881  padding: 12px;
9882  border: 1px solid #d1d5db;
9883  border-radius: 8px;
9884  font-size: 14px;
9885  font-family: inherit;
9886  transition: border-color 0.2s;
9887}
9888
9889.review-form .form-control:focus {
9890  outline: none;
9891  border-color: #2563eb;
9892  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
9893}
9894
9895.review-form .form-hint {
9896  font-size: 13px;
9897  color: #6b7280;
9898  margin-top: 8px;
9899}
9900
9901/* Image Upload */
9902.btn-upload-trigger {
9903  display: inline-flex;
9904  align-items: center;
9905  gap: 8px;
9906  padding: 10px 16px;
9907  background: #f9fafb;
9908  border: 2px dashed #d1d5db;
9909  border-radius: 8px;
9910  color: #374151;
9911  cursor: pointer;
9912  font-size: 14px;
9913  font-weight: 500;
9914  transition: all 0.2s;
9915}
9916
9917.btn-upload-trigger:hover {
9918  background: #f3f4f6;
9919  border-color: #2563eb;
9920  color: #2563eb;
9921}
9922
9923.image-preview-grid {
9924  display: grid;
9925  grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
9926  gap: 12px;
9927  margin-top: 16px;
9928}
9929
9930.image-preview-item {
9931  position: relative;
9932  aspect-ratio: 1;
9933  border-radius: 8px;
9934  overflow: hidden;
9935  border: 1px solid #e5e7eb;
9936}
9937
9938.image-preview-item img {
9939  width: 100%;
9940  height: 100%;
9941  object-fit: cover;
9942}
9943
9944.remove-image {
9945  position: absolute;
9946  top: 4px;
9947  right: 4px;
9948  background: rgba(0, 0, 0, 0.7);
9949  border: none;
9950  color: white;
9951  width: 24px;
9952  height: 24px;
9953  border-radius: 50%;
9954  cursor: pointer;
9955  display: flex;
9956  align-items: center;
9957  justify-content: center;
9958  transition: background 0.2s;
9959}
9960
9961.remove-image:hover {
9962  background: rgba(0, 0, 0, 0.9);
9963}
9964
9965/* Point Reward Info */
9966.point-reward-info {
9967  background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
9968  border: 2px solid #fbbf24;
9969  border-radius: 12px;
9970  padding: 16px;
9971  margin: 20px 0;
9972}
9973
9974.pri-header {
9975  display: flex;
9976  align-items: center;
9977  gap: 8px;
9978  font-size: 16px;
9979  font-weight: 700;
9980  color: #92400e;
9981  margin-bottom: 12px;
9982}
9983
9984.pri-list {
9985  list-style: none;
9986  padding: 0;
9987  margin: 0 0 12px 0;
9988}
9989
9990.pri-list li {
9991  padding: 6px 0;
9992  font-size: 14px;
9993  color: #78350f;
9994  transition: opacity 0.2s;
9995}
9996
9997.pri-total {
9998  padding-top: 12px;
9999  border-top: 2px solid rgba(251, 191, 36, 0.3);
10000  font-size: 16px;
10001  color: #92400e;
10002  font-weight: 600;
10003}
10004
10005.form-error {
10006  background: #fee2e2;
10007  border: 1px solid #f87171;
10008  color: #991b1b;
10009  padding: 12px;
10010  border-radius: 8px;
10011  font-size: 14px;
10012  margin-top: 16px;
10013}
10014
10015/* Buttons */
10016.review-form .btn {
10017  padding: 12px 24px;
10018  border: none;
10019  border-radius: 8px;
10020  font-size: 15px;
10021  font-weight: 600;
10022  cursor: pointer;
10023  transition: all 0.2s;
10024}
10025
10026.review-form .btn-primary {
10027  background: #2563eb;
10028  color: white;
10029}
10030
10031.review-form .btn-primary:hover {
10032  background: #1d4ed8;
10033}
10034
10035.review-form .btn-primary:disabled {
10036  background: #94a3b8;
10037  cursor: not-allowed;
10038}
10039
10040.review-form .btn-secondary {
10041  background: #f1f5f9;
10042  color: #64748b;
10043}
10044
10045.review-form .btn-secondary:hover {
10046  background: #e2e8f0;
10047  color: #475569;
10048}
10049
10050@media (max-width: 768px) {
10051    #review-modal-overlay { padding: 0; }
10052    .review-modal { max-width: 100%; width: 100% !important; }
10053}
10054
10055/* ===================================
10056   POINT SUMMARY GRID (Profile Page)
10057   =================================== */
10058
10059.point-summary-grid {
10060  display: grid;
10061  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
10062  gap: 12px;
10063  margin-bottom: 32px;
10064}
10065
10066.point-summary-card {
10067  background: linear-gradient(135deg, #f9fafb 0%, #ffffff 100%);
10068  border: 1px solid #e5e7eb;
10069  border-radius: 12px;
10070  padding: 16px 20px;
10071  display: flex;
10072  align-items: center;
10073  justify-content: space-between;
10074  gap: 12px;
10075  transition: all 0.2s;
10076}
10077
10078.point-summary-card:hover {
10079  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
10080  transform: translateY(-2px);
10081}
10082
10083.point-summary-card .psc-left {
10084  display: flex;
10085  flex-direction: column;
10086  gap: 2px;
10087}
10088
10089.point-summary-card .psc-icon {
10090  font-size: 24px;
10091  margin-bottom: 4px;
10092}
10093
10094.point-summary-card .psc-label {
10095  font-size: 13px;
10096  color: #6b7280;
10097  font-weight: 500;
10098  text-transform: uppercase;
10099  letter-spacing: 0.5px;
10100}
10101
10102.point-summary-card .psc-value {
10103  font-size: 26px;
10104  font-weight: 700;
10105  color: #111827;
10106  flex-shrink: 0;
10107}
10108
10109/* Point History Section */
10110.point-history-section {
10111  margin-top: 32px;
10112}
10113
10114.point-history-section h3 {
10115  font-size: 18px;
10116  font-weight: 600;
10117  color: #111827;
10118  margin-bottom: 16px;
10119}
10120
10121.point-history-list {
10122  display: flex;
10123  flex-direction: column;
10124  gap: 12px;
10125}
10126
10127.point-history-item {
10128  display: grid;
10129  grid-template-columns: 40px 1fr auto;
10130  gap: 16px;
10131  padding: 16px;
10132  background: white;
10133  border: 1px solid #e5e7eb;
10134  border-radius: 10px;
10135  align-items: center;
10136  transition: all 0.2s;
10137}
10138
10139.point-history-item:hover {
10140  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
10141}
10142
10143.point-history-item.credit {
10144  border-left: 3px solid #10b981;
10145}
10146
10147.point-history-item.debit {
10148  border-left: 3px solid #f59e0b;
10149}
10150
10151.point-history-item .phi-icon {
10152  width: 40px;
10153  height: 40px;
10154  display: flex;
10155  align-items: center;
10156  justify-content: center;
10157  font-size: 20px;
10158  border-radius: 50%;
10159  background: #f9fafb;
10160}
10161
10162.point-history-item.credit .phi-icon {
10163  background: #d1fae5;
10164}
10165
10166.point-history-item.debit .phi-icon {
10167  background: #fef3c7;
10168}
10169
10170.point-history-item .phi-details {
10171  display: flex;
10172  flex-direction: column;
10173  gap: 4px;
10174}
10175
10176.point-history-item .phi-desc {
10177  font-size: 15px;
10178  font-weight: 500;
10179  color: #111827;
10180}
10181
10182.point-history-item .phi-date {
10183  font-size: 13px;
10184  color: #9ca3af;
10185}
10186
10187.point-history-item .phi-amount {
10188  font-size: 18px;
10189  font-weight: 700;
10190}
10191
10192.point-history-item .phi-amount.credit {
10193  color: #10b981;
10194}
10195
10196.point-history-item .phi-amount.debit {
10197  color: #f59e0b;
10198}
10199
10200@media (max-width: 640px) {
10201  .point-summary-grid {
10202    grid-template-columns: 1fr;
10203  }
10204  
10205  .point-summary-card .psc-value {
10206    font-size: 22px;
10207  }
10208
10209  .point-history-item {
10210    grid-template-columns: 36px 1fr;
10211    gap: 12px;
10212  }
10213  
10214  .point-history-item .phi-amount {
10215    grid-column: 2;
10216    text-align: right;
10217    margin-top: 8px;
10218  }
10219}
10220
10221/* ── Pending points: compact widget indicator ── */
10222.pbc-pending {
10223  font-size: 12px;
10224  opacity: 0.85;
10225  margin-top: 4px;
10226  color: #fde68a;
10227}
10228
10229/* ── Pending points: summary card ── */
10230.point-summary-card.psc-pending-card {
10231  background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);
10232  border-color: #fcd34d;
10233}
10234
10235.point-summary-card.psc-pending-card .psc-value {
10236  color: #b45309;
10237}
10238
10239.point-summary-card .psc-sub {
10240  font-size: 11px;
10241  color: #9ca3af;
10242  margin-top: 4px;
10243}
10244
10245/* ── Pending points: history item ── */
10246.point-history-item.phi-pending {
10247  opacity: 0.75;
10248  border-left-color: #fcd34d;
10249  background: #fffbeb;
10250}
10251
10252.point-history-item.phi-pending .phi-icon {
10253  background: #fef3c7;
10254}
10255
10256.phi-pending-badge {
10257  display: inline-block;
10258  font-size: 11px;
10259  font-weight: 600;
10260  padding: 2px 7px;
10261  border-radius: 10px;
10262  background: #fef3c7;
10263  color: #92400e;
10264  border: 1px solid #fcd34d;
10265  margin-left: 8px;
10266  vertical-align: middle;
10267}
10268
10269/* ── Reversed points: history item ── */
10270.point-history-item.phi-reversed {
10271  opacity: 0.55;
10272  border-left-color: #d1d5db;
10273  background: #f9fafb;
10274}
10275
10276.point-history-item.phi-reversed .phi-icon {
10277  background: #f3f4f6;
10278}
10279
10280.point-history-item.phi-reversed .phi-amount {
10281  text-decoration: line-through;
10282  color: #9ca3af !important;
10283}
10284
10285.phi-reversed-badge {
10286  display: inline-block;
10287  font-size: 11px;
10288  font-weight: 600;
10289  padding: 2px 7px;
10290  border-radius: 10px;
10291  background: #fee2e2;
10292  color: #991b1b;
10293  border: 1px solid #fca5a5;
10294  margin-left: 8px;
10295  vertical-align: middle;
10296}
10297
10298</style>
10299            
10300</head>
10301<body>
10302<header class="topbar">
10303  <div class="container topbar-inner">
10304    <div class="topbar-left">
10305      Apa Itu BERLINTOTO | Situs Toto Slot Gacor & Bandar Togel Online 4D Resmi
10306    </div>
10307    <div class="topbar-right">
10308      <a href="https://www.bim.miami/">TOGEL</a>
10309      <a href="https://www.bim.miami/">BANDAR TOGEL</a>
10310      <a href="https://www.bim.miami/">BERLINTOTO</a>
10311      <a href="https://www.bim.miami/">BANDAR BERLINTOTO</a>
10312    </div>
10313  </div>
10314</header>
10315<nav class="navbar">
10316  <nav class="navbar-logo" style="width: 100%;">
10317    <div class="container nav-inner" style="justify-items: center; justify-content: center;">
10318        <a href="https://www.bim.miami/" class="logo-mobile" title="BERLINTOTO">
10319          <img src="https://www.bim.miami/logoberlintoto.png" alt="BERLINTOTO" height="40">
10320        </a>
10321    </div>
10322</nav>
10323  <div class="container nav-inner">
10324    <a href="https://www.bim.miami/" class="logo" title="BERLINTOTO">
10325      <img src="https://www.bim.miami/logoberlintoto.png" alt="BERLINTOTO" height="40">
10326    </a>
10327
10328    
10329    <form class="search" method="get" action="#">
10330      <input type="hidden" name="page" value="produk">
10331      
10332      <!-- Search Icon (Desktop) -->
10333      <button type="submit" class="search-icon-btn" id="searchBtn" aria-label="Cari">
10334        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
10335          <circle cx="11" cy="11" r="8"/>
10336          <path d="m21 21-4.35-4.35"/>
10337        </svg>
10338      </button>
10339      
10340      <input type="text" name="q" id="searchInput" placeholder="Apa Carik Palku?"
10341             value="" autocomplete="off"> 
10342      
10343      <div class="search-suggest" id="searchSuggest" hidden></div>
10344    </form>
10345
10346    <div class="nav-actions">
10347      <!--<button class="icon-btn" id="notifBtn" title="Notifikasi">🔔</button>-->
10348      <button class="icon-btn cart-indicator" id="cartBtn" title="Keranjang">
10349        🛒 <span id="cartCount" class="badge" data-role="cart-badge" aria-live="polite">0</span>
10350      </button>
10351
10352      <button class="icon-btn compare-indicator" id="compareBtn" title="Perbandingan">
10353        ⚖️ <span id="compareCount" class="badge" data-role="compare-badge" aria-live="polite">0</span>
10354      </button>
10355      
10356      <!-- Dynamic login/logout button -->
10357            <button class="login-btn" id="loginBtn" aria-label="Masuk">
10358        <span class="login-text">Masuk</span>
10359        <span class="login-ico" aria-hidden="true">😊</span>
10360      </button>
10361          </div>
10362  </div>
10363
10364  <div class="container" style="display: flex; align-items: center;">
10365      
10366
10367      <div class="mega-cats-wrapper">
10368        <div class="mega-cats container" id="megaCatsScroll" style="padding-left: 5px !important;">
10369                      <a class="" href="https://www.bim.miami/">
10370              BERLINTOTO           </a>
10371                      <a class="" href="https://www.bim.miami/">
10372              BERLIN TOTO            </a>
10373                      <a class="" href="https://www.bim.miami/">
10374              LOGIN BERLINTOTO            </a>
10375                      <a class="" href="https://www.bim.miami/">
10376              DAFTAR BERLINTOTO           </a>
10377                      <a class="" href="https://www.bim.miami/">
10378              LINK BERLINTOTO            </a>
10379                      <a class="" href="https://www.bim.miami/">
10380              SLOT GACOR            </a>
10381                  </div>
10382        <div class="mega-cats-fade" id="megaCatsFade"></div>
10383      </div>
10384  </div>
10385</nav>
10386
10387<!-- Panel Mega Menu - Outside navbar for full-screen -->
10388<div class="mm2-panel" data-mm2="panel" role="dialog" aria-label="Kategori">
10389  <div class="mm2-view is-active" data-mm2="top"></div>
10390  <div class="mm2-view" data-mm2="detail"></div>
10391</div>
10392
10393<!-- Backdrop - Outside navbar for full-screen -->
10394<div class="mm2-backdrop" data-mm2="backdrop"></div>
10395
10396<!-- Mobile Bottom Navigation -->
10397<nav class="bottom-nav">
10398  <div class="bottom-nav-inner">
10399    <!-- Home -->
10400    <a href="/" class="bottom-nav-item" id="bottomHomeBtn" data-page="home">
10401      <div class="bottom-nav-item-icon">
10402        <!-- Outlined icon (default) -->
10403        <svg class="icon-outlined" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
10404          <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
10405          <polyline points="9 22 9 12 15 12 15 22"/>
10406        </svg>
10407        <!-- Filled icon (active) -->
10408        <svg class="icon-filled" viewBox="0 0 24 24" fill="currentColor">
10409          <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"/>
10410          <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"/>
10411        </svg>
10412      </div>
10413      <span class="bottom-nav-item-label">Home</span>
10414    </a>
10415
10416    <!-- Kategori -->
10417    <button class="bottom-nav-item" id="bottomCategoryBtn" data-mm2="trigger" aria-label="Kategori">
10418      <div class="bottom-nav-item-icon">
10419        <!-- Outlined icon (default) -->
10420        <svg class="icon-outlined" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
10421          <line x1="3" y1="12" x2="21" y2="12"/>
10422          <line x1="3" y1="6" x2="21" y2="6"/>
10423          <line x1="3" y1="18" x2="21" y2="18"/>
10424        </svg>
10425        <!-- Filled icon (active) -->
10426        <svg class="icon-filled" viewBox="0 0 24 24" fill="currentColor">
10427          <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"/>
10428        </svg>
10429      </div>
10430      <span class="bottom-nav-item-label">Kategori</span>
10431    </button>
10432
10433    <!-- Keranjang -->
10434    <button class="bottom-nav-item" id="bottomCartBtn">
10435      <div class="bottom-nav-item-icon">
10436        <!-- Outlined icon (default) -->
10437        <svg class="icon-outlined" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
10438          <circle cx="9" cy="21" r="1"/>
10439          <circle cx="20" cy="21" r="1"/>
10440          <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/>
10441        </svg>
10442        <!-- Filled icon (active) -->
10443        <svg class="icon-filled" viewBox="0 0 24 24" fill="currentColor">
10444          <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"/>
10445        </svg>
10446        <span class="bottom-nav-item-badge" id="bottomCartCount">0</span>
10447      </div>
10448      <span class="bottom-nav-item-label">Keranjang</span>
10449    </button>
10450
10451    <!-- Compare -->
10452    <button class="bottom-nav-item" id="bottomCompareBtn">
10453      <div class="bottom-nav-item-icon">
10454        <!-- Outlined icon (default) -->
10455        <svg class="icon-outlined" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
10456          <polyline points="17 11 21 7 17 3"/>
10457          <path d="M21 7H9"/>
10458          <polyline points="7 21 3 17 7 13"/>
10459          <path d="M15 17H3"/>
10460        </svg>
10461        <!-- Filled icon (active) -->
10462        <svg class="icon-filled" viewBox="0 0 24 24" fill="currentColor">
10463          <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"/>
10464        </svg>
10465        <span class="bottom-nav-item-badge" id="bottomCompareCount" style="display:none;">0</span>
10466      </div>
10467      <span class="bottom-nav-item-label">Compare</span>
10468    </button>
10469
10470    <!-- Chat - Always show, check login in JavaScript -->
10471    <button class="bottom-nav-item" id="bottomChatBtn" data-page="chat">
10472      <div class="bottom-nav-item-icon">
10473        <!-- Outlined icon (default) -->
10474        <svg class="icon-outlined" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" opacity="0.85">
10475          <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
10476        </svg>
10477        <!-- Filled icon (active) -->
10478        <svg class="icon-filled" viewBox="0 0 24 24" fill="currentColor">
10479          <path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/>
10480        </svg>
10481        <span class="bottom-nav-item-badge" id="bottomChatCount" style="display:none;">0</span>
10482      </div>
10483      <span class="bottom-nav-item-label">Chat</span>
10484    </button>
10485
10486    <!-- Akun -->
10487        <button class="bottom-nav-item" id="bottomLoginBtn">
10488      <div class="bottom-nav-item-icon">
10489        <!-- Outlined icon (default) -->
10490        <svg class="icon-outlined" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
10491          <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
10492          <circle cx="12" cy="7" r="4"/>
10493        </svg>
10494        <!-- Filled icon (active) -->
10495        <svg class="icon-filled" viewBox="0 0 24 24" fill="currentColor">
10496          <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"/>
10497        </svg>
10498      </div>
10499      <span class="bottom-nav-item-label">Login</span>
10500    </button>
10501      </div>
10502</nav>
10503
10504<!-- Mobile User Menu Bottom Sheet -->
10505
10506<script nonce="se3iyzEHMc9FQEB4wSyd0w==">
10507(function(){
10508  const btn = document.getElementById('megaMoreBtn');
10509  const dd  = document.getElementById('megaMoreDropdown');
10510  if(!btn || !dd) return;
10511
10512  function openDD(){
10513    dd.hidden = false;
10514    btn.setAttribute('aria-expanded','true');
10515    document.addEventListener('click', outside, {capture:true});
10516    document.addEventListener('keydown', esc);
10517  }
10518  function closeDD(){
10519    if(dd.hidden) return;
10520    dd.hidden = true;
10521    btn.setAttribute('aria-expanded','false');
10522    document.removeEventListener('click', outside, {capture:true});
10523    document.removeEventListener('keydown', esc);
10524  }
10525  function toggle(){ dd.hidden ? openDD() : closeDD(); }
10526  function outside(e){
10527    if(!dd.contains(e.target) && e.target !== btn){
10528      closeDD();
10529    }
10530  }
10531  function esc(e){
10532    if(e.key === 'Escape'){
10533      closeDD();
10534      btn.focus();
10535    }
10536  }
10537  btn.addEventListener('click', toggle);
10538})();
10539
10540// ========== HORIZONTAL SCROLL FADE INDICATOR ==========
10541(function(){
10542  const megaCats = document.getElementById('megaCatsScroll');
10543  const fade = document.getElementById('megaCatsFade');
10544  if (!megaCats || !fade) return;
10545
10546  function updateFade() {
10547    const scrollLeft = megaCats.scrollLeft;
10548    const scrollWidth = megaCats.scrollWidth;
10549    const clientWidth = megaCats.clientWidth;
10550    const maxScroll = scrollWidth - clientWidth;
10551    
10552    // Hide fade when scrolled to end (within 10px tolerance)
10553    if (maxScroll - scrollLeft <= 10) {
10554      fade.style.opacity = '0';
10555    } else {
10556      fade.style.opacity = '1';
10557    }
10558  }
10559
10560  // Update on scroll
10561  megaCats.addEventListener('scroll', updateFade, { passive: true });
10562  
10563  // Update on resize
10564  window.addEventListener('resize', updateFade, { passive: true });
10565  
10566  // Initial check
10567  setTimeout(updateFade, 100);
10568})();
10569
10570document.addEventListener('DOMContentLoaded', function () {
10571  var input = document.getElementById('searchInput');
10572  if (!input) return;
10573  input.addEventListener('keydown', function (e) {
10574    if (e.key === 'Enter') {
10575      e.preventDefault();
10576      var form = input.form;
10577      if (!form) return;
10578      if (typeof form.requestSubmit === 'function') form.requestSubmit();
10579      else form.submit();
10580    }
10581  });
10582});
10583
10584
10585(function(){
10586  const cartBtn = document.getElementById('cartBtn');
10587  if (cartBtn) {
10588    cartBtn.addEventListener('click', function(e) {
10589      e.preventDefault();
10590      
10591      const checkLogin = () => {
10592        return window.isUserLoggedIn ? window.isUserLoggedIn() : false;
10593      };
10594      
10595      if (checkLogin()) {
10596        window.location.href = '/keranjang';
10597      } else {
10598        showCartLoginModal();
10599      }
10600    });
10601  }
10602
10603  const loginBtn = document.getElementById('loginBtn');
10604  if (loginBtn) {
10605    loginBtn.addEventListener('click', function(e) {
10606      e.preventDefault();
10607      window.location.href = 'https://www.bim.miami/';
10608    });
10609  }
10610
10611  const userMenuBtn = document.getElementById('userMenuBtn');
10612  const userDropdown = document.getElementById('userDropdown');
10613  
10614  if (userMenuBtn && userDropdown) {
10615    function openDropdown() {
10616      userDropdown.hidden = false;
10617      userMenuBtn.setAttribute('aria-expanded', 'true');
10618      document.addEventListener('click', closeOnOutside, { capture: true });
10619      document.addEventListener('keydown', closeOnEscape);
10620    }
10621    
10622    function closeDropdown() {
10623      if (userDropdown.hidden) return;
10624      userDropdown.hidden = true;
10625      userMenuBtn.setAttribute('aria-expanded', 'false');
10626      document.removeEventListener('click', closeOnOutside, { capture: true });
10627      document.removeEventListener('keydown', closeOnEscape);
10628    }
10629    
10630    function closeOnOutside(e) {
10631      if (!userDropdown.contains(e.target) && e.target !== userMenuBtn) {
10632        closeDropdown();
10633      }
10634    }
10635    
10636    function closeOnEscape(e) {
10637      if (e.key === 'Escape') {
10638        closeDropdown();
10639        userMenuBtn.focus();
10640      }
10641    }
10642    
10643    userMenuBtn.addEventListener('click', function(e) {
10644      e.preventDefault();
10645      userDropdown.hidden ? openDropdown() : closeDropdown();
10646    });
10647    
10648    // Close dropdown when clicking profile links
10649    userDropdown.querySelectorAll('[data-close-dropdown]').forEach(link => {
10650      link.addEventListener('click', function() {
10651        closeDropdown();
10652      });
10653    });
10654  }
10655
10656  // Bottom nav profile button - toggle mobile bottom sheet
10657  const bottomProfileBtn = document.getElementById('bottomProfileBtn');
10658  const mobileUserSheet = document.getElementById('mobileUserSheet');
10659  const mobileUserSheetBackdrop = document.getElementById('mobileUserSheetBackdrop');
10660  
10661  if (bottomProfileBtn && mobileUserSheet) {
10662    function openMobileSheet() {
10663      mobileUserSheet.hidden = false;
10664      document.body.style.overflow = 'hidden';
10665      // Trigger animation
10666      requestAnimationFrame(() => {
10667        mobileUserSheet.classList.add('active');
10668      });
10669    }
10670    
10671    function closeMobileSheet() {
10672      mobileUserSheet.classList.remove('active');
10673      document.body.style.overflow = '';
10674      setTimeout(() => {
10675        mobileUserSheet.hidden = true;
10676      }, 300);
10677    }
10678    
10679    bottomProfileBtn.addEventListener('click', function(e) {
10680      e.preventDefault();
10681      openMobileSheet();
10682    });
10683    
10684    if (mobileUserSheetBackdrop) {
10685      mobileUserSheetBackdrop.addEventListener('click', closeMobileSheet);
10686    }
10687    
10688    // Close on escape key
10689    document.addEventListener('keydown', function(e) {
10690      if (e.key === 'Escape' && !mobileUserSheet.hidden) {
10691        closeMobileSheet();
10692      }
10693    });
10694    
10695    // Close sheet when clicking profile links
10696    mobileUserSheet.querySelectorAll('[data-close-sheet]').forEach(link => {
10697      link.addEventListener('click', function() {
10698        closeMobileSheet();
10699      });
10700    });
10701  }
10702  
10703  // Handle Google avatar error in bottom-nav (fallback to initials)
10704  const bottomNavAvatar = document.querySelector('.bottom-nav-avatar-img');
10705  if (bottomNavAvatar) {
10706    bottomNavAvatar.addEventListener('error', function() {
10707      const initials = this.getAttribute('data-fallback-initials') || 'U';
10708      const initialsDiv = document.createElement('div');
10709      initialsDiv.className = 'bottom-nav-avatar-initials';
10710      initialsDiv.textContent = initials;
10711      this.parentNode.replaceChild(initialsDiv, this);
10712    });
10713  }
10714
10715  // Mobile logout button
10716  const mobileLogoutBtn = document.getElementById('mobileLogoutBtn');
10717  if (mobileLogoutBtn) {
10718    mobileLogoutBtn.addEventListener('click', async function(e) {
10719      e.preventDefault();
10720      
10721      // Check push notification before logout
10722      if (window.ChatNotification && typeof window.ChatNotification.confirmLogoutWithPushNotification === 'function') {
10723        const confirmLogout = await window.ChatNotification.confirmLogoutWithPushNotification();
10724        if (!confirmLogout) {
10725          return; // User cancelled logout
10726        }
10727      }
10728      
10729      const getCsrfToken = () => document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
10730      
10731      try {
10732        const response = await fetch('/auth/logout', {
10733          method: 'POST',
10734          credentials: 'same-origin',
10735          headers: {
10736            'Content-Type': 'application/json',
10737            'X-CSRF-Token': getCsrfToken()
10738          },
10739          body: JSON.stringify({
10740            csrf_token: getCsrfToken()
10741          })
10742        });
10743        
10744        if (response.ok) {
10745          if (window.guestCart && typeof window.guestCart.clear === 'function') {
10746            window.guestCart.clear();
10747          }
10748          window.location.href = '/login?logged_out=1';
10749        } else {
10750          console.error('Logout failed');
10751          window.location.href = '/logout';
10752        }
10753      } catch (error) {
10754        console.error('Logout error:', error);
10755        window.location.href = '/logout';
10756      }
10757    });
10758  }
10759
10760  const logoutBtn = document.getElementById('logoutBtn');
10761  if (logoutBtn) {
10762    logoutBtn.addEventListener('click', async function(e) {
10763      e.preventDefault();
10764      
10765      // Check push notification before logout
10766      if (window.ChatNotification && typeof window.ChatNotification.confirmLogoutWithPushNotification === 'function') {
10767        const confirmLogout = await window.ChatNotification.confirmLogoutWithPushNotification();
10768        if (!confirmLogout) {
10769          return; // User cancelled logout
10770        }
10771      }
10772      
10773      const getCsrfToken = () => document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
10774      
10775      try {
10776        const response = await fetch('/auth/logout', {
10777          method: 'POST',
10778          credentials: 'same-origin',
10779          headers: {
10780            'Content-Type': 'application/json',
10781            'X-CSRF-Token': getCsrfToken()
10782          },
10783          body: JSON.stringify({
10784            csrf_token: getCsrfToken()
10785          })
10786        });
10787        
10788        if (response.ok) {
10789          if (window.guestCart && typeof window.guestCart.clear === 'function') {
10790            window.guestCart.clear();
10791          }
10792          
10793          window.location.href = '/login?logged_out=1';
10794        } else {
10795          console.error('Logout failed');
10796          window.location.href = '/logout';
10797        }
10798      } catch (error) {
10799        console.error('Logout error:', error);
10800        window.location.href = '/logout';
10801      }
10802    });
10803  }
10804
10805  const wishlistBtn = document.getElementById('wishlistBtn');
10806  const notifBtn = document.getElementById('notifBtn');
10807  
10808  if (wishlistBtn) {
10809    wishlistBtn.addEventListener('click', function(e) {
10810      e.preventDefault();
10811      alert('Fitur wishlist akan segera tersedia!');
10812    });
10813  }
10814  
10815  if (notifBtn) {
10816    notifBtn.addEventListener('click', function(e) {
10817      e.preventDefault();
10818      alert('Fitur notifikasi akan segera tersedia!');
10819    });
10820  }
10821})();
10822
10823// ========== MOBILE BOTTOM NAVIGATION ==========
10824(function initBottomNav() {
10825  // Sync cart badge with bottom nav
10826  function syncBottomCartBadge() {
10827    const mainBadge = document.getElementById('cartCount');
10828    const bottomBadge = document.getElementById('bottomCartCount');
10829    if (mainBadge && bottomBadge) {
10830      const count = mainBadge.textContent;
10831      bottomBadge.textContent = count;
10832      bottomBadge.style.display = parseInt(count) > 0 ? 'flex' : 'none';
10833    }
10834  }
10835
10836  // Sync compare badge with bottom nav
10837  function syncBottomCompareBadge() {
10838    const mainBadge = document.getElementById('compareCount');
10839    const bottomBadge = document.getElementById('bottomCompareCount');
10840    if (mainBadge && bottomBadge) {
10841      const count = mainBadge.textContent;
10842      bottomBadge.textContent = count;
10843      bottomBadge.style.display = parseInt(count) > 0 ? 'flex' : 'none';
10844    }
10845  }
10846
10847  // Initial sync
10848  syncBottomCartBadge();
10849  syncBottomCompareBadge();
10850
10851  // Observe badge changes
10852  const observer = new MutationObserver(function() {
10853    syncBottomCartBadge();
10854    syncBottomCompareBadge();
10855  });
10856
10857  const cartBadge = document.getElementById('cartCount');
10858  const compareBadge = document.getElementById('compareCount');
10859  
10860  if (cartBadge) observer.observe(cartBadge, { childList: true, characterData: true, subtree: true });
10861  if (compareBadge) observer.observe(compareBadge, { childList: true, characterData: true, subtree: true });
10862
10863  // Bottom Cart Button
10864  const bottomCartBtn = document.getElementById('bottomCartBtn');
10865  if (bottomCartBtn) {
10866    bottomCartBtn.addEventListener('click', function(e) {
10867      e.preventDefault();
10868      
10869      const checkLogin = () => {
10870        return window.isUserLoggedIn ? window.isUserLoggedIn() : false;
10871      };
10872      
10873      if (checkLogin()) {
10874        window.location.href = '/keranjang';
10875      } else {
10876        showCartLoginModal();
10877      }
10878    });
10879  }
10880
10881  // Bottom Compare Button
10882  const bottomCompareBtn = document.getElementById('bottomCompareBtn');
10883  if (bottomCompareBtn) {
10884    bottomCompareBtn.addEventListener('click', function(e) {
10885      e.preventDefault();
10886      
10887      // Close chat widget if open
10888      if (window.plazaChat && window.plazaChat.isWidgetOpen) {
10889        window.plazaChat.closeWidget();
10890      }
10891      
10892      const mainCompareBtn = document.getElementById('compareBtn');
10893      if (mainCompareBtn) {
10894        mainCompareBtn.click();
10895      }
10896    });
10897  }
10898
10899  // Bottom Category Button - triggers mega menu
10900  const bottomCategoryBtn = document.getElementById('bottomCategoryBtn');
10901  if (bottomCategoryBtn) {
10902    bottomCategoryBtn.addEventListener('click', function(e) {
10903      e.preventDefault();
10904      // Trigger desktop mega menu
10905      const desktopTrigger = document.querySelector('.mm2-trigger:not(#bottomCategoryBtn)');
10906      if (desktopTrigger) {
10907        desktopTrigger.click();
10908      }
10909    });
10910  }
10911
10912  // Bottom Login Button
10913  const bottomLoginBtn = document.getElementById('bottomLoginBtn');
10914  if (bottomLoginBtn) {
10915    bottomLoginBtn.addEventListener('click', function(e) {
10916      e.preventDefault();
10917      window.location.href = '/login';
10918    });
10919  }
10920
10921  // Bottom Chat Button
10922  const bottomChatBtn = document.getElementById('bottomChatBtn');
10923  if (bottomChatBtn) {
10924    bottomChatBtn.addEventListener('click', function(e) {
10925      e.preventDefault();
10926      
10927      // Trigger chat widget (try multiple times if needed)
10928      const triggerChat = () => {
10929        const chatToggle = document.getElementById('chat-toggle-btn');
10930        if (chatToggle) {
10931          chatToggle.click();
10932          return true;
10933        }
10934        return false;
10935      };
10936      
10937      // Immediate attempt
10938      if (!triggerChat()) {
10939        // Retry after 100ms if not found
10940        setTimeout(() => {
10941          if (!triggerChat()) {
10942            // Last resort: manually show popup
10943            const popup = document.getElementById('chat-popup');
10944            if (popup) {
10945              popup.style.display = 'block';
10946            }
10947          }
10948        }, 100);
10949      }
10950    });
10951  }
10952
10953  // Sync chat unread badge
10954  function syncBottomChatBadge() {
10955    const chatBadge = document.getElementById('chat-unread-badge');
10956    const bottomChatBadge = document.getElementById('bottomChatCount');
10957    if (chatBadge && bottomChatBadge) {
10958      const count = chatBadge.textContent;
10959      bottomChatBadge.textContent = count;
10960      bottomChatBadge.style.display = chatBadge.style.display;
10961    }
10962  }
10963
10964  // Observe chat badge changes
10965  const chatBadge = document.getElementById('chat-unread-badge');
10966  if (chatBadge) {
10967    observer.observe(chatBadge, { attributes: true, childList: true, characterData: true, subtree: true });
10968    syncBottomChatBadge();
10969  }
10970
10971  // Active state management based on current page
10972  function updateActiveState() {
10973    const currentPath = window.location.pathname;
10974    const bottomNavItems = document.querySelectorAll('.bottom-nav-item');
10975    
10976    bottomNavItems.forEach(item => {
10977      item.classList.remove('active');
10978      
10979      const itemId = item.id;
10980      const page = item.getAttribute('data-page');
10981      const href = item.getAttribute('href');
10982      
10983      // Check by ID for specific pages
10984      if (itemId === 'bottomHomeBtn' && currentPath === '/') {
10985        item.classList.add('active');
10986      } else if (itemId === 'bottomCartBtn' && (currentPath === '/keranjang' || currentPath === '/cart')) {
10987        item.classList.add('active');
10988      } else if (itemId === 'bottomProfileBtn' && currentPath.startsWith('/profile')) {
10989        item.classList.add('active');
10990      } else if (itemId === 'bottomCategoryBtn' && currentPath.startsWith('/kategori')) {
10991        item.classList.add('active');
10992      } else if (itemId === 'bottomCompareBtn' && currentPath === '/compare') {
10993        item.classList.add('active');
10994      } else if (itemId === 'bottomChatBtn' && currentPath === '/chat') {
10995        item.classList.add('active');
10996      }
10997      // Fallback: check by data-page attribute
10998      else if (page === 'home' && currentPath === '/') {
10999        item.classList.add('active');
11000      } else if (page === 'profile' && currentPath.startsWith('/profile')) {
11001        item.classList.add('active');
11002      }
11003      // Fallback: check by href
11004      else if (href && currentPath === href) {
11005        item.classList.add('active');
11006      }
11007    });
11008  }
11009
11010  // Update active state on load
11011  updateActiveState();
11012
11013  // Bottom nav always visible - auto-hide disabled for better UX
11014  // const bottomNav = document.querySelector('.bottom-nav');
11015  // if (bottomNav && window.innerWidth <= 640) {
11016  //   // Auto-hide feature disabled
11017  // }
11018})();
11019// Cart Login Modal Handler (Global scope)
11020function showCartLoginModal() {
11021  const modal = document.getElementById('cartLoginModal');
11022  if (!modal) return;
11023  
11024  modal.classList.add('show');
11025  
11026  const confirmBtn = document.getElementById('cartLoginConfirm');
11027  const registerBtn = document.getElementById('cartRegisterBtn');
11028  const cancelBtn = document.getElementById('cartLoginCancel');
11029  
11030  const handleLogin = () => {
11031    window.location.href = '/login?return=' + encodeURIComponent('/keranjang');
11032  };
11033  
11034  const handleRegister = () => {
11035    window.location.href = '/register?return=' + encodeURIComponent('/keranjang');
11036  };
11037  
11038  const handleCancel = () => {
11039    modal.classList.remove('show');
11040    confirmBtn.removeEventListener('click', handleLogin);
11041    registerBtn.removeEventListener('click', handleRegister);
11042    cancelBtn.removeEventListener('click', handleCancel);
11043  };
11044  
11045  confirmBtn.removeEventListener('click', handleLogin);
11046  registerBtn.removeEventListener('click', handleRegister);
11047  cancelBtn.removeEventListener('click', handleCancel);
11048  
11049  confirmBtn.addEventListener('click', handleLogin);
11050  registerBtn.addEventListener('click', handleRegister);
11051  cancelBtn.addEventListener('click', handleCancel);
11052  
11053  modal.addEventListener('click', (e) => {
11054    if (e.target === modal) {
11055      handleCancel();
11056    }
11057  });
11058}
11059</script>
vendor: 1 bytes, line 11059
11059
11060<script data-inlined="cart-badge.js">
11061(function(){
11062  function selBadge(){
11063    return document.querySelector('[data-role="cart-badge"]') || document.getElementById('cartCount');
11064  }
11065  function setBadge(n){
11066    const el = selBadge();
11067    if (!el) return;
11068    const count = Math.max(0, parseInt(n||0,10) || 0);
11069    el.textContent = String(count);
11070    // Sembunyikan jika 0? Jika ingin selalu terlihat, hapus 2 baris di bawah
11071    if (count <= 0) { el.classList.add('is-empty'); }
11072    else { el.classList.remove('is-empty'); }
11073  }
11074  async function refresh(){
11075    try{
11076      // Primary: Use server-side endpoint (handles both logged and guest users)
11077      const res = await fetch('/api/cart/get-simple.php', { credentials:'same-origin' });
11078      if (res.ok) {
11079        const data = await res.json();
11080        if (data && data.ok) {
11081          setBadge(data.items_count || 0);
11082          return;
11083        }
11084      }
11085      
11086      // Fallback hanya untuk guest user: check localStorage
11087      const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true' ||
11088                        document.body.classList.contains('logged-in') ||
11089                        window.userLoggedIn === true;
11090      
11091      if (!isLoggedIn && window.guestCart && typeof window.guestCart.getItemsCount === 'function') {
11092        setBadge(window.guestCart.getItemsCount());
11093      }
11094    }catch(e){ 
11095      // Final fallback hanya untuk guest
11096      const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true';
11097      if (!isLoggedIn) {
11098        try {
11099          const guestCartData = JSON.parse(localStorage.getItem('plazait_guest_cart') || '{"items":{}}');
11100          const items = guestCartData.items || {};
11101          const count = Object.values(items).reduce((total, item) => total + (item.qty || 0), 0);
11102          setBadge(count);
11103        } catch(e2) {
11104          setBadge(0);
11105        }
11106      } else {
11107        setBadge(0);
11108      }
11109    }
11110  }
11111
11112  // Ekspos untuk dipanggil dari skrip lain (opsional)
11113  window.CartBadge = { set: setBadge, refresh };
11114  window.updateCartCounter = refresh; // Alias for compatibility
11115
11116  // Update saat halaman load
11117  if (document.readyState === 'loading') {
11118    document.addEventListener('DOMContentLoaded', refresh);
11119  } else {
11120    refresh();
11121  }
11122
11123  // Dengarkan event global dari add/remove/update
11124  document.addEventListener('cart:updated', function(ev){
11125    const c = ev && ev.detail && typeof ev.detail.items_count === 'number'
11126      ? ev.detail.items_count : null;
11127    if (c !== null) setBadge(c);
11128    else refresh();
11129  });
11130
11131  // Dengarkan event khusus cart remove untuk update badge
11132  document.addEventListener('cart:removed', function(ev){
11133    // Langsung refresh untuk memastikan data terbaru
11134    refresh();
11135  });
11136
11137  // Jika addToCart sudah ada sebelum file ini, coba wrap agar badge ter-update.
11138  const prevAdd = window.addToCart;
11139  if (typeof prevAdd === 'function'){
11140    window.addToCart = async function(pid, qty, vid, meta){
11141      const res = await prevAdd(pid, qty, vid, meta);
11142      try{
11143        const cnt = res && res.cart ? res.cart.items_count : null;
11144        if (cnt !== null) setBadge(cnt);
11145        // tetap siarkan event agar listener lain ikut sync
11146        document.dispatchEvent(new CustomEvent('cart:updated', { detail: res.cart || {} }));
11147      }catch(_){}
11148      return res;
11149    };
11150  }
11151
11152  // Helper function to check if user is logged in
11153  function isUserLoggedIn() {
11154    const metaTag = document.querySelector('meta[name="user-logged-in"]');
11155    return metaTag ? metaTag.getAttribute('content') === 'true' : false;
11156  }
11157
11158  // Make login check globally accessible
11159  window.isUserLoggedIn = isUserLoggedIn;
11160})();
11161</script>
11161
11162
11163<!-- Product Compare Sidebar -->
11164<div class="compare-sidebar" id="compareSidebar">
11165  <div class="compare-header">
11166    <h4 class="compare-title">
11167      <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
11168        <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"/>
11169      </svg>
11170      Bandingkan Produk
11171      <span class="compare-count" id="compareCountSidebar">(0)</span>
11172    </h4>
11173    <button class="compare-close" id="compareClose" aria-label="Tutup">
11174      <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
11175        <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"/>
11176      </svg>
11177    </button>
11178  </div>
11179  
11180  <div class="compare-content">
11181    <div class="compare-empty" id="compareEmpty">
11182      <div class="empty-icon">
11183        <svg width="48" height="48" viewBox="0 0 24 24" fill="currentColor" opacity="0.3">
11184          <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"/>
11185        </svg>
11186      </div>
11187      <h5>Belum ada produk dipilih</h5>
11188      <p>Pilih produk yang ingin dibandingkan dengan klik tombol "Bandingkan Produk" di halaman produk.</p>
11189    </div>
11190    
11191    <div class="compare-list" id="compareList">
11192      <!-- Products will be populated here -->
11193    </div>
11194  </div>
11195  
11196  <div class="compare-actions" id="compareActions">
11197    <button class="btn btn-outline-danger btn-sm" id="compareClearAll">
11198      <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
11199        <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"/>
11200      </svg>
11201      Hapus Semua
11202    </button>
11203    <button class="btn btn-primary btn-sm" id="compareAnalyze" disabled>
11204      <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
11205        <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"/>
11206      </svg>
11207      Analisis AI
11208    </button>
11209  </div>
11210  
11211  <!-- AI Disclaimer in Sidebar -->
11212  <div class="compare-disclaimer">
11213    <div class="ai-disclaimer">
11214      <div class="disclaimer-text">
11215        <strong>💡 Catatan:</strong> Analisis ini dihasilkan secara otomatis dan dapat memiliki keterbatasan.
11216        Gunakan sebagai referensi tambahan dengan tetap mempertimbangkan kebutuhan Anda.
11217        Untuk informasi lebih lanjut, silakan hubungi tim BERLINTOTO.
11218      </div>
11219    </div>
11220  </div>
11221</div><style data-inlined="pd-lightbox.css">
11222/* PlazaIT Product Lightbox (vanilla, no CDN) — v4 */
11223.pd-lb {
11224  position: fixed; inset: 0; z-index: 99999;
11225  background: rgba(0,0,0,.95);
11226  display: none;
11227  color: #fff;
11228  /* Transition untuk swipe-down feedback */
11229  transition: transform .18s ease, opacity .18s ease;
11230}
11231.pd-lb.show { display: block; }
11232
11233/* Canvas di bawah (z:1), Controls di atas (z:2) */
11234.pd-lb-canvas {
11235  position: absolute; inset: 56px 0 56px 0;
11236  display: flex; align-items: center; justify-content: center;
11237  overflow: hidden;
11238  touch-action: none;
11239  z-index: 1;
11240}
11241
11242.pd-lb-img {
11243  max-width: 96vw; max-height: 86vh;
11244  will-change: transform;
11245  user-select: none;
11246  pointer-events: none;
11247  transform-origin: 50% 50%;
11248}
11249
11250.pd-lb-close, .pd-lb-prev, .pd-lb-next, .pd-lb-counter {
11251  position: absolute;
11252  z-index: 2;
11253}
11254
11255.pd-lb-close, .pd-lb-prev, .pd-lb-next {
11256  background: rgba(17,24,39,.7);
11257  color: #fff; border: 0; cursor: pointer;
11258  width: 40px; height: 40px; border-radius: 999px;
11259  display: inline-flex; align-items: center; justify-content: center;
11260  transition: background-color .15s ease, transform .05s ease;
11261  -webkit-tap-highlight-color: transparent;
11262}
11263.pd-lb-close:hover, .pd-lb-prev:hover, .pd-lb-next:hover { background: rgba(31,41,55,.85); }
11264.pd-lb-close:active, .pd-lb-prev:active, .pd-lb-next:active { transform: translateY(.5px); }
11265
11266.pd-lb-close { top: max(12px, env(safe-area-inset-top, 12px)); right: max(12px, env(safe-area-inset-right, 12px)); }
11267.pd-lb-prev  { top: 50%; left:  10px; transform: translateY(-50%); }
11268.pd-lb-next  { top: 50%; right: 10px; transform: translateY(-50%); }
11269
11270.pd-lb-counter {
11271  top: max(16px, env(safe-area-inset-top, 16px)); left: 50%;
11272  transform: translateX(-50%);
11273  font-weight: 700; font-size: 13px; letter-spacing: .4px;
11274  background: rgba(17,24,39,.65);
11275  padding: 6px 10px; border-radius: 999px; border: 1px solid rgba(255,255,255,.1);
11276}
11277
11278@media (max-width: 640px){
11279  .pd-lb-img { max-width: 98vw; max-height: 80vh; }
11280  .pd-lb-prev, .pd-lb-next { width: 36px; height: 36px; }
11281  .pd-lb-close { width: 38px; height: 38px; }
11282}
11283</style>
11284<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;
11284cursor: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}.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>
11285<style nonce="se3iyzEHMc9FQEB4wSyd0w==">
11286.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;
11286display: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}
11287
11288.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)}}
11289
11290.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}
11291
11292.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}
11293
11294.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)}
11295
11296.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%,#dcce77 100%);color:#fff}.pd-share-name{font-size:12px;font-weight:600;color:#475569;text-align:center}
11297
11298.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)}
11298.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}
11299
11300@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}}
11301
11302.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}
11303
11304@media(max-width:640px){.pd-share-btn{display:none!important}}
11305
11306.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}
11307
11308.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}
11309
11310.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}
11311
11312.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}
11313
11314
11315.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}
11316
11317.sp-wa{
11318  grid-column:3; justify-self:end;
11319  width:32px; height:32px; min-width:32px; min-height:32px;
11320  box-sizing:border-box;
11321  padding:0; line-height:0; text-decoration:none; outline:0;
11322  display:inline-flex; align-items:center; justify-content:center;
11323  border-radius:999px; border:1px solid rgba(37,211,102,.60);  /* 1px untuk hasil “crisp” */
11324  background:rgba(37,211,102,.08); color:#25D366;
11325  transition:background-color .15s ease, border-color .15s ease, transform .05s ease;
11326  -webkit-tap-highlight-color:transparent;
11327}
11328.sp-wa:hover{background:rgba(37,211,102,.16);border-color:rgba(37,211,102,.75)}
11329.sp-wa:active{transform:translateY(.5px) scale(.98)}
11330
11331.sp-wa svg{width:18px;height:18px;display:block;vertical-align:middle}
11332.stock-popover a{text-decoration:none}
11333
11334#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}}
11335
11336.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}}
11337
11338.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}}
11339
11340.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}
11341.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)}}
11342.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:#dcce77;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:#dcce77;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}
11343@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}}
11344
11345.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:#dcce77;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}}
11346</style>
11347
11348<div class="pd-sticky-header" id="pd-sticky-header"><div class="pd-sticky-header-container"><div class="pd-sticky-thumb"><img src="https://www.bim.miami/berlintoto.png" alt="BERLINTOTO" id="pd-sticky-thumb-img"></div><div class="pd-sticky-info"><h2 class="pd-sticky-title" id="pd-sticky-title">Apa Itu BERLINTOTO | Situs Toto Slot Gacor & Bandar Togel Online 4D Re
11348smi</h2><div class="pd-sticky-price-row"><span class="pd-sticky-price" id="pd-sticky-price">Rp 10.000,-</span><span class="pd-sticky-price-original" id="pd-sticky-price-original">Rp 1.000.000</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 produk" title="Bagikan"><svg viewBox="0 0 24 24" width="16" height="16" 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><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>+ Keranjang</button><button type="button" class="pd-sticky-btn pd-sticky-btn-buy" id="pd-sticky-buy" disabled>Beli Sekarang</button></div></div></div>
11349
11350<main class="product-single product-single--v3"><nav class="pd-breadcrumb" aria-label="breadcrumb"><a href="https://www.bim.miami/">BERLINTOTO</a><span>-</span><a href="https://www.bim.miami/">BERLIN TOTO</a><span>-</span><a href="https://www.bim.miami/">LOGIN BERLINTOTO</a><span>-</span><a href="https://www.bim.miami/">DAFTAR BERLINTOTO</a><span>-</span><a href="https://www.bim.miami/">LINK BERLINTOTO</a><span>-</span><span class="current">
11351
11352</span></nav>
11353
11354<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://www.bim.miami/berlintoto.png" alt="BERLINTOTO" data-slug="" loading="eager">
11355</figure>
11356
11357<div class="pd-mobile-slider-wrapper">
11358<div class="pd-mobile-slider" id="pd-mobile-slider" aria-label="Galeri gambar (geser)"><div class="pd-mslide"><img src="https://www.bim.miami/berlintoto.png" alt="BERLINTOTO" loading="eager"></div><div class="pd-mslide"><img src="https://www.bim.miami/berlintoto.png" alt="BERLINTOTO" loading="lazy"></div><div class="pd-mslide"><img src="https://www.bim.miami/berlintoto.png" alt="BERLINTOTO" loading="lazy"></div><div class="pd-mslide"><img src="https://www.bim.miami/berlintoto.png" alt="BERLINTOTO" loading="lazy"></div><div class="pd-mslide"><img src="https://www.bim.miami/berlintoto.png" alt="BERLINTOTO" loading="lazy"></div><div class="pd-mslide"><img src="https://www.bim.miami/berlintoto.png" alt="BERLINTOTO" loading="lazy"></div></div><div class="pd-gallery-counter" id="pd-gallery-counter">1/6</div></div>
11359<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://www.bim.miami/berlintoto.png" data-alt="BERLINTOTO" data-slug="" aria-label="Gambar 1"><img src="https://www.bim.miami/berlintoto.png" alt="BERLINTOTO" loading="lazy"></button><button type="button" class="pd-thumb-btn " data-full="https://www.bim.miami/berlintoto.png" data-alt="BERLINTOTO" data-slug="https://www.bim.miami/berlintoto.png" aria-label="Gambar 2"><img src="https://www.bim.miami/berlintoto.png" alt="BERLINTOTO" loading="lazy"></button><button type="button" class="pd-thumb-btn " data-full="https://www.bim.miami/berlintoto.png" data-alt
11359="BERLINTOTO" data-slug="lenovo-legion-go-8asp2-83n0000rid-ryzen-z2-extreme-amd-radeon-graphics-eclipse-b-1761796941-223.webp" aria-label="Gambar 3"><img src="https://www.bim.miami/berlintoto.png" alt="BERLINTOTO" loading="lazy"></button><button type="button" class="pd-thumb-btn " data-full="https://www.bim.miami/berlintoto.png" data-alt="BERLINTOTO" data-slug="lenovo-legion-go-8asp2-83n0000rid-ryzen-z2-extreme-amd-radeon-graphics-eclipse-b-1761796941-776.webp" aria-label="Gambar 4"><img src="https://www.bim.miami/berlintoto.png" alt="BERLINTOTO" loading="lazy"></button><button type="button" class="pd-thumb-btn " data-full="https://www.bim.miami/berlintoto.png" data-alt="BERLINTOTO" data-slug="lenovo-legion-go-8asp2-83n0000rid-ryzen-z2-extreme-amd-radeon-graphics-eclipse-b-1761796941-634.webp" aria-label="Gambar 5"><img src="https://www.bim.miami/berlintoto.png" alt="BERLINTOTO" loading="lazy"></button><button type="button" class="pd-thumb-btn " data-full="https://www.bim.miami/berlintoto.png" data-alt="BERLINTOTO" data-slug="lenovo-legion-go-8asp2-83n0000rid-ryzen-z2-extreme-amd-radeon-graphics-eclipse-b-1761796941-892.webp" aria-label="Gambar 6"><img src="https://www.bim.miami/berlintoto.png" alt="BERLINTOTO" 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">
11360        <h1 class="pd-title" id="pd-title">Apa Itu BERLINTOTO | Situs Toto Slot Gacor & Bandar Togel Online 4D Resmi</h1><button type="button" class="pd-share-btn" id="pd-share-btn" aria-label="Bagikan produk" 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>
11361<div id="pd-price-anchor">
11362</div>
11363<div class="pd-price-qr-container">
11364        <div class="pd-price-block" id="pd-price-block">
11365                <div class="pd-price-row">
11366                        <span class="pd-price-current" id="pd-price-current" >Rp 10.000</span>
11367                        <span class="pd-price-original" id="pd-price-original">Rp 50.000</span>
11368                        <span class="pd-discount-badge" id="pd-discount-badge">-90%</span></div>
11369                        <div class="pd-meta-row">
11370                                <span class="sold-inline" id="pd-sold-inline" data-role="sold-chip">Terjual Terjual 5</span>
11371                                <span class="pwp-trigger-badge" id="pwp-trigger-badge" hidden>
11372                                        <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
11373                                                <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>
11374                                                <div class="pd-meta-inline"><span class="stock-badge out" id="pd-stock-badge">BERLINTOTO</span>
11375                                                        <span class="sku">SKU: <span id="pd-sku">BANDAR TOGEL</span></span></div>
11376<div class="paylater-promo paylater-promo-sm paylater-mobile-only">
11377    <span class="paylater-icon">💳</span>
11378    <span class="paylater-text">
11379        <strong>TOGEL</strong>
11380        <small>ONLINE</small>
11381    </span>
11382</div>
11383
11384<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">Stok Cabang</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">
11385<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%2Flenovo-legion-go-8as
11385p2-83n0000rid-ryzen-z2-extreme-amd-radeon-graphics-eclipse-black" target="_blank" rel="noopener" aria-label="WhatsApp Asia Mega Mas"><svg class="ico-wa" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><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>
11386                
11387                    <li class="sp-item">
11388                      <div class="sp-left">
11389                        <span class="sp-dot" style="--dot:#34d399;"></span>
11390                        <span class="sp-label">Medan Fair</span>
11391                      </div>
11392                      <span class="sp-qty" id="sp-qty-mdf">0</span>
11393                      <a class="sp-wa"
11394                         href="https://wa.me/6285388992009?text=Halo%2C%20saya%20ingin%20tanya%20stok%20di%20Medan%20Fair%20untuk%20produk%20https%3A%2F%2Fplazait.co.id%2Fproduct%2Flenovo-legion-go-8asp2-83n0000rid-ryzen-z2-extreme-amd-radeon-graphics-eclipse-black"
11395                         target="_blank" rel="noopener" aria-label="WhatsApp Medan Fair">
11396                        <svg class="ico-wa" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
11397                            <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"/>
11398                          </svg>
11399                      </a>
11400                    </li>
11401                
11402                    <li class="sp-item">
11403                      <div class="sp-left">
11404                        <span class="sp-dot" style="--dot:#f59e0b;"></span>
11405                        <span class="sp-label">Banda Aceh</span>
11406                      </div>
11407                      <span class="sp-qty" id="sp-qty-bda">0</span>
11408                      <a class="sp-wa"
11409                         href="https://wa.me/628116833411?text=Halo%2C%20saya%20ingin%20tanya%20stok%20di%20Banda%20Aceh%20untuk%20produk%20https%3A%2F%2Fplazait.co.id%2Fproduct%2Flenovo-legion-go-8asp2-83n0000rid-ryzen-z2-extreme-amd-radeon-graphics-eclipse-black"
11410                         target="_blank" rel="noopener" aria-label="WhatsApp Banda Aceh">
11411                        <svg class="ico-wa" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
11412                            <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"/>
11413                          </svg>
11414                      </a>
11415                    </li>
11416                  </ul>
11417                </div>
11418
11419                            </div>
11420            <div class="pd-qr-print" style="display:none;"><div class="pd-qr-label">Scan untuk lihat produk:</div><img src="https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=https%3A%2F%2Fplazait.co.id%2Fproduct%2Flenovo-legion-go-8asp2-83n0000rid-ryzen-z2-extreme-amd-radeon-graphics-eclipse-black" alt="QR Code" class="pd-qr-image"></div>
11421            </div>
11422                                    <div id="pd-variants-anchor"></div>
11423
11424                        
11425
11426            <div class="pd-compare-section">
11427                <button type="button" class="pd-compare-btn" id="pd-compare-btn" data-product-id="325">
11428                    <svg class="compare-icon" width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
11429                        <path d="M21,9L17,5V8H10V10H17V13M7,11L3,15L7,19V16H14V14H7V11Z"/>
11430                    </svg>
11431                    <span class="compare-text">Bandingkan Produk</span>
11432                    <span class="compare-remove" hidden>
11433                        <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
11434                            <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"/>
11435                        </svg>
11436                        Hapus dari Perbandingan
11437                    </span>
11438                </button>
11439                <div class="pd-compare-info">
11440                    <small class="compare-note" id="compare-note">Bandingkan hingga 5 produk dengan bantuan AI</small>
11441                </div>
11442            </div>
11443            
11444            <div class="pd-short-desc"><p><strong>Informasi BERLINTOTO:</strong></p><ul><li>Minimal Deposit: Rp.5.000,-</li><li>Jenis Permainan: Slot Gacor</li><li>Metode Pembayaran: Bank, QRIS, E-Wallet</li><li>Mata Uang: IDR (Indonesian Rupiah)</li><li>Jam Operasional: 24 Jam Nonstop</li><li>Total Rating: 399.999.999</li><li>Rating: ⭐⭐⭐⭐⭐</li><li>Daftar Buruan: <a href="https://bim.miami/aboutus/">Klik Disini</a></li></ul></div><div class="BERLINTOTO-container-v1 mini">
11445<div class="BERLINTOTO-container">
11446
11447  <!-- HEADER -->
11448<div class="togel-container-v3 mini">
11449                    <div class="togel-machine-v3">
11450                        <h2 class="togel-title-v3">🎯 PREDIKSI TOGEL RESMI (SIMULASI) 🎯</h2>
11451
11452                        <div class="togel-controls-v3">
11453                            <label>
11454                                PASARAN
11455                                <select id="marketFilter">
11456                                    <option value="ALL">ALL PASARAN</option>
11457                                </select>
11458                            </label>
11459
11460                            <label>
11461                                TIPE
11462                                <select id="typeFilter">
11463                                    <option value="4D">4D</option>
11464                                    <option value="3D">3D</option>
11465                                    <option value="2D">2D</option>
11466                                </select>
11467                            </label>
11468
11469                            <label>
11470                                MIN CONF
11471                                <input id="minConf" type="number" min="50" max="99" step="1" value="70">
11472                            </label>
11473
11474                            <button id="btnTogelRandom" type="button">
11475                                <span class="icon">🎲</span> GENERATE
11476                            </button>
11477                        </div>
11478
11479                        <div class="togel-main-result-v3" id="togelResult">
11480                            <div class="togel-status-v3" id="togelStatus">— READY —</div>
11481
11482                            <div class="togel-highlight-box">
11483                                <div class="togel-col-left">
11484                                    <div class="k">Pasaran Rekomendasi</div>
11485                                    <div class="v-game" id="rMarket">-</div>
11486
11487                                    <div class="provider-info">
11488                                        <span class="k-small">Tipe:</span>
11489                                        <span class="v-small" id="rType">-</span>
11490                                        <span class="k-small" style="margin-left:10px;">Conf:</span>
11491                                        <span class="v-small" id="rConf">-</span>
11492                                    </div>
11493                                </div>
11494
11495                                <div class="togel-col-right">
11496                                    <div class="k-rtp">Angka Utama</div>
11497                                    <div class="v-rtp-huge" id="rAngka">-</div>
11498                                </div>
11499                            </div>
11500
11501                            <div class="togel-details-v3">
11502                                <div class="togel-detail-item">
11503                                    <div class="k-detail">BBFS</div>
11504                                    <div class="v-detail" id="rBBFS">-</div>
11505                                </div>
11506                                <div class="togel-detail-item">
11507                                    <div class="k-detail">Angka Ikut</div>
11508                                    <div class="v-detail" id="rIkut">-</div>
11509                                </div>
11510                                <div class="togel-detail-item">
11511                                    <div class="k-detail">Shio</div>
11512                                    <div class="v-detail" id="rShio">-</div>
11513                                </div>
11514                            </div>
11515
11516                            <div class="togel-tip-v3" id="rTip">
11517                                Tips: Ini simulasi hiburan. Tetap bijak & disiplin.
11518                            </div>
11519                        </div>
11520
11521                        <div class="togel-alts-v3">
11522                            <div class="togel-alts-title-v3">3 Alternatif Angka</div>
11523                            <div class="togel-alt-list-v3" id="togelAltList"></div>
11524                        </div>
11525
11526                        <div class="togel-note-v3">
11527                            Disclaimer: Hasil di halaman ini random (simulasi), bukan acuan hasil resmi.
11528                        </div>
11529                    </div>
11530                </div>
11531
11532                <style>
11533                    @import url("https://fonts.googleapis.com/css2?family=Orbitron:wght@600;700;800&display=swap");
11534
11535                    /* ===============================
11536   WRAPPER
11537================================ */
11538                    .togel-container-v3 {
11539                        display: flex;
11540                        justify-content: center;
11541                        padding: 16px 10px;
11542                        font-family: "Orbitron", "Poppins", system-ui, sans-serif;
11543                    }
11544
11545                    /* ===============================
11546   MACHINE CORE (RED ELEGANT)
11547================================ */
11548                    .togel-machine-v3 {
11549                        position: relative;
11550                        width: 100%;
11551                        max-width: 520px;
11552                        padding: 16px;
11553                        border-radius: 16px;
11554                        overflow: hidden;
11555                        isolation: isolate;
11556                        background: radial-gradient(circle at 15% 0%, rgba(161 153 0), transparent 48%), radial-gradient(circle at 85% 100%, rgba(180, 0, 0, 0.18), transparent 55%), linear-gradient(123deg, #000000, #000000);
11557                        border: 1px solid rgba(185 162 0);
11558                        box-shadow: inset 0 0 18px rgba(196 199 0), 0 0 18px rgba(196 199 0);
11559                        color: #dbdbdb;
11560                    }
11561
11562                    /* HUD SWEEP */
11563                    .togel-machine-v3::before {
11564                        content: "";
11565                        position: absolute;
11566                        inset: 0;
11567                        background: linear-gradient(120deg, transparent 40%, rgba(255, 0, 0, .10), transparent 65%);
11568                        animation: hudSweep 7s linear infinite;
11569                        pointer-events: none;
11570                        opacity: .8;
11571                    }
11572
11573                    /* ===============================
11574   TITLE
11575================================ */
11576                    .togel-title-v3 {
11577                        text-align: center;
11578                        margin: 0 0 12px;
11579                        font-size: 15px;
11580                        font-weight: 900;
11581                        letter-spacing: 1px;
11582                        text-shadow: 0 0 6px rgba(167 0 0 / 90%);
11583                    }
11584
11585                    /* ===============================
11586   CONTROLS
11587================================ */
11588                    .togel-controls-v3 {
11589                        display: grid;
11590                        grid-template-columns: 1fr 1fr;
11591                        gap: 10px;
11592                        padding: 12px;
11593                        border-radius: 14px;
11594
11595                        background: rgba(0, 0, 0, 0.4);
11596                        border: 1px solid rgba(255 240 60 / 45%);
11597                        box-shadow: inset 0 0 14px rgba(205 189 0 / 22%);
11598                    }
11599
11600                    .togel-controls-v3 label {
11601                        display: grid;
11602                        gap: 6px;
11603                        font-size: 10px;
11604                        letter-spacing: .8px;
11605                        text-transform: uppercase;
11606                        color: #cdc800;
11607                    }
11608
11609                    .togel-controls-v3 select,
11610                    .togel-controls-v3 input {
11611                        background: rgba(0 0 0 / 92%);
11612                        color: #c5c5c5;
11613                        border: 1px solid rgba(205 189 0);
11614                        border-radius: 10px;
11615                        padding: 7px 9px;
11616                        font-weight: 700;
11617                        font-size: 12px;
11618                        outline: none;
11619                        box-shadow: inset 0 0 10px rgba(207 163 3);
11620                    }
11621
11622                    /* BUTTON */
11623                    #btnTogelRandom {
11624                        grid-column: span 2;
11625                        display: flex;
11626                        align-items: center;
11627                        justify-content: center;
11628                        gap: 8px;
11629
11630                        background: linear-gradient(145deg, #dcce77, #dcce77);
11631                        border: none;
11632                        color: #000000;
11633                        font-weight: 900;
11634                        letter-spacing: .8px;
11635                        border-radius: 14px;
11636                        cursor: pointer;
11637                        padding: 10px 12px;
11638                        font-size: 12px;
11639
11640                        box-shadow: 0 0 16px rgba(255 240 60 / 45%);
11641                        transition: .2s ease;
11642                    }
11643
11644                    #btnTogelRandom:active {
11645                        transform: scale(.98);
11646                    }
11647
11648                    #btnTogelRandom:disabled {
11649                        opacity: .75;
11650                        cursor: not-allowed;
11651                    }
11652
11653                    /* ===============================
11654   MAIN RESULT
11655================================ */
11656                    .togel-main-result-v3 {
11657                        margin-top: 12px;
11658                        padding: 12px;
11659                        border-radius: 14px;
11660
11661                        background: rgba(30, 0, 0, 0.78);
11662                        border: 1px solid rgba(196 199 0);
11663                        box-shadow: inset 0 0 16px rgba(196 199 0);
11664                    }
11665
11666                    .togel-status-v3 {
11667                        text-align: center;
11668                        font-weight: 800;
11669                        letter-spacing: .8px;
11670                        color: #cdc800;
11671                        margin-bottom: 10px;
11672                        font-size: 11px;
11673                    }
11674
11675                    .togel-highlight-box {
11676                        display: flex;
11677                        justify-content: space-between;
11678                        gap: 12px;
11679                        padding-bottom: 10px;
11680                        margin-bottom: 10px;
11681                        border-bottom: 1px dashed rgba(255 242 80 / 30%);
11682                    }
11683
11684                    .k,
11685                    .k-rtp {
11686                        font-size: 10px;
11687                        color: #c7c7c7;
11688                        text-transform: uppercase;
11689                    }
11690
11691                    .v-game {
11692                        font-size: 14px;
11693                        font-weight: 900;
11694                        color: #ffffff;
11695                        margin-top: 6px;
11696                        line-height: 1.2;
11697                    }
11698
11699                    .provider-info {
11700                        margin-top: 8px;
11701                        font-size: 11px;
11702                        color: #d9d9d9;
11703                        opacity: .95;
11704                    }
11705
11706                    .k-small {
11707                        color: #cdc800;
11708                        font-size: 10px;
11709                    }
11710
11711                    .v-small {
11712                        color: #fff0f0;
11713                        font-weight: 900;
11714                    }
11715
11716                    .v-rtp-huge {
11717                        font-size: 2.4em;
11718                        font-weight: 900;
11719                        color: #d7ab00;
11720                        text-shadow: 0 0 10px rgba(0 0 0);
11721                        line-height: 1;
11722                        letter-spacing: 1px;
11723                    }
11724
11725                    /* DETAILS */
11726                    .togel-details-v3 {
11727                        display: grid;
11728                        grid-template-columns: repeat(3, 1fr);
11729                        gap: 8px;
11730                        background: rgba(0, 0, 0, .30);
11731                        padding: 10px;
11732                        border-radius: 12px;
11733                    }
11734
11735                    .k-detail {
11736                        font-size: 9px;
11737                        color: #cdc800;
11738                        text-transform: uppercase;
11739                    }
11740
11741                    .v-detail {
11742                        font-size: 12px;
11743                        font-weight: 800;
11744                        color: #ffffff;
11745                        margin-top: 4px;
11746                        letter-spacing: .8px;
11747                    }
11748
11749                    .togel-tip-v3 {
11750                        margin-top: 10px;
11751                        font-size: 10px;
11752                        color: #d9d9d9;
11753                        opacity: .9;
11754                        text-align: center;
11755                    }
11756
11757                    /* ALTS */
11758                    .togel-alts-v3 {
11759                        margin-top: 12px;
11760                        padding: 12px;
11761                        border-radius: 14px;
11762                        background: rgba(0, 0, 0, .40);
11763                        border: 1px solid rgba(196 199 0);
11764                    }
11765
11766                    .togel-alts-title-v3 {
11767                        font-weight: 900;
11768                        color: #cdc800;
11769                        margin-bottom: 10px;
11770                        font-size: 12px;
11771                    }
11772
11773                    .togel-alt-list-v3 {
11774                        display: grid;
11775                        grid-template-columns: 1fr;
11776                        gap: 8px;
11777                    }
11778
11779                    .togel-alt-item {
11780                        background: rgba(30, 0, 0, .86);
11781                        border-radius: 12px;
11782                        padding: 10px;
11783                        border-left: 3px solid rgba(167 0 0 / 90%);
11784                    }
11785
11786                    .togel-alt-item .g {
11787                        font-weight: 900;
11788                        color: #c5c5c5;
11789                        font-size: 12px;
11790                    }
11791
11792                    .togel-alt-item .p {
11793                        font-size: 10px;
11794                        color: #cdc800;
11795                        margin-top: 4px;
11796                    }
11797
11798                    .togel-alt-item .r {
11799                        margin-top: 6px;
11800                        font-weight: 900;
11801                        color: #d7ab00;
11802                        letter-spacing: .8px;
11803                        font-size: 12px;
11804                    }
11805
11806                    .togel-note-v3 {
11807                        margin-top: 10px;
11808                        font-size: 10px;
11809                        color: #d9d9d9;
11810                        opacity: .85;
11811                        text-align: center;
11812                    }
11813
11814                    /* MOBILE */
11815                    @media(max-width:420px) {
11816                        .togel-highlight-box {
11817                            flex-direction: column
11818                        }
11819
11820                        .togel-details-v3 {
11821                            grid-template-columns: 1fr
11822                        }
11823                    }
11824
11825                    /* ANIM */
11826                    @keyframes hudSweep {
11827                        0% {
11828                            transform: translateX(-110%)
11829                        }
11830
11831                        100% {
11832                            transform: translateX(110%)
11833                        }
11834                    }
11835
11836                    @keyframes pulse {
11837                        0% {
11838                            opacity: .55
11839                        }
11840
11841                        50% {
11842                            opacity: 1
11843                        }
11844
11845                        100% {
11846                            opacity: .55
11847                        }
11848                    }
11849                </style>
11850
11851                
11851<script>
11852                    /* ===============================
11853                       DATASET TOGEL (SIMULASI)
11854                    ================================ */
11855                    const TOGEL_MARKETS = [
11856                        { market: "Singapore", code: "SGP", open: "17:45", zone: "WIB" },
11857                        { market: "Hongkong", code: "HK", open: "22:45", zone: "WIB" },
11858                        { market: "Sydney", code: "SDY", open: "13:45", zone: "WIB" },
11859                        { market: "Macau", code: "MC", open: "16:00", zone: "WIB" },
11860                        { market: "Cambodia", code: "KHM", open: "10:45", zone: "WIB" },
11861                        { market: "Japan", code: "JP", open: "11:30", zone: "WIB" }
11862                    ];
11863
11864                    const SHIO = ["Tikus", "Kerbau", "Macan", "Kelinci", "Naga", "Ular", "Kuda", "Kambing", "Monyet", "Ayam", "Anjing", "Babi"];
11865
11866                    const $ = (q) => document.querySelector(q);
11867                    const randInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
11868
11869                    function uniqueMarkets() {
11870                        return TOGEL_MARKETS.map(m => m.market);
11871                    }
11872
11873                    function genDigits(len) {
11874                        let s = "";
11875                        for (let i = 0; i < len; i++) s += String(randInt(0, 9));
11876                        return s;
11877                    }
11878
11879                    function genBBFS() {
11880                        const pool = [...Array(10)].map((_, i) => i);
11881                        const out = [];
11882                        for (let i = 0; i < 7; i++) {
11883                            const idx = randInt(0, pool.length - 1);
11884                            out.push(pool.splice(idx, 1)[0]);
11885                        }
11886                        return out.join("");
11887                    }
11888
11889                    function genIkut() {
11890                        const pool = [...Array(10)].map((_, i) => i);
11891                        const out = [];
11892                        for (let i = 0; i < 4; i++) {
11893                            const idx = randInt(0, pool.length - 1);
11894                            out.push(pool.splice(idx, 1)[0]);
11895                        }
11896                        return out.join(" • ");
11897                    }
11898
11899                    function simulateConfidence() {
11900                        return randInt(60, 96);
11901                    }
11902
11903                    function applyFilters() {
11904                        const m = $("#marketFilter").value;
11905                        const minC = parseFloat($("#minConf").value || "0");
11906
11907                        return TOGEL_MARKETS
11908                            .filter(x => (m === "ALL" || x.market === m))
11909                            .map(x => ({ ...x, conf: simulateConfidence() }))
11910                            .filter(x => x.conf >= minC);
11911                    }
11912
11913                    function pickRandom(arr, n = 1) {
11914                        const copy = [...arr];
11915                        const out = [];
11916                        for (let i = 0; i < n && copy.length; i++) {
11917                            const idx = randInt(0, copy.length - 1);
11918                            out.push(copy.splice(idx, 1)[0]);
11919                        }
11920                        return out;
11921                    }
11922
11923                    function formatAngka(type) {
11924                        if (type === "4D") return genDigits(4);
11925                        if (type === "3D") return genDigits(3);
11926                        return genDigits(2);
11927                    }
11928
11929                    function renderMain(item, type) {
11930                        $("#rMarket").textContent = `${item.market} (${item.code}) • Open ${item.open} ${item.zone}`;
11931                        $("#rType").textContent = type;
11932                        $("#rConf").textContent = `${item.conf}%`;
11933
11934                        $("#rAngka").textContent = formatAngka(type);
11935                        $("#rBBFS").textContent = genBBFS();
11936                        $("#rIkut").textContent = genIkut();
11937                        $("#rShio").textContent = SHIO[randInt(0, SHIO.length - 1)];
11938                        $("#rTip").textContent = "Tips: Ini simulasi hiburan. Tetap bijak & disiplin.";
11939                    }
11940
11941                    function renderAlts(list, type) {
11942                        const wrap = $("#togelAltList");
11943                        wrap.innerHTML = "";
11944                        list.forEach(x => {
11945                            const el = document.createElement("div");
11946                            el.className = "togel-alt-item";
11947                            el.innerHTML = `
11948      <div class="g">${x.market} (${x.code})</div>
11949      <div class="p">Tipe ${type} • Conf <strong>${x.conf}%</strong></div>
11950      <div class="r">Angka: <strong>${formatAngka(type)}</strong></div>
11951    `;
11952                            wrap.appendChild(el);
11953                        });
11954                    }
11955
11956                    function spinButton(btn, on) {
11957                        if (on) {
11958                            btn.disabled = true;
11959                            btn.dataset.text = btn.innerHTML;
11960                            btn.innerHTML = `<span class="icon">🔄</span> PROCESS...`;
11961                        } else {
11962                            btn.disabled = false;
11963                            btn.innerHTML = btn.dataset.text || `<span class="icon">🎲</span> GENERATE`;
11964                        }
11965                    }
11966
11967                    /* ===============================
11968                       INIT
11969                    ================================ */
11970                    (function init() {
11971                        const sel = $("#marketFilter");
11972                        uniqueMarkets().forEach(m => {
11973                            const o = document.createElement("option");
11974                            o.value = m; o.textContent = m;
11975                            sel.appendChild(o);
11976                        });
11977
11978                        $("#btnTogelRandom").addEventListener("click", (e) => {
11979                            const btn = e.currentTarget;
11980                            const type = $("#typeFilter").value;
11981                            const filtered = applyFilters();
11982                            const st = $("#togelStatus");
11983
11984                            if (!filtered.length) {
11985                                st.textContent = "— NO MATCH —";
11986                                st.style.color = "white";
11987                                renderAlts([], type);
11988                                $("#rMarket").textContent = "-";
11989                                $("#rType").textContent = type;
11990                                $("#rConf").textContent = "-";
11991                                $("#rAngka").textContent = "-";
11992                                $("#rBBFS").textContent = "-";
11993                                $("#rIkut").textContent = "-";
11994                                $("#rShio").textContent = "-";
11995                                return;
11996                            }
11997
11998                            spinButton(btn, true);
11999                            st.textContent = "— GENERATING —";
12000                            st.style.animation = "pulse .6s linear infinite";
12001                            st.style.color = "white";
12002
12003                            const t0 = setInterval(() => {
12004                                const temp = pickRandom(filtered, 1)[0];
12005                                $("#rMarket").textContent = `${temp.market} (${temp.code}) • Open ${temp.open} ${temp.zone}`;
12006                                $("#rType").textContent = type;
12007                                $("#rConf").textContent = `${temp.conf}%`;
12008                                $("#rAngka").textContent = formatAngka(type);
12009                                $("#rBBFS").textContent = genBBFS();
12010                                $("#rIkut").textContent = genIkut();
12011                                $("#rShio").textContent = SHIO[randInt(0, SHIO.length - 1)];
12012                            }, 85);
12013
12014                            setTimeout(() => {
12015                                clearInterval(t0);
12016
12017                                const picks = pickRandom(filtered, 4);
12018                                renderMain(picks[0], type);
12019                                renderAlts(picks.slice(1), type);
12020
12021                                st.textContent = "— READY —";
12022                                st.style.animation = "";
12023                                st.style.color = "gold";
12024                                spinButton(btn, false);
12025                            }, 1200);
12026                        });
12027
12028                        renderAlts([], "4D");
12029                    })();
12030                </script>
12030
12031
12032<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v8c78df7c7c0f484497ecbca7046644da1771523124516" integrity="sha512-8DS7rgIrAmghBFwoOTujcf6D9rXvH8xm8JQ1Ja01h9QX8EzXldiszufYa4IFfKdLUKTTrnSFXLDkUEOTrZQ8Qg==" data-cf-beacon='{"version":"2024.11.0","token":"1909e908a10849b7af66d8986368283e","r":1,"server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 12032
12032
12033<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v833ccba57c9e4d2798f2e76cebdd09a11778172276447" integrity="sha512-57MDmcccJXYtNnH+ZiBwzC4jb2rvgVCEokYN+L/nLlmO8rfYT/gIpW2A569iJ/3b+0UEasghjuZH/ma3wIs/EQ==" data-cf-beacon='{"version":"2024.11.0","token":"f591dd59004a48eeafbe4ab22ac1a9da","r":1,"server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 12033
12033
12034<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v833ccba57c9e4d2798f2e76cebdd09a11778172276447" integrity="sha512-57MDmcccJXYtNnH+ZiBwzC4jb2rvgVCEokYN+L/nLlmO8rfYT/gIpW2A569iJ/3b+0UEasghjuZH/ma3wIs/EQ==" data-cf-beacon='{"version":"2024.11.0","token":"e160a7c7e3074d01bd34414603c53b23","r":1,"server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 12034
12034
12035<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v833ccba57c9e4d2798f2e76cebdd09a11778172276447" integrity="sha512-57MDmcccJXYtNnH+ZiBwzC4jb2rvgVCEokYN+L/nLlmO8rfYT/gIpW2A569iJ/3b+0UEasghjuZH/ma3wIs/EQ==" data-cf-beacon='{"version":"2024.11.0","token":"7d1313fe7e8b4cc3ac480817ec77c2a1","r":1,"server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 12035
12035
12036<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v833ccba57c9e4d2798f2e76cebdd09a11778172276447" integrity="sha512-57MDmcccJXYtNnH+ZiBwzC4jb2rvgVCEokYN+L/nLlmO8rfYT/gIpW2A569iJ/3b+0UEasghjuZH/ma3wIs/EQ==" data-cf-beacon='{"version":"2024.11.0","token":"7d1313fe7e8b4cc3ac480817ec77c2a1","r":1,"server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 12036
12036
12037<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v833ccba57c9e4d2798f2e76cebdd09a11778172276447" integrity="sha512-57MDmcccJXYtNnH+ZiBwzC4jb2rvgVCEokYN+L/nLlmO8rfYT/gIpW2A569iJ/3b+0UEasghjuZH/ma3wIs/EQ==" data-cf-beacon='{"version":"2024.11.0","token":"57cb2e82efc64548a4a6bee0c0fb1166","r":1,"server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 12037
12037
12038<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v833ccba57c9e4d2798f2e76cebdd09a11778172276447" integrity="sha512-57MDmcccJXYtNnH+ZiBwzC4jb2rvgVCEokYN+L/nLlmO8rfYT/gIpW2A569iJ/3b+0UEasghjuZH/ma3wIs/EQ==" data-cf-beacon='{"version":"2024.11.0","token":"51df3b17a746455f940c0b561e8c0c0b","r":1,"server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
12038
12039
12040<style>
12041body{background:#ebf2fc; font-family:Poppins,sans-serif; color:white;}
12042.BERLINTOTO-container{
12043max-width: 520px;
12044    margin: auto;
12045    padding: 15px;
12046    border-radius: 12px;
12047    background: #a19508;
12048    border: 2px solid #c7ca00;
12049    box-shadow: 0 0 20px #d7d700;
12050}
12051
12052.BERLINTOTO-header{
12053  font-weight:900;
12054  text-align:center;
12055  color:#ff4c4c;
12056  text-shadow:0 0 12px #5f90d9;
12057  margin-bottom:12px;
12058}
12059
12060.BERLINTOTO-controls{
12061  display:grid;
12062  grid-template-columns:1fr 1fr;
12063  gap:10px;
12064  margin-bottom:12px;
12065}
12066.BERLINTOTO-controls label{
12067  display:flex; flex-direction:column;
12068  font-size:11px; color:#ff9999;
12069}
12070.BERLINTOTO-controls select,
12071.BERLINTOTO-controls input{
12072  padding:8px;
12073  border-radius:8px;
12074  border:1px solid #ff4c4c;
12075  background:#2b0000;
12076  color:white;
12077}
12078#btnGenerate{
12079  grid-column:span 2;
12080  padding:12px;
12081  background:#5f90d9;
12082  border:none;
12083  color:black;
12084  font-weight:900;
12085  cursor:pointer;
12086  margin-top:5px;
12087  box-shadow:0 0 15px #ff4c4c;
12088}
12089
12090.BERLINTOTO-result{
12091  margin-top:12px;
12092  padding:12px;
12093  border-radius:10px;
12094  background:rgba(255,0,0,0.05);
12095  border:1px solid #ff4c4c;
12096}
12097
12098.BERLINTOTO-main div, .BERLINTOTO-details div{
12099  margin:5px 0;
12100}
12101
12102#rPrediction{
12103  font-size:24px;
12104  font-weight:900;
12105  color:#5f90d9;
12106}
12107
12108.BERLINTOTO-alts{
12109  margin-top:10px;
12110}
12111.BERLINTOTO-alts h4{margin-bottom:6px;}
12112#BERLINTOTOAltList div{
12113  background:#2b0000;
12114  padding:6px 10px;
12115  margin:3px 0;
12116  border-radius:8px;
12117  border-left:3px solid #5f90d9;
12118}
12119  .bank-tab.active, .bank-tab:hover {
12120    background: #df0000;
12121    color: #fff;
12122    border-color: #df0000;
12123    box-shadow: 0 3px 10px -4px rgb(240 29 29 / 60%);
12124}
12125
12126.BERLINTOTO-disclaimer{
12127  font-size:10px;
12128  margin-top:8px;
12129  text-align:center;
12130  color:#ff9999;
12131}
12132</style>
12133
12134<script>
12135const leagues = ["Premier League","La Liga","Serie A"];
12136const tips = ["Analisis statistik tim","Performa terakhir","Cek head-to-head"];
12137function rand(arr){return arr[Math.floor(Math.random()*arr.length)];}
12138
12139var _bgBtn=document.getElementById("btnGenerate");if(_bgBtn)_bgBtn.onclick=function(){
12140  const type = document.getElementById("betTypeFilter").value;
12141  const league = rand(leagues);
12142
12143  let pred;
12144  if(type==="1X2") pred=rand(["1","X","2"]);
12145  else if(type==="OVER_UNDER") pred=rand(["Over","Under"]);
12146  else pred=rand(["Home -1","Away +1","Draw"]);
12147
12148  document.getElementById("rLeague").innerText=league;
12149  document.getElementById("rPrediction").innerText=pred;
12150  document.getElementById("rBetType").innerText=type;
12151  document.getElementById("rConf").innerText=Math.floor(Math.random()*40+60)+"%";
12152  document.getElementById("rTip").innerText=rand(tips);
12153  document.getElementById("rOdds").innerText=(Math.random()*3+1).toFixed(2);
12154
12155  // alternatif
12156  const alt=document.getElementById("BERLINTOTOAltList"); alt.innerHTML="";
12157  for(let i=0;i<3;i++){let d=document.createElement("div"); d.innerText=rand(["1","X","2"]); alt.appendChild(d);}
12158  
12159  document.getElementById("BERLINTOTOStatus").innerText="— READY —";
12160}
12161</script>
12161
12162
12163
12164                        
12165            <div class="pwp-deals-section" id="pwp-deals-section" hidden data-product-id="325" data-variant-id="0" data-category-path="Komputer &amp; Laptop/Laptop/Gaming"><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">Beli produk ini dan dapatkan produk pilihan dengan harga spesial</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">💡 Produk PWP dapat ditambahkan di halaman keranjang setelah produk ini dimasukkan</p></div>
12166        </div>
12167        </div>
12168        </section>
12169
12170        <aside class="pd-sidebar">
12171            <div class="pd-side-card pd-buy-card">
12172                <form class="pd-buy-form" id="pd-buy-form" data-pid="325">
12173                    <input type="hidden" name="variant_id" id="pd-variant-id" value="0">
12174                    <div>
12175                        <label for="qty" class="qty-label">Qty</label>
12176                        <input type="number" id="qty" name="qty" value="0" min="1"
12177                               max="0"
12178                               disabled>
12179                    </div>
12180                    <div class="pd-actions">
12181                        <button type="submit" class="btn-add-cart" id="btn-add-cart" disabled>+ Keranjang</button>
12182                        <button type="button" class="btn-buy" id="btn-buy" data-id="278426" disabled >Beli</button>
12183                        <button type="button" class="btn-chat-icon" id="btn-chat-icon" aria-label="Tanya Produk" title="Tanya Produk"><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>
12184                                            </div>
12185                </form>
12186                <div class="paylater-promo paylater-promo-md paylater-desktop-only">
12187    <span class="paylater-icon">💳</span>
12188    <span class="paylater-text">
12189        <strong>Autorized BERLINTOTO</strong>
12190        <small>Situs Resmi BERLINTOTO </small>
12191    </span>
12192</div>            </div>
12193
12194                        
12195                        <div class="pd-side-card qoala-banner-card">
12196                <div class="qoala-banner">
12197                    <div class="qoala-banner-content">
12198                        <div class="qoala-icon">
12199                            <center><img src="https://www.bim.miami/iconberlintoto.png" alt="BERLINTOTO" class="qoala-shield-img"></center>
12200                        </div>
12201                        <div class="qoala-text">
12202                            <div class="qoala-title">Login BERLINTOTO</div>
12203                            <div class="qoala-subtitle">Verified</div>
12204                        </div>
12205                    </div>
12206                    <a href="https://bim.miami/aboutus/" class="qoala-cta"
12207                       data-ga-event="qoala_banner_click"
12208                       data-product-id="325"
12209                       data-product-name="BERLINTOTO"
12210                       data-category-path="Komputer &amp; Laptop/Laptop/Gaming" target="_blank">Info</a>
12211                </div>
12212<style>
12213.qoala-banner-card{padding:0;overflow:hidden}
12214
12215.qoala-banner{
12216  background:linear-gradient(135deg, #cdc800 0%, #c90000 100%);
12217  padding:12px 16px;
12218  display:flex;
12219  align-items:center;
12220  justify-content:space-between;
12221  color:white;
12222  text-decoration:none;
12223  min-height:60px;
12224}
12225
12226.qoala-banner-content{
12227  display:flex;
12228  align-items:center;
12229  gap:12px;
12230}
12231
12232.qoala-icon{
12233  display:flex;
12234  align-items:center;
12235  justify-content:center;
12236}
12237
12238.qoala-shield-img{
12239  width:36px;
12240  height:36px;
12241  object-fit:contain;
12242  opacity:0.9;
12243}
12244
12245.qoala-title{
12246  font-weight:600;
12247  font-size:14px;
12248  line-height:1.2;
12249}
12250
12251.qoala-subtitle{
12252  font-size:12px;
12253  opacity:0.9;
12254  line-height:1.2;
12255}
12256
12257.qoala-cta{
12258  background:rgba(255,255,255,0.15);
12259  color:white;
12260  padding:6px 12px;
12261  border-radius:6px;
12262  font-size:12px;
12263  font-weight:600;
12264  text-decoration:none;
12265  transition:background 0.2s ease;
12266  border:1px solid rgba(255,80,80,0.4);
12267}
12268
12269.qoala-cta:hover{
12270  background:rgba(255,60,60,0.25);
12271  color:white;
12272}
12273
12274@media(max-width:768px){
12275  .qoala-banner{padding:10px 12px}
12276  .qoala-title{font-size:17px}
12277  .qoala-subtitle{font-size:11px}
12278}
12279</style>
12280            </div>
12281            
12282                        <div class="pd-side-card pd-installment-card2"
12283                 id="inst-card"
12284                 data-base-price="20789000"
12285                 data-zero-smart="0">
12286                <div class="inst-head">
12287                    <h3>Promo & Fitur BERLINTOTO</h3>
12288                                    </div>
12289
12290                <div class="inst-bank-tabs" role="tablist" aria-label="Bank">
12291                    <button type="button" class="bank-tab active" data-bank="Lucky Draw" aria-selected="true">Lucky Draw</button>
12292                    <button type="button" class="bank-tab" data-bank="Lomba Panjat Tur
12292nover" aria-selected="false">Lomba Panjat Turnover</button>
12293                    <button type="button" class="bank-tab" data-bank="Prediksi Jitu" aria-selected="false">Prediksi Jitu</button>
12294                </div>
12295
12296                <ul class="inst-month-list" id="inst-month-list" aria-label="Daftar cicilan per bulan"></ul>
12297                <div class="inst-note" id="inst-zero-note" style="display:none;">0% Cicilan khusus bank terpilih*</div>
12298            </div>
12299            
12300            <!-- MECHA BLUE FINAL BOSS REVIEW SLIDER -->
12301<div class="tnt-review-mecha">
12302
12303<div class="tnt-header">
12304<h2>REVIEW PEMAIN TOTO TOGEL</h2>
12305
12306</div>
12307
12308<div class="tnt-slider" id="tntSlider">
12309
12310<div class="tnt-slide active">
12311  <div class="tnt-name">Aliong – Surabaya</div>
12312  <div class="tnt-comment">"Sangat puas pasang angka 4D di sini karena hasilnya real-time dan sesuai dengan jadwal pengeluaran resmi. Tampilan dashboard akunnya bersih, riwayat tebakan tercatat transparan, dan sistem klaim diskon togelnya langsung otomatis tanpa ribet."</div>
12313</div>
12314
12315<div class="tnt-slide">
12316  <div class="tnt-name">Santoso88 – Semarang</div>
12317  <div class="tnt-comment">"Urusan transaksi finansial di sini jempolan banget. Deposit pakai scan QRIS instan masuk hitungan detik tanpa perlu konfirmasi manual yang berbelit-belit. Withdraw berapapun selalu diproses cepat dan transparan tanpa ada biaya potongan tersembunyi."</div>
12318</div>
12319
12320<div class="tnt-slide">
12321  <div class="tnt-name">Wijaya – Makassar</div>
12322  <div class="tnt-comment">"Layanan Customer Service di fitur Live Chat bener-bener siaga 24 jam nonstop. Waktu itu pernah salah ketik nominal transaksi pas jam 2 malam, CS-nya langsung bantu selesaikan dengan cepat, ramah, dan solutif. Sistem keamanannya bikin tenang untuk main jangka panjang."</div>
12323</div>
12324
12325
12326
12327</div>
12328</div>
12329
12330</aside>
12331</div>
12332
12333<style>
12334.tnt-review-mecha{
12335  background: radial-gradient(circle at center, #da0000, #350000);
12336  border: 2px solid #aa0000;
12337  border-radius: 14px;
12338  padding: 20px;
12339  color: #dbdbdb;
12340  font-family: 'Orbitron', sans-serif;
12341  position: relative;
12342  overflow: hidden;
12343  box-shadow: 0 0 25px #990000, inset 0 0 20px rgba(255, 0, 0, 0.2);
12344}
12345
12346/* scanline */
12347.tnt-review-mecha::before{
12348  content:"";
12349  position:absolute;
12350  inset:0;
12351  background: repeating-linear-gradient(
12352    to bottom,
12353    rgba(255,0,0,0.08),
12354    rgba(255,0,0,0.08) 1px,
12355    transparent 2px,
12356    transparent 4px
12357  );
12358  pointer-events:none;
12359}.bank-tab.active, .bank-tab:hover
12360  
12361
12362.tnt-header h2{
12363  font-size: 18px;
12364  color:#000000;
12365  text-shadow:0 0 10px #d71414, 0 0 20px #d10000;
12366  margin-bottom:5px;
12367}
12368
12369.tnt-header p{
12370  font-size:12px;
12371  opacity:0.85;
12372  margin-bottom:15px;
12373}
12374
12375.tnt-slider{
12376  position:relative;
12377  height:90px;
12378}
12379
12380.tnt-slide{
12381  position:absolute;
12382  width:100%;
12383  opacity:0;
12384  transform:translateX(40px);
12385  transition:all .6s ease;
12386}
12387  .tenor-badge {
12388    background: #df0000;
12389    color: #fff;
12390    font-weight: 700;
12391    min-width: 42px;
12392    text-align: center;
12393    padding: .38rem .4rem;
12394    font-size: .8rem;
12395    border-radius: 10px;
12396    box-shadow: 0 3px 10px -4px rgb(211 3 3);
12397}
12398
12399.tnt-slide.active{
12400  opacity:1;
12401  transform:translateX(0);
12402}
12403
12404.tnt-name{
12405font-size: 14px;
12406    color: #000000;
12407    margin-bottom: 5px;
12408    text-shadow: 0 0 6px #290000;
12409}
12410
12411.tnt-comment{
12412  font-size:13px;
12413  line-height:1.5;
12414}
12415
12416/* glowing border animation */
12417@keyframes pulseGlow{
12418  0%{box-shadow:0 0 10px #d71414;}
12419  50%{box-shadow:0 0 25px #d71414;}
12420  100%{box-shadow:0 0 10px #d71414;}
12421}
12422
12423.tnt-review-mecha{
12424  animation:pulseGlow 2.5s infinite;
12425}
12426</style>
12427
12428<script>
12429let slides = document.querySelectorAll('.tnt-slide');
12430let index = 0;
12431
12432setInterval(()=>{
12433  slides[index].classList.remove('active');
12434  index = (index + 1) % slides.length;
12435  slides[index].classList.add('active');
12436}, 3000);
12437</script>
12437
12438
12439        <div class="artikel-glow-KT">
12440
12441  <div class="artikel-shine"></div>
12442
12443  <h1>Apa Itu BERLINTOTO | Situs Toto Slot Gacor & Bandar Togel Online 4D Resmi</h1>
12444 <p style="text-align: justify;"><a href="https://www.bim.miami/">BERLINTOTO</a> Hadir situs Toto slot gacor & bandar togel online 4D resmi, BERLINTOTO tampil sebagai solusi portal permainan digital serba ada yang menjawab kebutuhan para pecinta taruhan angka dan putaran mesin modern di Indonesia. Kehadiran platform ini mengusung standar layanan bertaraf internasional dengan memprioritaskan stabilitas sistem, transparansi permainan, serta kemudahan navigasi bagi seluruh member.</p>
12445          
12446<p style="text-align: justify;">Infrastruktur portal ini dibangun di atas jaringan server berspesifikasi tinggi yang dilengkapi sistem keamanan enkripsi data mutakhir. Hal ini memastikan seluruh data pribadi dan riwayat transaksi finansial member terproteksi dengan sangat ketat. Selain itu, optimasi antarmuka yang ringan memungkinkan pengguna untuk mengakses beragam deretan permainan slot gacor mudah JP serta pasaran togel 4D resmi secara stabil melalui ponsel pintar (Android/iOS) maupun komputer tanpa memerlukan aplikasi tambahan seperti VPN.</p>
12447
12448 <p style="text-align: justify;">Sebagai bandar taruhan online terpercaya, penyedia layanan ini menyajikan hasil live draw pengeluaran angka togel secara real-time dan akurat langsung dari server pusat resmi. Seluruh proses transaksi—baik deposit maupun penarikan dana (withdraw)—dijalankan secara serba otomatis melalui berbagai jalur fleksibel, seperti transfer bank lokal, e-wallet, hingga scan QRIS instan. Didukung oleh tim Customer Service profesional yang siaga selama 24 jam nonstop, situs ini menegaskan komitmennya untuk menghadirkan pengalaman bertaruh yang aman, nyaman, dan terbukti terpercaya.</p>
12449          
12450</p>
12451</div>
12452
12453<style>
12454.artikel-glow-KT{
12455      max-width: 820px;
12456    margin: 40px auto;
12457    padding: 36px;
12458    box-sizing: border-box;
12459    position: relative;
12460    overflow: hidden;
12461    border-radius: 30px;
12462    font-family: Arial, sans-serif;
12463    color: #ffffff;
12464    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);
12465    border: 2px solid rgba(255, 255, 255, .55);
12466    box-shadow: 0 0 25px rgb(207 163 3), 0 0 55px rgb(225 0 0), 0 20px 45px rgba(0, 0, 0, .25), inset 0 0 35px rgba(145 0 0);
12467}
12468
12469.artikel-glow-KT:before{
12470  content:"";
12471  position:absolute;
12472  inset:10px;
12473  border-radius:24px;
12474  border:1px solid rgba(255,255,255,.32);
12475  pointer-events:none;
12476}
12477
12478.artikel-shine{
12479  position:absolute;
12480  top:-120px;
12481  left:-180px;
12482  width:280px;
12483  height:650px;
12484  background:linear-gradient(120deg,transparent,rgba(255,255,255,.55),transparent);
12485  transform:rotate(25deg);
12486  animation:artikelShine 4.5s infinite;
12487  pointer-events:none;
12488}
12489
12490@keyframes artikelShine{
12491  0%{left:-220px;}
12492  55%{left:110%;}
12493  100%{left:110%;}
12494}
12495
12496.artikel-glow-KT h1,
12497.artikel-glow-KT h2{
12498  position:relative;
12499  color:#000000;
12500  text-align:center;
12501  line-height:1.35;
12502  text-shadow:
12503    0 0 8px rgba(179 0 0 / 95%),
12504    0 0 18px rgba(197 0 0),
12505    0 2px 8px rgba(149 0 0);
12506}
12507
12508.artikel-glow-KT h1{
12509  font-size:31px;
12510  margin:0 0 20px;
12511}
12512
12513.artikel-glow-KT h2{
12514  font-size:24px;
12515  margin:28px 0 14px;
12516}
12517
12518.artikel-glow-KT p{
12519  position:relative;
12520  margin:0 0 16px;
12521  color:#f8ffff;
12522  font-size:15px;
12523  line-height:1.85;
12524  text-align:justify;
12525  text-shadow:0 1px 4px rgba(0,0,0,.28);
12526}
12527
12528.artikel-glow-KT a{
12529  color:#000000;
12530  font-weight:800;
12531  text-shadow:0 0 8px rgba(153 0 0);
12532}
12533
12534.artikel-glow-KT img{
12535  position:relative;
12536  display:block;
12537  max-width:100%;
12538  margin:16px auto 24px;
12539  border-radius:16px;
12540  border:1px solid rgba(255,255,255,.55);
12541  box-shadow:
12542    0 0 18px rgba(255,255,255,.55),
12543    0 12px 28px rgba(0,0,0,.28);
12544}
12545
12546.artikel-highlight{
12547  position:relative;
12548  margin:24px 0;
12549  padding:22px;
12550  border-radius:22px;
12551  background:rgba(255,255,255,.20);
12552  border:1px solid rgba(255,255,255,.45);
12553  box-shadow:
12554    inset 0 1px 0 rgba(255,255,255,.65),
12555    inset 0 0 22px rgba(255,255,255,.14),
12556    0 10px 25px rgba(0,0,0,.18);
12557  backdrop-filter:blur(8px);
12558}
12559
12560.artikel-highlight:before{
12561  content:"";
12562  position:absolute;
12563  left:0;
12564  right:0;
12565  top:0;
12566  height:45%;
12567  border-radius:22px 22px 0 0;
12568  background:linear-gradient(rgba(255,255,255,.35),rgba(255,255,255,.04));
12569  pointer-events:none;
12570}
12571
12572.artikel-glow-KT ul{
12573  position:relative;
12574  margin:15px 0 0 20px;
12575  padding:0;
12576}
12577
12578.artikel-glow-KT li{
12579  margin-bottom:11px;
12580  padding:10px 12px;
12581  border-radius:12px;
12582  background:rgba(255,255,255,.16);
12583  color:#ffffff;
12584  line-height:1.6;
12585  box-shadow:inset 0 0 10px rgba(255,255,255,.08);
12586}
12587
12588@media(max-width:600px){
12589  .artikel-glow-KT{
12590    margin:25px 12px;
12591    padding:25px;
12592    border-radius:24px;
12593  }
12594
12595  .artikel-glow-KT h1{
12596    font-size:24px;
12597  }
12598
12599  .artikel-glow-KT h2{
12600    font-size:21px;
12601  }
12602}
12603</style>
12604
12605<div class="KT-faq-rainbow">
12606  <div class="KT-shine"></div>
12607
12608  <div class="KT-faq-top">
12609    <span>★ PREMIUM FAQ ★</span>
12610    <h2>Pertanyaan Umum BERLINTOTO</h2>
12611    
12612  </div>
12613
12614  <div class="KT-rainbow-item">
12615    <button type="button">Apa yang membuat BERLINTOTO dikenal sebagai platform taruhan online terpercaya?</button>
12616    <div class="KT-rainbow-answer">
12617      <p>Hadir situs Toto slot gacor & bandar togel online 4D resmi, BERLINTOTO menyajikan ekosistem permainan digital terlengkap yang didukung oleh teknologi server modern, stabilitas akses tanpa kendala pemblokiran, serta jaminan transparansi permainan secara menyeluruh.</p>
12618    </div>
12619  </div>
12620
12621  <div class="KT-rainbow-item">
12622    <button type="button">Pasaran togel apa saja yang dapat dimainkan di BERLINTOTO?</button>
12623    <div class="KT-rainbow-answer">
12624      <p>
12624BERLINTOTO menyediakan pilihan pasaran togel online 4D resmi terpopuler dan terlengkap. Seluruh hasil pengeluaran angka diputar secara live draw dan diperbarui secara real-time langsung dari server pusat yang akurat.</p>
12625    </div>
12626  </div>
12627
12628  <div class="KT-rainbow-item">
12629    <button type="button">Bagaimana cara menggunakan BERLINTOTO untuk membuka situs toto slot dan situs toto togel?</button>
12630    <div class="KT-rainbow-answer">
12631      <p>Keamanan member adalah prioritas utama. Seluruh infrastruktur jaringan BERLINTOTO dilindungi oleh sistem enkripsi data canggih yang menjaga kerahasiaan informasi pribadi serta menjamin keamanan penuh pada setiap riwayat transaksi finansial.</p>
12632    </div>
12633  </div>
12634
12635  <div class="KT-rainbow-item">
12636    <button type="button">Metode pembayaran apa saja yang disediakan untuk transaksi deposit dan withdraw?</button>
12637    <div class="KT-rainbow-answer">
12638      <p>Proses transaksi diproses secara serba otomatis dan fleksibel. Member dapat menggunakan transfer bank lokal utama, fitur scan QRIS instan, hingga berbagai opsi e-wallet populer untuk melakukan pengisian saldo maupun penarikan dana.</p>
12639    </div>
12640  </div>
12641  <div class="KT-rainbow-item">
12642    <button type="button">Ke mana harus menghubungi jika membutuhkan bantuan transaksi atau kendala akun?</button>
12643    <div class="KT-rainbow-answer">
12644      <p>Layanan Customer Service profesional BERLINTOTO bersiaga penuh selama 24 jam nonstop setiap hari melalui fitur Live Chat dan kontak resmi untuk membantu kendala Anda dengan cepat dan ramah.</p>
12645    </div>
12646  </div>
12647</div>
12648
12649<style>
12650.KT-faq-rainbow{
12651      max-width: 760px;
12652    margin: 40px auto;
12653    padding: 30px;
12654    box-sizing: border-box;
12655    position: relative;
12656    overflow: hidden;
12657    border-radius: 32px;
12658    font-family: Arial, sans-serif;
12659    background: radial-gradient(circle at 15% 10%, rgba(177 2 2), transparent 18%), radial-gradient(circle at 85% 15%, rgb(207 163 3), transparent 25%), radial-gradient(circle at 20% 90%, rgb(207 163 3), transparent 25%), linear-gradient(135deg, #c70000, #8e0b0b, #960e0e, #dcce77);
12660    border: 2px solid rgba(177 2 2);
12661    box-shadow: 0 0 25px rgb(207 163 3), 0 0 55px rgba(196 199 0), inset 0 0 35px rgba(145 0 0);
12662}
12663
12664.KT-shine{
12665  position:absolute;
12666  top:-90px;
12667  left:-120px;
12668  width:260px;
12669  height:520px;
12670  background:linear-gradient(120deg,transparent,rgba(255,255,255,.55),transparent);
12671  transform:rotate(25deg);
12672  animation:KTShine 4s infinite;
12673  pointer-events:none;
12674}
12675
12676@keyframes KTShine{
12677  0%{left:-180px;}
12678  55%{left:110%;}
12679  100%{left:110%;}
12680}
12681
12682.KT-faq-top{
12683  position:relative;
12684  text-align:center;
12685  margin-bottom:24px;
12686}
12687
12688.KT-faq-top span{
12689  display:inline-block;
12690  padding:8px 22px;
12691  border-radius:999px;
12692  background:rgba(185 162 0 / 0%);
12693  color:#dbdbdb;
12694  font-size:12px;
12695  font-weight:900;
12696  letter-spacing:2px;
12697  border:1px solid rgba(215 215 215 / 62%);
12698  box-shadow:inset 0 0 14px rgba(255,255,255,.25);
12699}
12700
12701.KT-faq-top h2{
12702  margin:16px 0 8px;
12703  color:#000000;
12704  font-size:34px;
12705  line-height:1.15;
12706  text-shadow:
12707    0 0 10px rgba(177 0 0),
12708    0 0 22px rgba(173 0 0);
12709}
12710
12711.KT-faq-top p{
12712  margin:0;
12713  color:#f3ffff;
12714  font-size:14px;
12715}
12716
12717.KT-rainbow-item{
12718  position:relative;
12719  overflow:hidden;
12720  margin-bottom:14px;
12721  border-radius:20px;
12722  background:rgba(255,255,255,.20);
12723  border:1px solid rgba(255,255,255,.48);
12724  box-shadow:
12725    inset 0 1px 0 rgba(255,255,255,.6),
12726    0 10px 25px rgba(0,0,0,.18);
12727  backdrop-filter:blur(10px);
12728}
12729
12730.KT-rainbow-item:before{
12731  content:"";
12732  position:absolute;
12733  top:0;
12734  left:0;
12735  right:0;
12736  height:45%;
12737  background:linear-gradient(rgba(255,255,255,.32),rgba(255,255,255,.04));
12738  pointer-events:none;
12739}
12740
12741.KT-rainbow-item button{
12742  width:100%;
12743  padding:18px 58px 18px 20px;
12744  border:0;
12745  background: #af0000;
12746  color:#dbdbdb;
12747  text-align:left;
12748  cursor:pointer;
12749  font-size:15px;
12750  font-weight:900;
12751  position:relative;
12752  z-index:2;
12753  text-shadow:0 2px 6px rgba(0,0,0,.35);
12754}
12755
12756.KT-rainbow-item button:after{
12757  content:"+";
12758  position:absolute;
12759  right:18px;
12760  top:50%;
12761  transform:translateY(-50%);
12762  width:34px;
12763  height:34px;
12764  border-radius:50%;
12765  background:linear-gradient(135deg,#b10000, #9b0000);
12766  color:#d1d1d1;
12767  text-align:center;
12768  line-height:34px;
12769  font-size:24px;
12770  font-weight:900;
12771  box-shadow:
12772    0 0 14px rgba(255,255,255,.8),
12773    inset 0 -4px 8px rgba(0,0,0,.18);
12774}
12775
12776.KT-rainbow-item.active button:after{
12777  content:"−";
12778  background:linear-gradient(135deg,#ff0000, #5b0000);
12779}
12780
12781.KT-rainbow-answer{
12782  max-height:0;
12783  overflow:hidden;
12784  transition:max-height .35s ease;
12785  position:relative;
12786  z-index:2;
12787}
12788
12789.KT-rainbow-item.active .KT-rainbow-answer{
12790  max-height:170px;
12791}
12792
12793.KT-rainbow-answer p{
12794  margin:0;
12795  padding:0 20px 18px;
12796  color:#dbdbdb;
12797  font-size:14px;
12798  line-height:1.7;
12799  text-shadow:0 2px 5px rgba(0 0 0);
12800}
12801
12802@media(max-width:600px){
12803  .KT-faq-rainbow{
12804    margin:25px 12px;
12805    padding:22px;
12806    border-radius:24px;
12807  }
12808
12809  .KT-faq-top h2{
12810    font-size:26px;
12811  }
12812}
12813</style>
12814
12815<script>
12816document.querySelectorAll(".KT-rainbow-item button").forEach(function(btn){
12817  btn.addEventListener("click", function(){
12818    this.parentElement.classList.toggle("active");
12819  });
12820});
12821</script>
12821
12822<style>
12823@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@500;700;900&display=swap');
12824
12825.cyber-mecha {
12826  max-width: 800px;
12827  margin: 40px auto;
12828
12829  /* deep luxury red */
12830  background: radial-gradient(circle at top, #14b1d7, #14b1d7 70%);
12831
12832  border-radius: 16px;
12833  border: 1px solid rgba(255, 90, 90, 0.35);
12834
12835  box-shadow:
12836    0 0 25px rgba(140, 30, 30, 0.35),
12837    inset 0 0 20px rgba(255, 60, 60, 0.10);
12838
12839  padding: 25px;
12840  font-family: 'Orbitron', sans-serif;
12841  color: #f7e6e6;
12842  position: relative;
12843  overflow: hidden;
12844}
12845
12846/* scanline overlay */
12847.cyber-mecha::before {
12848  content: "";
12849  position: absolute;
12850  inset: 0;
12851  background: repeating-linear-gradient(
12852    to bottom,
12853    rgba(255,90,90,0.03),
12854    rgba(255,90,90,0.03) 1px,
12855    transparent 1px,
12856    transparent 3px
12857  );
12858  pointer-events: none;
12859}
12860
12861.pd-full-desc-title {
12862  text-align: center;
12863  font-size: 22px;
12864
12865  /* elegant muted red */
12866  color: #e08b8b;
12867
12868  letter-spacing: 2px;
12869  text-shadow: 0 0 10px rgba(224, 139, 139, 0.4);
12870  margin-bottom: 20px;
12871}
12872
12873.mecha-header h3 {
12874  text-align: center;
12875  font-size: 18px;
12876  color: #ffb3b3;
12877  text-shadow: 0 0 8px rgba(255, 90, 90, 0.5);
12878  margin-bottom: 15px;
12879}
12880
12881.mecha-image img {
12882  width: 100%;
12883  border-radius: 10px;
12884  border: 1px solid rgba(255, 90, 90, 0.4);
12885  box-shadow: 0 0 20px rgba(255, 60, 60, 0.25);
12886  margin-bottom: 20px;
12887}
12888
12889.mecha-content p {
12890  font-size: 14px;
12891  line-height: 1.8;
12892  margin-bottom: 14px;
12893  color: #f3dede;
12894}
12895
12896.glow {
12897  color: #e08b8b;
12898  text-shadow: 0 0 6px rgba(224, 139, 139, 0.5);
12899  font-weight: 600;
12900}
12901
12902.glow-strong {
12903  color: #ff6b6b;
12904  text-shadow:
12905    0 0 10px rgba(255, 90, 90, 0.6),
12906    0 0 20px rgba(255, 60, 60, 0.4);
12907  font-weight: 700;
12908}
12909
12910.final-line {
12911  margin-top: 18px;
12912  padding: 10px;
12913  border: 1px solid rgba(255, 90, 90, 0.35);
12914  background: rgba(60, 0, 0, 0.25);
12915  border-radius: 8px;
12916  text-align: center;
12917  color: #ffd0d0;
12918}
12919</style>
12920    
12921    
12922    
12923        <section class="pd-related-full">
12924        <h2 class="pd-related-title">Produk Terkait</h2>
12925        <div class="pd-related-grid pd-related-grid--full">
12926                        <a class="pd-related-card" href="https://www.bim.miami/">
12927                <div class="thumb">
12928                    <img src="https://www.bim.miami/berlintoto.png" alt="BERLINTOTO">
12929                    <span class="mini-disc">-2%</span>                </div>
12930                <div class="info">
12931                    <h3>BERLINTOTO</h3>
12932                    <div class="spec-chips">I5 <span class="spec-divider">|</span> 16GB <span class="spec-divider">|</span> 512GB <span class="spec-divider">|</span> RTX3050</div>                    <span class="price">
12933                        <span class="final">Rp 11.500.000</span>
12934                                                  <span class="ori">Rp 12.799.000</span>
12935                                            </span>
12936                    <span class="sold">Terjual 200+</span>                    <span class="stock-dot low" title="Stok Terbatas"></span>
12937                </div>
12938            </a>
12939                        <a class="pd-related-card" href="https://www.bim.miami/">
12940                <div class="thumb">
12941                    <img src="https://www.bim.miami/berlintoto.png" alt="TOTO TOGEL">
12942                    <span class="mini-disc">-1%</span>                </div>
12943                <div class="info">
12944                    <h3>BERLIN TOTO</h3>
12945                    <div class="spec-chips">I7 <span class="spec-divider">|</span> 16GB <span class="spec-divider">|</span> 512GB <span class="spec-divider">|</span> RTX5050</div>                    <span class="price">
12946                        <span class="final">Rp 22.887.000</span>
12947                                                  <span class="ori">Rp 21.999.000</span>
12948                                            </span>
12949                    <span class="sold">Terjual 20+</span>                    <span class="stock-dot out" title="Habis"></span>
12950                </div>
12951            </a>
12952                        <a class="pd-related-card" href="https://www.bim.miami/">
12953                <div class="thumb">
12954                    <img src="https://www.bim.miami/berlintoto.png" alt="BERLINTOTO">
12955                    <span class="mini-disc">-1%</span>                </div>
12956                <div class="info">
12957                    <h3>LOGIN BERLINTOTO</h3>
12958                    <div class="spec-chips">R7AI <span class="spec-divider">|</span> 16GB <span class="spec-divider">|</span> 1TB <span class="spec-divider">|</span> RTX5060</div>                    <span class="price">
12959                        <span class="final">Rp 23.600.000</span>
12960                                                  <span class="ori">Rp 21.399.000</span>
12961                                            </span>
12962                    <span class="sold">Terjual 6</span>                    <span class="stock-dot ready" title="Ready"></span>
12963                </div>
12964            </a>
12965                        <a class="pd-related-card" href="https://www.bim.miami/">
12966                <div class="thumb">
12967                    <img src="https://www.bim.miami/berlintoto.png" alt="TOGEL 4D">
12968                    <span class="mini-disc">-1%</span>                </div>
12969                <div class="info">
12970                    <h3>DAFTAR BERLINTOTO</h3>
12971                    <div class="spec-chips">R5AI <span class="spec-divider">|</span> 16GB <span class="spec-divider">|</span> 1TB <span class="spec-divider">|</span> RTX5060</div>                    <span class="price">
12972                        <span class="final">Rp 18.800.000</span>
12973                                                  <span class="ori">Rp 19.799.000</span>
12974                                            </span>
12975                    <span class="sold">Terjual 5</span>                    <span class="stock-dot ready" title="Ready"></span>
12976                </div>
12977            </a>
12978                        <a class="pd-related-card" href="https://www.bim.miami/">
12979                <div class="thumb">
12980                    <img src="https://www.bim.miami/berlintoto.png" alt="TOTO TOGEL 4D">
12981                    <span class="mini-disc">-1%</span>                </div>
12982                <div class="info">
12983                    <h3>LINK BERLINTOTO</h3>
12984                    <div class="spec-chips">I7 <span class="spec-divider">|</span> 16GB <span class="spec-divider">|</span> 512GB <span class="spec-divider">|</span> RTX5050</div>                    <span class="price">
12985                        <span class="final">Rp 28.600.080</span>
12986                                                  <span class="ori">Rp 27.999.000</span>
12987                                            </span>
12988                    <span class="sold">Terjual 6</span>                    <span class="stock-dot low" title="Stok Terbatas"></span>
12989                </div>
12990            </a>
12991                        <a class="pd-related-card" href="https://www.bim.miami/">
12992                <div class="thumb">
12993                    <img src="https://www.bim.miami/berlintoto.png" alt="BERLINTOTO">
12994                    <span class="mini-disc">-1%</span>                </div>
12995                <div class="info">
12996                    <h3>SLOT GACOR</h3>
12997                    <div class="spec-chips">CORE5 <span class="spec-divider">|</span> 16GB <span class="spec-divider">|</span> 512GB <span class="spec-divider">|</span> RTX4050</div>                    <span class="price">
12998                        <span class="final">Rp 17.888.000</span>
12999                                                  <span class="ori">Rp 18.499.000</span>
13000                                            </span>
13001                                        <span class="stock-dot low" title="Stok Terbatas"></span>
13002                </div>
13003            </a>
13004                        
13005                    </div>
13006    </section>
13007    
13008    <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
13008-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="mf-btn mf-chat" id="mf-chat" aria-label="Tanya Produk"><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">Beli Sekarang</span><span class="mf-buy-price">Rp 20.789.000</span></button></div></div>
13009
13010    <button type="button"
13011            class="pd-scroll-top"
13012            id="pd-scroll-top"
13013            aria-label="Kembali ke atas"
13014            title="Kembali ke atas">
13015        <svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
13016            <path fill="currentColor" d="M7.41 14.59 12 10l4.59 4.59L18 13.17l-6-6-6 6z"/>
13017        </svg>
13018    </button>
13019</main>
13020
13021<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 Produk</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://www.bim.miami/berlintoto.png" alt="BERLINTOTO" class="pd-share-preview-img"><div class="pd-share-preview-info"><h3 class="pd-share-preview-name">BERLINTOTO</h3><p class="pd-share-preview-price">Rp 20.789.000</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">
13021<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://www.bim.miami/" 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>
13022
13023<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;
13023left: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>
13024<style>
13025.toast .msg { word-break: break-word; overflow-wrap: anywhere; }
13026
13027.toast-root { bottom: max(12px, env(safe-area-inset-bottom, 12px)); }
13028
13029@media (max-width: 540px) {
13030  .toast-root {
13031    left: 0; right: 0; transform: none;
13032    padding: 0 12px;
13033    bottom: calc(max(12px, env(safe-area-inset-bottom, 12px)) + var(--fab-offset, 72px));
13034    max-width: none;
13035  }
13036  .toast {
13037    grid-template-columns: 1fr; /* 1 kolom */
13038    position: relative;
13039    padding: 14px 14px 12px 14px;
13040    border-radius: 16px;
13041  }
13042  .toast .ico {
13043    position: absolute; left: 14px; top: 14px;
13044    margin: 0; transform: translateY(1px);
13045  }
13046  .toast .cnt { padding-left: 34px; }
13047  .toast .ttl { font-size: 15px; margin-bottom: 6px; }
13048  .toast .msg { font-size: 13px; opacity: .96; }
13049  .toast .actions {
13050    margin-top: 12px; gap: 10px; width: 100%;
13051    display: grid; grid-template-columns: 1fr 1fr;
13052  }
13053  .toast .btn { width: 100%; min-width: 0; padding: 10px 0; }
13054  .toast .close { position: absolute; right: 6px; top: 6px; }
13055}
13056</style>
13057
13058<script nonce="se3iyzEHMc9FQEB4wSyd0w==">
13059(function adjustToastFabOffset(){
13060  const root = document.getElementById('toastRoot');
13061  if (!root) return;
13062
13063  function isVisible(el){
13064    if (!el) return false;
13065    const cs = getComputedStyle(el);
13066    if (cs.display === 'none' || cs.visibility === 'hidden' || cs.opacity === '0') return false;
13067    const r = el.getBoundingClientRect();
13068    return r.width > 0 && r.height > 0;
13069  }
13070
13071  function update(){
13072    const fab = document.getElementById('pd-mobile-fab');
13073    let extra = 16;
13074    if (isVisible(fab)) {
13075      const h = Math.round(fab.getBoundingClientRect().height) || 56;
13076      extra = h + 16; // FAB height + margin
13077    }
13078    root.style.setProperty('--fab-offset', extra + 'px');
13079  }
13080
13081  window.addEventListener('resize', update, { passive: true });
13082  const fab = document.getElementById('pd-mobile-fab');
13083  if (window.ResizeObserver && fab) {
13084    const ro = new ResizeObserver(update);
13085    ro.observe(fab);
13086  }
13087  
13088  update();
13089})();
13090</script>
13090
13091<div class="toast-root" id="toastRoot" aria-live="polite" aria-atomic="true"></div>
13092
13093<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>'}
13094window.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>`;
13095
13096    const close = ()=> {
13097      if (!el.parentNode) return;
13098      el.style.transition = 'transform .2s ease, opacity .2s ease';
13099      el.style.opacity = '0'; el.style.transform = 'translateY(10px)';
13100      setTimeout(()=>{ if (el.parentNode) el.parentNode.removeChild(el); }, 220);
13101    };
13102
13103    el.querySelector('.close').addEventListener('click', close);
13104    el.querySelectorAll('.actions .btn').forEach(btn=>{
13105      btn.addEventListener('click', ()=>{
13106        const lab = btn.getAttribute('data-act');
13107        try {
13108          (o.actions||[]).forEach(a=>{ if (a.label===lab && typeof a.onClick==='function') a.onClick(); });
13109        } catch(_){}
13110        close();
13111      });
13112    });
13113
13114    root.appendChild(el);
13115    setTimeout(close, o.duration);
13116    return el;
13117  };
13118})();
13119
13120const _0x1a2b=[{"url":"https://www.bim.miami/berlintoto.png","alt":"Lenovo Legion Go 8ASP2 - 83N0000RID /AMD Ryzen Z2 Extreme/32GB/1TB SSD/AMD Radeon Graphics/8.8\" WUXGA OLED Touch/Win 11 Home/Eclipse Black/3Y","slug":""},{"url":"https://www.bim.miami/berlintoto.png","alt":"Lenovo Lenovo Legion Go 8ASP2 - 83N0000RID /AMD Ryzen Z2 Extreme/32GB/1TB SSD/AMD Radeon Graphics/8.8\" WUXGA OLED Touch/Wi...","slug":"https://www.bim.miami/berlintoto.png"},{"url":"https://www.bim.miami/berlintoto.png","alt":"Lenovo Lenovo Legion Go 8ASP2 - 83N0000RID /AMD Ryzen Z2 Extreme/32GB/1TB SSD/AMD Radeon Graphics/8.8\" WUXGA OLED Touch/Wi...","slug":"lenovo-legion-go-8asp2-83n0000rid-ryzen-z2-extreme-amd-radeon-graphics-eclipse-b-1761796941-223.webp"},{"url":"https://www.bim.miami/berlintoto.png","alt":"Lenovo Lenovo Legion Go 8ASP2 - 83N0000RID /AMD Ryzen Z2 Extreme/32GB/1TB SSD/AMD Radeon Graphics/8.8\" WUXGA OLED Touch/Wi...","slug":"lenovo-legion-go-8asp2-83n0000rid-ryzen-z2-extreme-amd-radeon-graphics-eclipse-b-1761796941-776.webp"},{"url":"https://www.bim.miami/berlintoto.png","alt":"Lenovo Lenovo Legion Go 8ASP2 - 83N0000RID /AMD Ryzen Z2 Extreme/32GB/1TB SSD/AMD Radeon Graphics/8.8\" WUXGA OLED Touch/Wi...","slug":"lenovo-legion-go-8asp2-83n0000rid-ryzen-z2-extreme-amd-radeon-graphics-eclipse-b-1761796941-634.webp"},{"url":"https://www.bim.miami/berlintoto.png","alt":"Lenovo Lenovo Legion Go 8ASP2 - 83N0000RID /AMD Ryzen Z2 Extreme/32GB/1TB SSD/AMD Radeon Graphics/8.8\" WUXGA OLED Touch/Wi...","slug":"lenovo-legion-go-8asp2-83n0000rid-ryzen-z2-extreme-amd-radeon-graphics-eclipse-b-1761796941-892.webp"}],_0x3c4d="Lenovo Legion Go 8ASP2 - 83N0000RID /AMD Ryzen Z2 Extreme/32GB/1TB SSD/AMD Radeon Graphics/8.8\" WUXGA OLED Touch/Win 11 Home/Eclipse Black/3Y",_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=>
13120c.textContent.trim()).filter(Boolean);return b.length?b.join(' / '):''}
13121function encodeVariantHash(id){if(!id||id<=0)return '';const xor=id^0x4A5D;return xor.toString(36)}
13122function decodeVariantHash(hash){if(!hash)return 0;const xor=parseInt(hash,36);return isNaN(xor)?0:xor^0x4A5D}
13123
13124function 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)} · 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:"Lenovo Legion Go 8ASP2 - 83N0000RID /AMD Ryzen Z2 Extreme/32GB/1TB SSD/AMD Radeon Graphics/8.8\" WUXGA OLED Touch/Win 11 Home/Eclipse Black/3Y",item_brand:"Lenovo ",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})}
13125
13126function executeBuyNowLegacy(id,variantId){const a=getVariantSummaryText(),b=(function(){const c=document.getElementById('pd-sku');return c?c.textContent.trim():_0x5e6f})();
13127
13128    let willRedirect = false;
13129    try {
13130        if (window.addToCart) {
13131            window.addToCart(id, 1, variantId);
13132            willRedirect = false;
13133        }
13134    } catch(_){}
13135
13136    if (willRedirect) {
13137        showToast({
13138            type: 'info',
13139            title: 'Menuju Keranjang',
13140            message: `<strong>${escapeHtml(PD_NAME)}</strong>${variantText?` · <em>${escapeHtml(variantText)}</em>`:''}<br>SKU: ${escapeHtml(skuText)} · Qty: 1`,
13141            duration: 1800
13142        });
13143        setTimeout(()=>{ window.location.href='/keranjang'; }, 1200);
13144    } else {
13145        showToast({
13146            type: 'warn',
13147            title: 'Fitur Beli Langsung Segera Hadir',
13148            message: `Sementara ini Anda bisa menambahkan produk ke keranjang terlebih dahulu.<br><strong>${escapeHtml(PD_NAME)}</strong>${variantText?` · <em>${escapeHtml(variantText)}</em>`:''}`,
13149            duration: 6000,
13150            actions: [
13151                { label: '+ Keranjang', onClick: ()=> { try{ if (window.addToCart) window.addToCart(id,1,variantId); }catch(_){} } },
13152                { label: 'Hubungi CS', variant: 'alt', onClick: ()=> { window.open('https://wa.me/6281380009898','_blank'); } }
13153            ]
13154        });
13155    }
13156}
13157
13158document.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)})}});
13159
13160function _0x2m3n(s){return String(s||'').replace(/[&<>"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[m]))}
13161
13162(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')}})})();
13163
13164(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;
13164slides.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})})();
13165
13166/* ================= Simulasi Cicilan ================= */
13167(function initInstallmentList(){const card=document.getElementById('inst-card');if(!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'}
13168
13169function _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'}
13170
13171card.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)});
13172
13173window.instSetBasePrice=function(newPrice){setBasePrice(newPrice);_0x4o5p(activeBank())};_0x4o5p(activeBank());
13174})();
13175
13176/* ====== Fokus gambar varian ====== */
13177function _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 ''}}}
13178function _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}}
13179  if (c){
13180    const targetName = _0x8s9t(url);
13181    const slideImg = Array.from(c.querySelectorAll('img')).find(im => _0x8s9t(im.src) === targetName);
13182    if (slideImg){
13183      
13184      try {
13185        const container = c;
13186        const imgRect = slideImg.getBoundingClientRect();
13187        const contRect = container.getBoundingClientRect();
13188        const offsetLeft = slideImg.offsetLeft - container.offsetLeft;
13189        const center = offsetLeft - (container.clientWidth - slideImg.clientWidth) / 2;
13190        container.scrollTo({ left: Math.max(0, center), behavior: 'smooth' });
13191      } catch(_){
13192        
13193        slideImg.scrollIntoView({ behavior:'smooth', inline:'center', block:'nearest' });
13194      }
13195    }
13196  }
13197}
13198function findImageForVariant(variant){
13199  if (!variant || !variant.attrs) return null;
13200  const tokens = [];
13201  try{
13202    Object.values(variant.attrs).forEach(v=>{
13203      if (!v) return;
13204      const raw = String(v);
13205      tokens.push(raw);
13206      tokens.push(normalize(raw));
13207    });
13208  }catch(_){}
13209  if (variant.sku){
13210    tokens.push(String(variant.sku));
13211    tokens.push(normalize(String(variant.sku)));
13212  }
13213  if (variant.image_url) {
13214      tokens.push(String(variant.image_url));
13215  }
13216  let best = null;
13217  let bestScore = -1;
13218
13219  for (const g of (galleryData||[])){
13220    const slugN = normalize(g.slug||'');
13221    const altN  = normalize(g.alt||'');
13222    const file  = _0x8s9t(g.url||'');
13223    const fileN = normalize(file);
13224    const imgU  = g.url;
13225
13226    for (const t of tokens){
13227      const tN = normalize(t);
13228      if (!tN) continue;
13229
13230      let score = -1;
13231      if (imgU === tN) score = 300;
13232      if (slugN && slugN === tN) score = 300;
13233      else if (fileN && fileN.includes(tN)) score = 200;
13234      else if (altN && altN.includes(tN)) score = 120;
13235
13236      if (score > bestScore){
13237        bestScore = score;
13238        best = g;
13239      }
13240      if (bestScore >= 300) break;
13241    }
13242    if (bestScore >= 300) break;
13243  }
13244  return bestScore >= 120 ? best : null;
13245}
13246function _0xey3z(variant){const a=_0xcw2x(variant);if(a&&a.url){_0xau1v(a.url,a.alt||'')}}
13247
13248/* ====== Layout mobile ====== */
13249(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;
13250
13251    let pricePlaced = false;
13252    let varPlaced = false;
13253    let titlePlaced = false;
13254    let promoPlaced = false;
13255    const mq = window.matchMedia('(max-width: 640px)');
13256
13257    function apply(){
13258        if (mq.matches) {
13259            if (!titlePlaced && a && titleRow && d.contains(titleRow)) { try{a.appendChild(titleRow);titlePlaced=true;}catch(_){} }
13260            if (!pricePlaced && a && b) { try{a.appendChild(b);pricePlaced=true;}catch(_){} }
13261            if (!varPlaced && f && d && e) {
13262                try{
13263                    f.hidden = false;
13264                    if (i && !f.contains(i)) f.appendChild(i);
13265                    if (g && !f.contains(g)) f.appendChild(g);
13266                    if(d.contains(e))d.insertBefore(f, e);
13267                    varPlaced = true;
13268                }catch(_){}
13269            }
13270            if (!promoPlaced && promoBanner && compareSection && compareSection.parentNode) {
13271                try{
13272                    compareSection.parentNode.insertBefore(promoBanner, compareSection);
13273                    promoPlaced = true;
13274                }catch(_){}
13275            }
13276        } else {
13277            if (titlePlaced && titleRow && d) {
13278                try{
13279                    const vcombined = document.getElementById('pd-variant-combined');
13280                    if (vcombined && d.contains(vcombined)) {
13281                        d.insertBefore(titleRow, vcombined);
13282                    } else {
13283                        d.insertBefore(titleRow, d.firstChild);
13284                    }
13285                    titlePlaced=false;
13286                }catch(_){}
13287            }
13288            if (pricePlaced && b && c && c.parentNode) {
13289                try{c.parentNode.insertBefore(b, c.nextSibling);pricePlaced=false;}catch(_){}
13290            }
13291            if (varPlaced) {
13292                try{
13293                    if (i && j && j.parentNode && !j.parentNode.contains(i)) j.parentNode.insertBefore(i, j.nextSibling);
13294                    if (g && h && h.parentNode && !h.parentNode.contains(g)) h.parentNode.insertBefore(g, h.nextSibling);
13295                    if (f) { f.hidden = true; f.innerHTML = ''; }
13296                    varPlaced = false;
13297                }catch(_){}
13298            }
13299            if (promoPlaced && promoBanner && b) {
13300                try{
13301                    b.appendChild(promoBanner);
13302                    promoPlaced = false;
13303                }catch(_){}
13304            }
13305        }
13306    }
13307
13308    mq.addEventListener ? mq.addEventListener('change', apply) : mq.addListener(apply);
13309    apply();
13310
13311    const fab = document.getElementById('pd-mobile-fab');
13312    if (fab) {
13313        const pid = parseInt(fab.getAttribute('data-pid')||'0',10) || 325;
13314        const btnCart = document.getElementById('mf-cart');
13315
13316        if (btnCart) btnCart.addEventListener('click', function(){
13317            const varInput = document.getElementById('pd-variant-id');
13318            const variantId = varInput ? parseInt(varInput.value||'0',10) : 0;
13319            try { if (window.addToCart) addToCart(pid, 1, variantId); } catch(_){}
13320            const variantText = getVariantSummaryText();
13321            const skuText = (function(){ const el=document.getElementById('pd-sku'); return el?el.textContent.trim():PD_SKU; })();
13322            showToast({
13323                type: 'success',
13324                title: 'Berhasil ditambahkan ke Keranjang',
13325                message: `<strong>${escapeHtml(PD_NAME)}</strong>${variantText?` · <em>${escapeHtml(variantText)}</em>`:''}<br>SKU: ${escapeHtml(skuText)} · Qty: 1`,
13326                duration: 5000,
13327                actions: [
13328                    { label: 'Lihat Keranjang', onClick: ()=> { window.location.href = '/keranjang'; } },
13329                    { label: 'Lanjut Belanja', variant: 'alt', onClick: ()=>{} }
13330                ]
13331            });
13332        });
13333    }
13334})();
13335
13336/* ====== Auto-hide installment banner if sidebar too tall (Desktop only) ====== */
13337(function(){
13338    function adjustSidebarBanner() {
13339        if (window.innerWidth <= 768) return;
13340        const sidebar = document.querySelector('.pd-sidebar');
13341        const infoCard = document.querySelector('.pd-info-card');
13342        const banner = document.querySelector('.pd-side-card.inst-alt-banner.inst-alt-banner--compact');
13343        if (!sidebar || !infoCard || !banner) return;
13344        const sidebarHeight = sidebar.offsetHeight;
13345        const infoCardHeight = infoCard.offsetHeight;
13346        if (sidebarHeight > infoCardHeight) {
13347            banner.style.display = 'none';
13348        } else {
13349            banner.style.display = '';
13350        }
13351    }
13352    if (document.readyState === 'loading') {
13353        document.addEventListener('DOMContentLoaded', adjustSidebarBanner);
13354    } else {
13355        adjustSidebarBanner();
13356    }
13357    let resizeTimer;
13358    window.addEventListener('resize', function() {
13359        clearTimeout(resizeTimer);
13360        resizeTimer = setTimeout(adjustSidebarBanner, 250);
13361    });
13362    const pwpSection = document.getElementById('pwp-deals-section');
13363    if (pwpSection) {
13364        const observer = new MutationObserver(function(mutations) {
13365            let shouldRecheck = false;
13366            mutations.forEach(function(mutation) {
13367                if (mutation.type === 'attributes' && mutation.attributeName === 'hidden') {
13368                    shouldRecheck = true;
13369                } else if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
13370                    shouldRecheck = true;
13371                }
13372            });
13373            if (shouldRecheck) {
13374                setTimeout(adjustSidebarBanner, 100);
13375            }
13376        });
13377        observer.observe(pwpSection, {
13378            attributes: true,
13379            childList: true,
13380            subtree: true
13381        });
13382    }
13383    window.adjustSidebarBanner = adjustSidebarBanner;
13384})();
13385
13386/* ====== Global constants ====== */
13387const IS_DIGITAL=false;
13388const IS_PREORDER=false;
13389
13390/* ====== Variants state (REVISED) ====== */
13391(function initVariants(){
13392const _0x7g8h=[];window.variants=_0x7g8h;
13393window.isPreorderProduct=false;
13394    const attrOrder = [];
13395
13396    if (!variants.length || !attrOrder.length) return;
13397
13398    function comboKey(sel){
13399        return attrOrder.map(a => a + '=' + (sel[a] ?? '')).join('|');
13400    }
13401
13402    const variantMap = {};
13403    variants.forEach(v=>{
13404        const key = comboKey(v.attrs);
13405        variantMap[key] = v;
13406    });
13407
13408    function readSelection(){
13409        const sel = {};
13410        attrOrder.forEach(a=>{
13411            const r = document.querySelector(`.v-group[data-attr="${CSS.escape(a)}"] input[type=radio]:checked`);
13412            sel[a] = r ? r.value : '';
13413        });
13414        return sel;
13415    }
13416
13417    function findVariant(sel){
13418        return variantMap[comboKey(sel)] || null;
13419    }
13420
13421    function autoFillMissing(sel){
13422        let changed = false;
13423        for (const a of attrOrder) {
13424            if (sel[a]) continue;
13425            const possible = [];
13426            const seen = new Set();
13427            variants.forEach(v=>{
13428                let ok = true;
13429                for (const a2 of attrOrder) {
13430                    if (a2 === a) continue;
13431                    if (sel[a2] && v.attrs[a2] !== sel[a2]) { ok = false; break; }
13432                }
13433                if (ok) {
13434                    const val = v.attrs[a];
13435                    if (!seen.has(val)) { seen.add(val); possible.push(val); }
13436                }
13437            });
13438            if (possible.length === 1) {
13439                const val = possible[0];
13440                const input = document.querySelector(`.v-group[data-attr="${CSS.escape(a)}"] input[type=radio][value="${CSS.escape(val)}"]`);
13441                if (input && !input.checked) { input.checked = true; sel[a] = val; changed = true; }
13442            }
13443        }
13444        return changed;
13445    }
13446
13447    function rupiah(n){
13448        n = parseInt(n||0,10)||0;
13449        return 'Rp ' + n.toString().replace(/\B(?=(\d{3})+(?!\d))/g,'.');
13450    }
13451    
13452    function focusGalleryByUrl(imageUrl) {
13453        const isMobile = window.matchMedia && window.matchMedia('(max-width: 640px)').matches;
13454          if (isMobile) {
13455            _0xau1v(imageUrl);
13456            return;
13457          }
13458        
13459          const gallery = document.getElementById('pd-thumb-gallery');
13460          const targetName = _0x8s9t(imageUrl);
13461        
13462          if (gallery) {
13463            const btns = Array.from(gallery.querySelectorAll('.pd-thumb-btn'));
13464            const btn = btns.find(b => _0x8s9t(b.getAttribute('data-full') || '') === targetName);
13465            if (btn) { btn.click(); return; }
13466          }
13467          
13468          _0xau1v(imageUrl);
13469    }
13470    
13471
13472function formatStockLabel(n){n=parseInt(n||0,10)||0;return(n>10)?'10+':String(n)}
13473function 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(_){}}
13474
13475    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)}
13476
13477    function applyVariant(v, focusImage = true){
13478        const elCur  = document.getElementById('pd-price-current');
13479        const elOri  = document.getElementById('pd-price-original');
13480        const elDisc = document.getElementById('pd-discount-badge');
13481        const elSku  = document.getElementById('pd-sku');
13482        const elStock= document.getElementById('pd-stock-badge');
13483        const elQty  = document.getElementById('qty');
13484        const btnAdd = document.getElementById('btn-add-cart');
13485        const btnBuy = document.getElementById('btn-buy');
13486        const hidVar = document.getElementById('pd-variant-id');
13487
13488        const mfCart= document.getElementById('mf-cart');
13489        const mfBuy = document.getElementById('mf-buy');
13490        const fab = document.getElementById('pd-mobile-fab');
13491
13492        if (hidVar) hidVar.value = v ? v.id : 0;
13493        if (elCur) elCur.textContent = rupiah(v ? v.final : 0);
13494        if (elSku && v && v.sku) elSku.textContent = v.sku;
13495
13496        if (v && v.ori > v.final) {
13497            if (elOri) { elOri.style.display='inline'; elOri.textContent = rupiah(v.ori); }
13498            if (elDisc) { elDisc.style.display='inline'; elDisc.textContent = '-' + (v.disc||0) + '%'; }
13499        } else {
13500            if (elOri) elOri.style.display='none';
13501            if (elDisc) elDisc.style.display='none';
13502        }
13503
13504        const stock = v ? (parseInt(v.stock||0,10)||0) : 0;
13505        let effectiveStock = stock;
13506        let isActivePreorder = false;
13507        if (window.isPreorderProduct && v && 'preorder_remaining' in v && v.preorder_remaining !== null) {
13508            effectiveStock = parseInt(v.preorder_remaining, 10);
13509            if (isNaN(effectiveStock)) effectiveStock = 0;
13510            isActivePreorder = true;
13511        }
13512        if (elStock){
13513            elStock.classList.remove('in','out');
13514            if (effectiveStock>0){ 
13515                elStock.classList.add('in'); 
13516                elStock.textContent = IS_DIGITAL ? 'Tersedia' : (isActivePreorder ? 'Sisa Slot: ' + effectiveStock : 'Stok: ' + formatStockLabel(effectiveStock)); 
13517            } else { 
13518                elStock.classList.add('out'); 
13519                elStock.textContent = 'Habis'; 
13520            }
13521        }
13522        if (elQty){
13523            elQty.max = String(effectiveStock>0?effectiveStock:0);
13524            if (effectiveStock<=0) { elQty.value = '0'; elQty.disabled = true; }
13525            else { if (parseInt(elQty.value||'1',10) < 1) elQty.value='1'; elQty.disabled=false; }
13526        }
13527        
13528        const shouldDisable = !(v && effectiveStock>0);
13529        const updateButtons = () => {
13530            const btnAddCart = document.getElementById('btn-add-cart');
13531            const btnBuyNow = document.getElementById('btn-buy');
13532            const mobileCart = document.getElementById('mf-cart');
13533            const mobileBuy = document.getElementById('mf-buy');
13534            [btnAddCart, btnBuyNow, mobileCart, mobileBuy].forEach(btn => {
13535                if (!btn) return;
13536                if (shouldDisable) {
13537                    btn.disabled = true;
13538                    btn.setAttribute('disabled', 'disabled');
13539                    btn.classList.add('stock-disabled');
13540                    btn.setAttribute('data-stock-blocked', 'true');
13541                    btn.setAttribute('data-stock-reason', window.isPreorderProduct ? 'Pre-order habis' : 'Stok habis');
13542                } else {
13543                    btn.disabled = false;
13544                    btn.removeAttribute('disabled');
13545                    btn.classList.remove('stock-disabled');
13546                    btn.removeAttribute('data-stock-blocked');
13547                    btn.removeAttribute('data-stock-reason');
13548                }
13549            });
13550        };
13551        updateButtons();
13552        setTimeout(updateButtons, 10);
13553        setTimeout(updateButtons, 50);
13554        setTimeout(updateButtons, 100);
13555        if (mfBuy && v) {
13556            const voucherBtn = document.getElementById('btn-buy-voucher');
13557            const voucherCode = fab ? fab.dataset.voucherCode : '';
13558            let voucherDiscount = 0;
13559            
13560            if (voucherBtn && voucherCode) {
13561                const vType = voucherBtn.dataset.voucherType;
13562                const vValue = parseFloat(voucherBtn.dataset.voucherValue) || 0;
13563                const vMax = parseFloat(voucherBtn.dataset.voucherMax) || 0;
13564                const finalPrice = v.final || 0;
13565                if (vType && vValue > 0) {
13566                    voucherDiscount = calcVoucherDisc(vType, vValue, finalPrice, vMax);
13567                    if (fab) fab.dataset.voucherValue = voucherDiscount.toString();
13568                }
13569            }
13570            
13571            const finalPrice = v.final || 0;
13572            const textEl = mfBuy.querySelector('.mf-buy-text');
13573            const priceEl = mfBuy.querySelector('.mf-buy-price');
13574            
13575            if (effectiveStock > 0) {
13576                    if (IS_PREORDER) {
13577                        if (textEl) textEl.textContent = 'Pre Order Sekarang';
13578                        if (priceEl) priceEl.textContent = rupiah(finalPrice);
13579                    } else if (voucherCode && voucherDiscount > 0) {
13580                        if (textEl) textEl.textContent = 'Beli dengan Voucher';
13581                        if (priceEl) priceEl.textContent = rupiah(Math.max(0, finalPrice - voucherDiscount));
13582                    } else {
13583                        if (textEl) textEl.textContent = 'Beli Sekarang';
13584                        if (priceEl) priceEl.textContent = rupiah(finalPrice);
13585                    }
13586                } else {
13587                    if (textEl) textEl.textContent = 'Stok Habis';
13588                    if (priceEl) priceEl.textContent = rupiah(finalPrice);
13589                }
13590            }
13591        
13592        const isMobile = window.initMobile ?? 0;
13593        if (window.instSetBasePrice) window.instSetBasePrice(v ? (isMobile == '1' ? v.ori : v.final) : 0);
13594        if (window.updateSoldBySku) window.updateSoldBySku(v.sku);
13595        updateBranchPopover(v);
13596        if (v && v.image_url && focusImage) focusGalleryByUrl(v.image_url);
13597        if (v) {
13598            window.dispatchEvent(new CustomEvent('pd-variant-changed', {
13599                detail: {
13600                    price: v.final,
13601                    originalPrice: v.ori,
13602                    discount: v.disc,
13603                    stock: effectiveStock,
13604                    sku: v.sku,
13605                    variant_id: v.id
13606                }
13607            }));
13608            
13609            const encodedHash = encodeVariantHash(v.id);
13610            if (encodedHash && window.history && window.history.replaceState) {
13611                const newHash = '#v=' + encodedHash;
13612                const newUrl = window.location.pathname + window.location.search + newHash;
13613                window.history.replaceState(null, '', newUrl);
13614            }
13615        }
13616    }
13617
13618    let sel = readSelection();
13619    if (autoFillMissing(sel)) sel = readSelection();
13620
13621    let v = findVariant(sel);
13622    if (!v) {
13623        const candidates = variants.filter(variant=>{
13624            return attrOrder.every(a=>{
13625                if (!sel[a]) return true;
13626                return variant.attrs[a] === sel[a];
13627            });
13628        });
13629        if (candidates.length >= 1) {
13630            v = candidates[0];
13631            attrOrder.forEach(a=> sel[a] = v.attrs[a]);
13632            attrOrder.forEach(a=>{
13633                const val = sel[a];
13634                const input = document.querySelector(`.v-group[data-attr="${CSS.escape(a)}"] input[type=radio][value="${CSS.escape(val)}"]`);
13635                if (input) input.checked = true;
13636            });
13637        }
13638    }
13639
13640    const initSel = readSelection();
13641    const initVar = findVariant(initSel) || variants[0] || null;
13642    if (document.readyState === 'loading') {
13643        document.addEventListener('DOMContentLoaded', () => applyVariant(initVar, false));
13644    } else {
13645        applyVariant(initVar, false);
13646    }
13647    
13648function updateSummary(sel){Object.entries(sel).forEach(([a,val])=>{const el=document.getElementById('vs-'+a);if(el)el.textContent=val||'-'})}
13649    function refreshOptions(sel){
13650        attrOrder.forEach(a=>{
13651            const group = document.querySelector(`.v-group[data-attr="${CSS.escape(a)}"]`);
13652            if (!group) return;
13653            const chips = group.querySelectorAll('.v-chip');
13654            chips.forEach(ch=>{
13655                const val = ch.getAttribute('data-value');
13656                const possible = variants.some(v=>{
13657                    if (v.attrs[a] !== val) return false;
13658                    for (const a2 of attrOrder) {
13659                        if (a2 === a) continue;
13660                        if (sel[a2] && v.attrs[a2] !== sel[a2]) return false;
13661                    }
13662                    return true;
13663                });
13664                const input = ch.querySelector('input[type=radio]');
13665                const disabled = !possible;
13666                if (input) input.disabled = disabled;
13667                ch.classList.toggle('disabled', disabled);
13668
13669                let candidateVariant = null;
13670                if (possible) {
13671                    candidateVariant = variants.find(v=>{
13672                        if (v.attrs[a] !== val) return false;
13673                        for (const a2 of attrOrder) {
13674                            if (a2 === a) continue;
13675                            if (sel[a2] && v.attrs[a2] !== sel[a2]) return false;
13676                        }
13677                        return true;
13678                    });
13679                }
13680                const outOfStock = candidateVariant ? (window.isPreorderProduct && candidateVariant.preorder_remaining !== null ? candidateVariant.preorder_remaining <= 0 : candidateVariant.stock <= 0) : false;
13681                ch.classList.toggle('out', !disabled && outOfStock);
13682                const badge = ch.querySelector('.chip-badge');
13683                if (badge) badge.hidden = !(candidateVariant && outOfStock);
13684            });
13685            group.querySelectorAll('.v-chip').forEach(ch=>{
13686                const input = ch.querySelector('input[type=radio]');
13687                ch.classList.toggle('selected', input && input.checked);
13688            });
13689        });
13690    }
13691    updateSummary(sel);
13692    refreshOptions(sel);
13693    document.querySelectorAll('.v-chip').forEach(chip=>{const input=chip.querySelector('input[type=radio]');if(input&&input.checked){chip.classList.add('selected');}});
13694
13695    const container = document.getElementById('pd-variants');
13696    container && container.addEventListener('change', function(ev){
13697        const radio = ev.target.closest('input[type=radio]');
13698        if (!radio) return;
13699        sel = readSelection();
13700        if (autoFillMissing(sel)) sel = readSelection();
13701        const chosen = findVariant(sel);
13702        applyVariant(chosen || null, true);
13703        
13704        updateSummary(sel);
13705        refreshOptions(sel);
13706    });
13707    
13708window.applyVariant=applyVariant;
13709})();
13710
13711(function cleanBrokenDescImages(){
13712    const containers = [
13713        document.querySelector('.pd-full-desc-body'),
13714        document.querySelector('.pd-short-desc')
13715    ].filter(Boolean);
13716
13717    containers.forEach(root => {
13718        const imgs = root.querySelectorAll('img');
13719        imgs.forEach(img => {
13720            if (img.complete && img.naturalWidth === 0) {
13721                img.remove();
13722                return;
13723            }
13724            img.addEventListener('error', () => {
13725                img.remove();
13726            }, { once:true });
13727        });
13728    });
13729})();
13730
13731/* ====== Scroll To Top ====== */
13732(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'})})})();
13733
13734/* Format stok 10+ */
13735function _0xk67l(n){n=parseInt(n||0,10)||0;return n>10?'10+':String(n)}
13736
13737/* Toggle popover stok per cabang */
13738(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=''}}
13739function 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()}
13740a.addEventListener('click',function(h){h.preventDefault();b.classList.contains('show')?f():e()});if(c)c.addEventListener('click',f)})();
13741
13742/* ===== Share Product Modal ===== */
13743(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://www.bim.miami/",g="Lenovo Legion Go 8ASP2 - 83N0000RID /AMD Ryzen Z2 Extreme/32GB/1TB SSD/AMD Radeon Graphics/8.8\" WUXGA OLED Touch/Win 11 Home/Eclipse Black/3Y",h="Rp 20.789.000";const discountPercent=1;let i='';if(discountPercent>0){i=`🔥 Lagi Promo Nih! Diskon ${discountPercent}%\n\n✨ ${g}\n💰 Cuma ${h} aja!\n\n🛒 Buruan cek sebelum kehabisan!\n👉 `}else{i=`✨ Ada barang bagus nih!\n\n📦 ${g}\n💸 Harga: ${h}\n\n🛍️ 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
13743ncodeURIComponent(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)}})();
13744
13745/* ===== Embedding variants (server → client) ===== */
13746(function(){
13747  window.__PD_VARIANTS_WITH_BRANCH__ = [];
13748})();
13749
13750/* ===== Helper: update angka popover ===== */
13751function updateStockPopoverForVariant(v){
13752  try{
13753    const elMeg = document.getElementById('sp-qty-meg');
13754    const elMdf = document.getElementById('sp-qty-mdf');
13755    const elBda = document.getElementById('sp-qty-bda');
13756
13757    if (typeof v?.smm === 'number' && elMeg) elMeg.textContent = formatStockLabel(Math.max(0, v.smm|0));
13758    if (typeof v?.smf === 'number' && elMdf) elMdf.textContent = formatStockLabel(Math.max(0, v.smf|0));
13759    if (typeof v?.sba === 'number' && elBda) elBda.textContent = formatStockLabel(Math.max(0, v.sba|0));
13760  }catch(_){}
13761}
13762
13763/* ===== applyVariant: 10+ dan update popover ===== */
13764(function patchApplyVariant(){
13765  if (typeof applyVariant !== 'function') return;
13766  const original = applyVariant;
13767  window.applyVariant = function(v){
13768    original(v);
13769    
13770    try {
13771      const elStock = document.getElementById('pd-stock-badge');
13772      if (elStock){
13773        let txt = elStock.textContent || '';
13774        let m = txt.match(/\d+/);
13775        if (m) {
13776          const n = parseInt(m[0]||'0',10)||0;
13777          elStock.textContent = (n>0) ? ('Stok: ' + formatStockLabel(n)) : 'Habis';
13778        }
13779      }
13780    } catch(_){}
13781if(window.__PD_VARIANTS_WITH_BRANCH__&&v){const vv=window.__PD_VARIANTS_WITH_BRANCH__.find(x=>x.id===v.id)||v;updateStockPopoverForVariant(vv)}
13782  };
13783})();
13784
13785(function initCurrentVariantBranch(){
13786  const list = window.__PD_VARIANTS_WITH_BRANCH__ || [];
13787  if (!list.length) return;
13788  const hid = document.getElementById('pd-variant-id');
13789  const vid = hid ? parseInt(hid.value||'0',10) : 0;
13790  const current = list.find(x => x.id === vid) || list[0] || null;
13791  if (current) updateStockPopoverForVariant(current);
13792})();
13793
13794(function(){
13795    if (!window.ga4Ecomm) return;
13796    const catSegs = ["Komputer & Laptop","Laptop","Gaming"];
13797    window.ga4Ecomm.viewItem({
13798      currency: 'IDR',
13799      value: 20789000,
13800      item_id: "SB0B3T",
13801      item_name: "Lenovo Legion Go 8ASP2 - 83N0000RID /AMD Ryzen Z2 Extreme/32GB/1TB SSD/AMD Radeon Graphics/8.8\" WUXGA OLED Touch/Win 11 Home/Eclipse Black/3Y",
13802      item_brand: "Lenovo ",
13803      item_category: catSegs && catSegs.length ? catSegs.join(' / ') : ''
13804    });
13805  })();
13806  
13807(function patchApplyVariantFocus(){
13808  if (typeof applyVariant !== 'function') return;
13809  const original = applyVariant;
13810  window.applyVariant = function(v, focusImage = true){
13811    
13812    original(v, focusImage);
13813    
13814    if (focusImage && v && v.image_url) {
13815      _0xau1v(v.image_url);
13816    }
13817  };
13818})();
13819
13820(function() {
13821  var soldMap = [];
13822  var soldTotalLabel = "Terjual 2";
13823
13824  function formatSoldLabel(qty) {
13825    if (qty >= 1000000) {
13826      var v = Math.floor((qty / 100000) ) / 10;
13827      return 'Terjual ' + (String(v).replace('.', ',')) + 'jt+';
13828    }
13829    if (qty >= 1000) {
13830      var v2 = Math.floor((qty / 100) ) / 10;
13831      return 'Terjual ' + (String(v2).replace('.', ',')) + 'rb+';
13832    }
13833    if (qty >= 100) {
13834      var v2 = Math.floor((qty / 100) ) * 100;
13835      return 'Terjual ' + (String(v2).replace('.', ',')) + '+';
13836    }
13837    if (qty >= 10) {
13838      var v2 = Math.floor((qty / 10) ) * 10;
13839      return 'Terjual ' + (String(v2).replace('.', ',')) + '+';
13840    }
13841    return 'Terjual ' + (qty.toLocaleString('id-ID'));
13842  }
13843
13844  function updateSoldLabel(labelText) {
13845    var el = document.querySelector('#pd-sold-inline, [data-role="sold-chip"]');
13846    if (el) el.textContent = labelText;
13847  }
13848
13849  window.updateSoldBySku = function(sku) {
13850    if (!sku || typeof sku !== 'string') {
13851      updateSoldLabel(soldTotalLabel);
13852      return;
13853    }
13854    var qty = soldMap[sku] || 0;
13855    updateSoldLabel(formatSoldLabel(qty));
13856  };
13857
13858    updateSoldLabel(soldTotalLabel);
13859  })();
13860
13861(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)}})}
13862function 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():'Rp 20.789.000';const pd={id:325,name:"Lenovo Legion Go 8ASP2 - 83N0000RID \/AMD Ryzen Z2 Extreme\/32GB\/1TB SSD\/AMD Radeon Graphics\/8.8\" WUXGA OLED Touch\/Win 11 Home\/Eclipse Black\/3Y",price:currentPrice,image:"\/uploads\/products\/lenovo-legion-go-8asp2-83n0000rid.webp",url:window.location.href};if(variantId>0){pd.variant_id=variantId}if(variantSummary){pd.variant_summary=variantSummary}return pd}
13863const 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}});
13863function formatRupiah(n){return'Rp'+Math.floor(n).toLocaleString('id-ID')}})();
13864
13865(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')}})();
13866</script>
vendor: 1 bytes, line 13866
13866
13867<script nonce="se3iyzEHMc9FQEB4wSyd0w==">
13868window.PD_ID=325;window.PD_NAME=_0x3c4d;window.PD_SKU=_0x5e6f;window.PD_CATEGORY_PATH="Komputer & Laptop/Laptop/Gaming";window.PD_BEST_VOUCHER=null;
13869window.IS_DIGITAL=IS_DIGITAL;
13870window.isPreorderProduct=false;
13871window.PD_PREORDER_DATA=null;
13872</script>
13872
13873
13874<script data-inlined="pd-lightbox.js">
13875// PlazaIT Product Lightbox (vanilla, no CDN) — v4 (swipe-down to close)
13876(function(){
13877  const state = {
13878    items: [],
13879    open: false,
13880    idx: 0,
13881    scale: 1,
13882    minScale: 1,
13883    maxScale: 4,
13884    tx: 0, ty: 0,
13885    isDragging: false,
13886    dragStartX: 0, dragStartY: 0,
13887    baseTx: 0, baseTy: 0,
13888    touchStartX: 0, touchStartY: 0,
13889    lastTap: 0,
13890    swipeMode: 'none', // 'none' | 'h' | 'v'
13891    totalDx: 0, totalDy: 0
13892  };
13893
13894  let root, imgEl, counterEl, btnPrev, btnNext, btnClose, canvas;
13895
13896  const ORIGIN = (location.origin || (location.protocol + '//' + location.host));
13897
13898  function absUrl(u){
13899    if (!u) return '';
13900    try {
13901      if (/^https?:\/\//i.test(u)) return u;
13902      return new URL(u, ORIGIN).href;
13903    } catch(_) { return u; }
13904  }
13905  function fileName(u){
13906    try { return new URL(u, ORIGIN).pathname.split('/').pop().toLowerCase(); }
13907    catch(_){ return String(u||'').split('/').pop().toLowerCase(); }
13908  }
13909
13910  function buildItems(){
13911    const gd = (window.galleryData || []).filter(Boolean);
13912    if (gd.length){
13913      state.items = gd.map(g => ({
13914        src: absUrl(g.url), alt: g.alt || '', slug: g.slug || '', w: g.w || 1200, h: g.h || 1200
13915      })).filter(it => !!it.src);
13916      return;
13917    }
13918    const thumbs = document.querySelectorAll('.pd-thumb-btn');
13919    state.items = Array.from(thumbs).map(btn => {
13920      const raw = btn.getAttribute('data-full') || '';
13921      return { src: absUrl(raw), alt: btn.getAttribute('data-alt') || '', slug: btn.getAttribute('data-slug') || '', w: 1200, h: 1200 };
13922    }).filter(it => !!it.src);
13923  }
13924
13925  function ensureUI(){
13926    if (root) return;
13927    root = document.createElement('div');
13928    root.className = 'pd-lb';
13929    root.id = 'pd-lightbox';
13930    root.setAttribute('role','dialog');
13931    root.setAttribute('aria-modal','true');
13932    root.setAttribute('aria-hidden','true');
13933    root.setAttribute('tabindex','-1');
13934
13935    // Canvas terlebih dulu (z-index di CSS membuat tombol di atas)
13936    root.innerHTML = `
13937      <div class="pd-lb-canvas"><img class="pd-lb-img" id="pd-lb-img" alt=""></div>
13938      <button class="pd-lb-close" aria-label="Tutup" title="Tutup">&times;</button>
13939      <button class="pd-lb-prev" aria-label="Sebelumnya" title="Sebelumnya">&#10094;</button>
13940      <button class="pd-lb-next" aria-label="Berikutnya" title="Berikutnya">&#10095;</button>
13941      <div class="pd-lb-counter" id="pd-lb-counter">1 / 1</div>
13942    `;
13943    document.body.appendChild(root);
13944
13945    imgEl     = root.querySelector('#pd-lb-img');
13946    counterEl = root.querySelector('#pd-lb-counter');
13947    btnPrev   = root.querySelector('.pd-lb-prev');
13948    btnNext   = root.querySelector('.pd-lb-next');
13949    btnClose  = root.querySelector('.pd-lb-close');
13950    canvas    = root.querySelector('.pd-lb-canvas');
13951
13952    // Button clicks
13953    btnPrev.addEventListener('click', (e)=>{ e.stopPropagation(); prev(); });
13954    btnNext.addEventListener('click', (e)=>{ e.stopPropagation(); next(); });
13955    btnClose.addEventListener('click', (e)=>{ e.stopPropagation(); close(); });
13956
13957    // Klik backdrop untuk tutup
13958    root.addEventListener('click', (e)=>{ if (e.target === root) close(); });
13959
13960    // Keyboard global
13961    document.addEventListener('keydown', (e)=>{
13962      if (!state.open) return;
13963      if (e.key === 'Escape') { e.preventDefault(); close(); }
13964      if (e.key === 'ArrowRight') { e.preventDefault(); next(); }
13965      if (e.key === 'ArrowLeft')  { e.preventDefault(); prev(); }
13966    });
13967
13968    // Wheel zoom (desktop)
13969    root.addEventListener('wheel', (e)=>{
13970      if (!state.open) return;
13971      e.preventDefault();
13972      const delta = -Math.sign(e.deltaY) * 0.2;
13973      zoomAt(delta, e.clientX, e.clientY);
13974    }, { passive: false });
13975
13976    // Drag pan (mouse)
13977    canvas.addEventListener('mousedown', (e)=>{
13978      if (state.scale <= 1) return;
13979      state.isDragging = true;
13980      state.dragStartX = e.clientX;
13981      state.dragStartY = e.clientY;
13982      state.baseTx = state.tx;
13983      state.baseTy = state.ty;
13984      e.preventDefault();
13985    });
13986    window.addEventListener('mousemove', (e)=>{
13987      if (!state.isDragging) return;
13988      const dx = e.clientX - state.dragStartX;
13989      const dy = e.clientY - state.dragStartY;
13990      setTranslate(state.baseTx + dx, state.baseTy + dy);
13991    });
13992    window.addEventListener('mouseup', ()=>{ state.isDragging = false; });
13993
13994    // Touch: swipe navigate (H), swipe-down close (V), double-tap zoom, drag pan
13995    canvas.addEventListener('touchstart', (e)=>{
13996      if (!state.open) return;
13997      if (e.touches.length === 1) {
13998        const t = e.touches[0];
13999        state.touchStartX = t.clientX;
14000        state.touchStartY = t.clientY;
14001        state.totalDx = 0;
14002        state.totalDy = 0;
14003        state.swipeMode = 'none';
14004
14005        state.isDragging = state.scale > 1;
14006        state.dragStartX = t.clientX;
14007        state.dragStartY = t.clientY;
14008        state.baseTx = state.tx;
14009        state.baseTy = state.ty;
14010
14011        // Double-tap toggle zoom
14012        const now = Date.now();
14013        if (now - state.lastTap < 300) {
14014          if (state.scale > 1) resetTransform(); else zoomAt(+1, t.clientX, t.clientY, 2);
14015          state.lastTap = 0;
14016          e.preventDefault();
14017          return;
14018        }
14019        state.lastTap = now;
14020      }
14021    }, { passive: true });
14022
14023    canvas.addEventListener('touchmove', (e)=>{
14024      if (!state.open) return;
14025
14026      // Drag pan saat zoom
14027      if (state.isDragging && e.touches.length === 1 && state.scale > 1) {
14028        e.preventDefault();
14029        const t = e.touches[0];
14030        const dx = t.clientX - state.dragStartX;
14031        const dy = t.clientY - state.dragStartY;
14032        setTranslate(state.baseTx + dx, state.baseTy + dy);
14033        return;
14034      }
14035
14036      // Gesture saat scale <= 1
14037      if (state.scale <= 1 && e.touches.length === 1) {
14038        const t = e.touches[0];
14039        const dx = t.clientX - state.touchStartX;
14040        const dy = t.clientY - state.touchStartY;
14041        state.totalDx = dx; state.totalDy = dy;
14042
14043        // Tentukan mode swipe di awal gerakan
14044        if (state.swipeMode === 'none') {
14045          const absX = Math.abs(dx), absY = Math.abs(dy);
14046          if (absY > 12 && absY > absX * 1.3) {
14047            state.swipeMode = 'v'; // swipe vertical (untuk close)
14048          } else if (absX > 12 && absX > absY * 1.3) {
14049            state.swipeMode = 'h'; // swipe horizontal (ganti slide)
14050          }
14051        }
14052
14053        if (state.swipeMode === 'v') {
14054          // Ikuti jari, kurangi opacity untuk feedback
14055          e.preventDefault();
14056          setRootDrag(dy);
14057        } else if (state.swipeMode === 'h') {
14058          // Biarkan sampai touchend untuk menentukan next/prev (tidak perlu preventDefault di sini)
14059        }
14060      }
14061    }, { passive: false });
14062
14063    canvas.addEventListener('touchend', (e)=>{
14064      if (!state.open) return;
14065
14066      if (state.scale <= 1) {
14067        const dx = state.totalDx;
14068        const dy = state.totalDy;
14069
14070        if (state.swipeMode === 'v') {
14071          const TH = 80; // threshold jarak piksel untuk menutup
14072          if (Math.abs(dy) > TH) {
14073            // Animasi keluar arah swipe
14074            animateClose(dy);
14075          } else {
14076            // Snap back
14077            resetRootDrag(true);
14078          }
14079        } else if (state.swipeMode === 'h') {
14080          if (Math.abs(dx) > 50) { dx < 0 ? next() : prev(); }
14081        }
14082      }
14083
14084      state.isDragging = false;
14085      state.swipeMode = 'none';
14086      state.totalDx = 0;
14087      state.totalDy = 0;
14088    });
14089  }
14090
14091  function setImage(idx){
14092    if (!state.items.length) return;
14093    state.idx = (idx + state.items.length) % state.items.length;
14094    const it = state.items[state.idx];
14095    imgEl.src = it.src;
14096    imgEl.alt = it.alt || '';
14097    counterEl.textContent = (state.idx + 1) + ' / ' + state.items.length;
14098    resetTransform();
14099    preloadNeighbors();
14100  }
14101
14102  function resetTransform(){
14103    state.scale = 1;
14104    state.tx = 0; state.ty = 0;
14105    applyTransform();
14106  }
14107
14108  function setTranslate(tx, ty){
14109    state.tx = tx; state.ty = ty;
14110    applyTransform();
14111  }
14112
14113  function applyTransform(){
14114    imgEl.style.transform = `translate3d(${state.tx}px, ${state.ty}px, 0) scale(${state.scale})`;
14115  }
14116
14117  function zoomAt(delta, clientX, clientY, snapTo){
14118    const old = state.scale;
14119    let next = (typeof snapTo === 'number') ? snapTo : (old + delta);
14120    next = Math.max(state.minScale, Math.min(state.maxScale, next));
14121
14122    if (next !== old) {
14123      const rect = imgEl.getBoundingClientRect();
14124      const cx = clientX - rect.left - rect.width/2;
14125      const cy = clientY - rect.top  - rect.height/2;
14126      state.tx -= cx * (next/old - 1);
14127      state.ty -= cy * (next/old - 1);
14128      state.scale = next;
14129      applyTransform();
14130    }
14131  }
14132
14133  function preloadNeighbors(){
14134    if (state.items.length < 2) return;
14135    const nextIdx = (state.idx + 1) % state.items.length;
14136    const prevIdx = (state.idx - 1 + state.items.length) % state.items.length;
14137    [nextIdx, prevIdx].forEach(i=>{
14138      const img = new Image(); img.src = state.items[i].src;
14139    });
14140  }
14141
14142  // ----- Swipe-down visual helpers -----
14143  function setRootDrag(dy){
14144    const damped = dy * 0.6; // redam gerakan agar lembut
14145    const abs = Math.abs(dy);
14146    const fade = Math.max(0, Math.min(1, 1 - abs / 300)); // transparansi sesuai jarak
14147    root.style.transform = `translate3d(0, ${damped}px, 0)`;
14148    root.style.opacity = String(0.95 * fade + 0.05); // jangan jadi 0 total sebelum close
14149  }
14150  function resetRootDrag(withAnim){
14151    if (withAnim) {
14152      root.style.transition = 'transform .18s ease, opacity .18s ease';
14153      root.style.transform = 'translate3d(0,0,0)';
14154      root.style.opacity = '1';
14155      setTimeout(()=>{ root.style.transition = ''; }, 190);
14156    } else {
14157      root.style.transition = '';
14158      root.style.transform = '';
14159      root.style.opacity = '';
14160    }
14161  }
14162  function animateClose(dy){
14163    root.style.transition = 'transform .18s ease, opacity .18s ease';
14164    const off = (dy > 0 ? window.innerHeight : -window.innerHeight);
14165    root.style.transform = `translate3d(0, ${off}px, 0)`;
14166    root.style.opacity = '0';
14167    setTimeout(()=>{ root.style.transition = ''; close(); }, 180);
14168  }
14169
14170  function setSiblingsInert(makeInert){
14171    // Inert semua saudara root (elemen lain di body) agar fokus tidak lari
14172    const nodes = Array.from(document.body.children).filter(el => el !== root);
14173    nodes.forEach(el=>{
14174      if (makeInert) {
14175        if (!el.hasAttribute('inert')) el.setAttribute('inert','');
14176      } else {
14177        if (el.hasAttribute('inert')) el.removeAttribute('inert');
14178      }
14179    });
14180  }
14181
14182  function open(idx){
14183    if (!state.items.length) buildItems();
14184    if (!state.items.length) return;
14185    ensureUI();
14186
14187    // Reset efek drag visual
14188    resetRootDrag(false);
14189
14190    document.body.style.overflow = 'hidden';
14191    root.classList.add('show');
14192    root.removeAttribute('aria-hidden'); // hindari aria-hidden saat fokus child
14193    setSiblingsInert(true);
14194
14195    state.open = true;
14196    setImage(typeof idx === 'number' ? idx : 0);
14197
14198    // Fokuskan dialog agar keyboard pasti tertangkap
14199    try { root.focus(); } catch(_){}
14200  }
14201
14202  function close(){
14203    state.open = false;
14204    root.classList.remove('show');
14205    root.setAttribute('aria-hidden','true');
14206    setSiblingsInert(false);
14207    document.body.style.overflow = '';
14208
14209    // Bersihkan transform/opacity
14210    resetRootDrag(false);
14211  }
14212
14213  function next(){ setImage(state.idx + 1); }
14214  function prev(){ setImage(state.idx - 1); }
14215
14216  function findIndexByUrl(url){
14217    const target = fileName(absUrl(url));
14218    const idx = state.items.findIndex(it => fileName(it.src) === target);
14219    return idx >= 0 ? idx : 0;
14220  }
14221
14222  function initPdLightbox(){
14223    buildItems();
14224    ensureUI();
14225
14226    // Klik gambar utama → buka LB di index yang sama
14227    const mainImg = document.getElementById('pd-main-img');
14228    if (mainImg) {
14229      mainImg.style.cursor = 'zoom-in';
14230      mainImg.addEventListener('click', ()=>{
14231        open(findIndexByUrl(mainImg.src));
14232      });
14233    }
14234
14235    // Klik gambar slider mobile → buka LB di gambar yang diketuk
14236    const slider = document.getElementById('pd-mobile-slider');
14237    if (slider) {
14238      slider.addEventListener('click', (e)=>{
14239        const im = e.target && e.target.tagName === 'IMG' ? e.target : e.target.closest('img');
14240        if (!im) return;
14241        open(findIndexByUrl(im.src));
14242      });
14243    }
14244
14245    // (Opsional) double-click pada thumbnail untuk buka langsung
14246    const thumbs = document.querySelectorAll('.pd-thumb-btn');
14247    thumbs.forEach((btn)=>{
14248      let last = 0;
14249      btn.addEventListener('click', ()=>{
14250        const now = Date.now();
14251        if (now - last < 300) {
14252          const src = btn.getAttribute('data-full') || '';
14253          open(findIndexByUrl(absUrl(src)));
14254          last = 0;
14255        } else {
14256          last = now;
14257        }
14258      });
14259    });
14260
14261    // Ekspos API
14262    window.PDLightbox = { open, close, next, prev, findIndexByUrl };
14263  }
14264
14265  if (document.readyState === 'loading') {
14266    document.addEventListener('DOMContentLoaded', initPdLightbox);
14267  } else {
14268    initPdLightbox();
14269  }
14270})();
14271</script>
vendor: 1 bytes, line 14271
14271
14272<script data-inlined="cart.js">
14273(function(){
14274  function $(s,ctx=document){ return ctx.querySelector(s); }
14275  function esc(s){ return String(s||'').replace(/[&<>"']/g, m=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;' }[m])); }
14276  
14277  // CSRF token helper
14278  function getCsrfToken() {
14279    const metaTag = document.querySelector('meta[name="csrf-token"]');
14280    return metaTag ? metaTag.getAttribute('content') : '';
14281  }
14282  
14283  function guessPdMeta(){
14284    const name = (window.PD_NAME !== undefined) ? String(window.PD_NAME) : ($('#pd-title')?.textContent || '');
14285    const sku  = (function(){ const el=$('#pd-sku'); return el ? el.textContent.trim() : (window.PD_SKU||''); })();
14286    // SECURITY FIX: Remove price from client-side metadata (will be validated server-side)
14287    const imgEl = $('#pd-main-img');
14288    const image_url = imgEl ? (imgEl.getAttribute('src') || '') : '';
14289    return { name, sku, image_url }; // price removed for security
14290  }
14291
14292  async function addToCart(productId, qty=1, variantId=0, metaOpt){
14293    const meta = Object.assign(guessPdMeta(), metaOpt||{});
14294    const payload = {
14295      product_id: parseInt(productId||0,10) || 0,
14296      qty: parseInt(qty||1,10) || 1,
14297      variant_id: parseInt(variantId||0,10) || 0,
14298      meta
14299    };
14300    
14301    // Check if user is logged in
14302    const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true' ||
14303                      document.body.classList.contains('logged-in') ||
14304                      window.userLoggedIn === true ||
14305                      document.body.dataset.userLoggedIn === 'true';
14306    
14307    // Skip localStorage untuk logged user - mereka pakai database
14308    if (!isLoggedIn && window.guestCart) {
14309      try {
14310        window.guestCart.add(payload.product_id, payload.qty, payload.variant_id, meta);
14311      } catch (e) {
14312        console.warn('Failed to add to localStorage cart:', e);
14313      }
14314    }
14315    
14316    const res = await fetch('/api/cart/add.php', {
14317      method: 'POST',
14318      headers: { 
14319        'Content-Type':'application/json',
14320        'X-CSRF-Token': getCsrfToken()
14321      },
14322      credentials: 'same-origin',
14323      body: JSON.stringify({
14324        ...payload,
14325        csrf_token: getCsrfToken()
14326      }),
14327    });
14328    const data = await res.json().catch(()=>({ ok:false, error:'Gagal menambahkan ke keranjang' }));
14329    if (!res.ok || !data.ok) {
14330      const msg = data && data.error ? data.error : 'Gagal menambahkan ke keranjang';
14331      throw new Error(msg);
14332    }
14333
14334    // Update badge di navbar dengan data dari response
14335    try {
14336      const el = document.querySelector('[data-role="cart-badge"]') || document.getElementById('cartCount');
14337      if (el && data.cart && typeof data.cart.items_count === 'number') {
14338        el.textContent = String(data.cart.items_count);
14339        el.classList.toggle('is-empty', data.cart.items_count <= 0);
14340      }
14341    } catch(_){}
14342
14343    // Siarkan event global (dipakai cart-badge.js dan halaman lain)
14344    try {
14345      document.dispatchEvent(new CustomEvent('cart:updated', { detail: data.cart || {} }));
14346    } catch(_){}
14347
14348    // Update CartBadge dengan data dari response (lebih reliable)
14349    try { 
14350      if (window.CartBadge && window.CartBadge.set) {
14351        window.CartBadge.set(data.cart.items_count||0);
14352      }
14353    } catch(_){}
14354
14355    // Tambahan: refresh cart counter setelah delay kecil untuk memastikan data tersinkronisasi
14356    setTimeout(() => {
14357      if (window.updateCartCounter) {
14358        window.updateCartCounter();
14359      }
14360    }, 100);
14361
14362    return data;
14363  }
14364
14365  // Function to update cart counter from server
14366  async function updateCartCounter(){
14367    try {
14368      const res = await fetch('/api/cart/get.php', { credentials:'same-origin' });
14369      const data = await res.json().catch(()=>({ok:false}));
14370      if (data.ok && typeof data.items_count === 'number') {
14371        const el = document.querySelector('[data-role="cart-badge"]') || document.getElementById('cartCount');
14372        if (el) {
14373          el.textContent = String(data.items_count);
14374          el.classList.toggle('is-empty', data.items_count <= 0);
14375        }
14376        // Dispatch event
14377        try {
14378          document.dispatchEvent(new CustomEvent('cart:updated', { detail: { items_count: data.items_count } }));
14379        } catch(_){}
14380      }
14381    } catch (err) {
14382      console.log('Could not update cart counter:', err);
14383    }
14384  }
14385
14386  // Function to manually merge guest cart (if needed)
14387  async function mergeGuestCart(){
14388    try {
14389      const res = await fetch('/api/cart/merge-guest.php', {
14390        method: 'POST',
14391        headers: { 
14392          'Content-Type': 'application/json',
14393          'X-CSRF-Token': getCsrfToken()
14394        },
14395        credentials: 'same-origin',
14396        body: JSON.stringify({ csrf_token: getCsrfToken() })
14397      });
14398      const data = await res.json().catch(()=>({ok:false}));
14399      
14400      if (data.ok && data.merged_items > 0) {
14401        console.log(`Guest cart merged: ${data.merged_items} items`);
14402        // Update cart counter after merge
14403        await updateCartCounter();
14404        return data;
14405      }
14406      return data;
14407    } catch (err) {
14408      console.log('Could not merge guest cart:', err);
14409      return { ok: false, error: err.message };
14410    }
14411  }
14412
14413  window.addToCart = addToCart;
14414  window.updateCartCounter = updateCartCounter;
14415  window.mergeGuestCart = mergeGuestCart;
14416
14417  // Optional: auto-bind untuk tombol apapun yang punya data-addtocart
14418  document.addEventListener('click', async (e)=>{
14419    const btn = e.target.closest('[data-addtocart]');
14420    if (!btn) return;
14421    e.preventDefault();
14422    if (btn.disabled) return;
14423
14424    const pid = parseInt(btn.getAttribute('data-pid')||'0',10);
14425    const vid = parseInt(btn.getAttribute('data-vid')||'0',10);
14426    const qty = parseInt(btn.getAttribute('data-qty')||'1',10) || 1;
14427
14428    try {
14429      const data = await addToCart(pid, qty, vid);
14430      if (window.showToast) {
14431        const meta = guessPdMeta();
14432        window.showToast({
14433          type:'success',
14434          title:'Berhasil ditambahkan',
14435          message:`<strong>${esc(meta.name)}</strong>${meta.sku?` · <em>SKU: ${esc(meta.sku)}</em>`:''}<br>Qty: ${qty}`,
14436          duration: 3800,
14437          actions: [
14438            { label:'Lihat Keranjang', onClick: ()=> { window.location.href='/keranjang'; } },
14439            { label:'Lanjut Belanja', variant:'alt', onClick: ()=>{} }
14440          ]
14441        });
14442      }
14443    } catch (err) {
14444      if (window.showToast) {
14445        window.showToast({ type:'warn', title:'Gagal', message: String(err?.message||'Gagal menambahkan'), duration: 4200 });
14446      } else {
14447        console.error(err);
14448      }
14449    }
14450  });
14451})();
14452</script>
vendor: 1 bytes, line 14452
14452
14453<script data-inlined="stock-validator-vanilla.js">
14454/**
14455 * Stock Validation - Vanilla JS Version
14456 * 
14457 * This script provides client-side stock validation without jQuery dependency
14458 * - Works with native JavaScript and fetch API
14459 * - Graceful fallback for older browsers
14460 * - Same functionality as jQuery version
14461 */
14462
14463window.StockValidator = (function() {
14464    'use strict';
14465
14466    const config = {
14467        apiEndpoint: '/api/stock/check.php',
14468        modalSelector: '#stock-modal-overlay',  // We create our own modal
14469        loadingClass: 'loading-stock',
14470        disabledClass: 'stock-disabled',
14471        debug: true  // Enable debug logging
14472    };
14473
14474    /**
14475     * Cross-browser event listener helper
14476     */
14477    function addEventListener(element, event, handler) {
14478        if (element.addEventListener) {
14479            element.addEventListener(event, handler);
14480        } else if (element.attachEvent) {
14481            element.attachEvent('on' + event, handler);
14482        }
14483    }
14484
14485    /**
14486     * Get stock information from embedded variant data (instant, no AJAX needed)
14487     */
14488    function getStockFromEmbeddedData(variantId) {
14489        // Check if embedded variant data exists
14490        if (!window.__PD_VARIANTS_WITH_BRANCH__ || !Array.isArray(window.__PD_VARIANTS_WITH_BRANCH__)) {
14491            return null;
14492        }
14493        
14494        const variants = window.__PD_VARIANTS_WITH_BRANCH__;
14495        
14496        // SPECIAL CASE: Simple product (no variants) - empty array means simple product
14497        if (variants.length === 0) {
14498            // For simple products, we cannot determine stock from embedded data
14499            // Return null to force API validation
14500            return null;
14501        }
14502        
14503        // If no variant specified, check base product stock (all variants)
14504        if (!variantId || variantId <= 0) {
14505            // For base product, check if ANY variant has stock
14506            const isPreorder = window.isPreorderProduct || false;
14507            let hasAnyStock = false;
14508            let totalStock = 0;
14509            
14510            if (isPreorder) {
14511                // For pre-order, check preorder_remaining
14512                hasAnyStock = variants.some(v => {
14513                    if ('preorder_remaining' in v && v.preorder_remaining !== null) {
14514                        return v.preorder_remaining > 0;
14515                    }
14516                    return v.stock > 0;
14517                });
14518                totalStock = variants.reduce((sum, v) => {
14519                    if ('preorder_remaining' in v && v.preorder_remaining !== null) {
14520                        return sum + (v.preorder_remaining || 0);
14521                    }
14522                    return sum + (v.stock || 0);
14523                }, 0);
14524            } else {
14525                hasAnyStock = variants.some(v => v.stock > 0);
14526                totalStock = variants.reduce((sum, v) => sum + (v.stock || 0), 0);
14527            }
14528            
14529            return {
14530                hasStock: hasAnyStock,
14531                stock: totalStock,
14532                sku: 'Base Product',
14533                variant: null
14534            };
14535        }
14536        
14537        // Find specific variant
14538        const variant = variants.find(v => v.id === variantId);
14539        
14540        if (!variant) {
14541            return {
14542                hasStock: false,
14543                stock: 0,
14544                sku: 'Unknown Variant',
14545                variant: null
14546            };
14547        }
14548        
14549        // For pre-order products, check preorder_remaining instead of stock
14550        const isPreorder = window.isPreorderProduct || false;
14551        let hasStock = false;
14552        let availableStock = 0;
14553        
14554        if (isPreorder && 'preorder_remaining' in variant && variant.preorder_remaining !== null) {
14555            availableStock = parseInt(variant.preorder_remaining, 10);
14556            if (isNaN(availableStock)) availableStock = 0;
14557            hasStock = availableStock > 0;
14558        } else {
14559            availableStock = variant.stock || 0;
14560            hasStock = availableStock > 0;
14561        }
14562        
14563        return {
14564            hasStock: hasStock,
14565            stock: availableStock,
14566            sku: variant.sku,
14567            variant: variant
14568        };
14569    }
14570
14571    /**
14572     * Cross-browser AJAX helper
14573     */
14574    function makeRequest(url, options) {
14575        return new Promise((resolve, reject) => {
14576            // Try fetch first (modern browsers)
14577            if (typeof fetch !== 'undefined') {
14578                const fetchOptions = {
14579                    method: options.method || 'POST',
14580                    headers: {
14581                        'Content-Type': 'application/x-www-form-urlencoded',
14582                    },
14583                    body: options.data ? new URLSearchParams(options.data).toString() : null
14584                };
14585
14586                fetch(url, fetchOptions)
14587                    .then(response => {
14588                        if (!response.ok) {
14589                            throw new Error(`HTTP ${response.status}`);
14590                        }
14591                        return response.json();
14592                    })
14593                    .then(resolve)
14594                    .catch(reject);
14595            } else {
14596                // Fallback to XMLHttpRequest
14597                const xhr = new XMLHttpRequest();
14598                xhr.open(options.method || 'POST', url);
14599                xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
14600                
14601                xhr.onload = function() {
14602                    if (xhr.status >= 200 && xhr.status < 300) {
14603                        try {
14604                            resolve(JSON.parse(xhr.responseText));
14605                        } catch (e) {
14606                            reject(new Error('Invalid JSON response'));
14607                        }
14608                    } else {
14609                        reject(new Error(`HTTP ${xhr.status}`));
14610                    }
14611                };
14612                
14613                xhr.onerror = function() {
14614                    reject(new Error('Network error'));
14615                };
14616                
14617                const data = options.data ? new URLSearchParams(options.data).toString() : null;
14618                xhr.send(data);
14619            }
14620        });
14621    }
14622
14623    /**
14624     * Check stock via AJAX
14625     */
14626    function checkStock(productId, variantId, qty) {
14627        return makeRequest(config.apiEndpoint, {
14628            method: 'POST',
14629            data: {
14630                product_id: productId,
14631                variant_id: variantId || 0,
14632                qty: qty || 1
14633            }
14634        });
14635    }
14636
14637    /**
14638     * Create and show stock alert modal using modal.css structure
14639     */
14640    function showStockModal(stockInfo, action = 'add to cart') {
14641        let title, message, type, iconClass;
14642
14643        // Handle both string and object formats
14644        if (typeof stockInfo === 'string') {
14645            title = 'Stok Tidak Tersedia';
14646            message = stockInfo;
14647            type = 'warning';
14648            iconClass = 'warning';
14649        } else if (stockInfo && typeof stockInfo === 'object') {
14650            if (!stockInfo.is_active) {
14651                title = 'Produk Tidak Tersedia';
14652                message = 'Maaf, produk ini sudah tidak aktif atau telah dihapus dari katalog.';
14653                type = 'danger';
14654                iconClass = 'danger';
14655            } else if (!stockInfo.is_in_stock) {
14656                title = 'Stok Habis';
14657                message = `<strong>${stockInfo.product_name || 'Produk ini'}</strong> sedang habis stok. Silakan pilih varian lain atau tunggu restock.`;
14658                if (stockInfo.sku) {
14659                    message += `<br><small style="color: #64748b;">SKU: ${stockInfo.sku}</small>`;
14660                }
14661                type = 'warning';
14662                iconClass = 'warning';
14663            } else if (!stockInfo.can_add_to_cart) {
14664                title = 'Stok Tidak Mencukupi';
14665                message = `Stok <strong>${stockInfo.product_name || 'produk'}</strong> tidak mencukupi.<br>`;
14666                message += `Tersedia: <strong>${stockInfo.available_stock}</strong>, diminta: <strong>${stockInfo.requested_qty}</strong><br>`;
14667                message += `Silakan kurangi jumlah atau pilih varian lain.`;
14668                type = 'warning';
14669                iconClass = 'warning';
14670            } else {
14671                title = 'Stok Tidak Tersedia';
14672                message = stockInfo.message || 'Produk ini tidak dapat ditambahkan ke keranjang saat ini.';
14673                type = 'warning';
14674                iconClass = 'warning';
14675            }
14676        } else {
14677            title = 'Stok Tidak Tersedia';
14678            message = 'Produk ini tidak dapat ditambahkan ke keranjang saat ini.';
14679            type = 'warning';
14680            iconClass = 'warning';
14681        }
14682
14683        // Create modal HTML using modal.css structure
14684        const modalHTML = `
14685            <div class="modal-overlay" id="stock-modal-overlay">
14686                <div class="modal-dialog">
14687                    <div class="modal-header">
14688                        <div class="modal-icon ${iconClass}">
14689                            ${getIconSVG(iconClass)}
14690                        </div>
14691                        <h3 class="modal-title">${title}</h3>
14692                    </div>
14693                    <div class="modal-body">
14694                        <p class="modal-message">${message}</p>
14695                        <div class="modal-actions">
14696                            <button type="button" class="modal-btn modal-btn-secondary" data-action="close">
14697                                Tutup
14698                            </button>
14699                            <button type="button" class="modal-btn modal-btn-primary" data-action="reload">
14700                                Muat Ulang
14701                            </button>
14702                        </div>
14703                    </div>
14704                </div>
14705            </div>
14706        `;
14707
14708        // Remove existing modal if any
14709        const existingModal = document.getElementById('stock-modal-overlay');
14710        if (existingModal) {
14711            existingModal.remove();
14712        }
14713
14714        // Add modal to body
14715        document.body.insertAdjacentHTML('beforeend', modalHTML);
14716        
14717        // Show modal with animation
14718        const modal = document.getElementById('stock-modal-overlay');
14719        setTimeout(() => {
14720            modal.classList.add('show');
14721        }, 10);
14722
14723        // Add event listeners to modal buttons
14724        const closeBtn = modal.querySelector('[data-action="close"]');
14725        const reloadBtn = modal.querySelector('[data-action="reload"]');
14726        
14727        if (closeBtn) {
14728            addEventListener(closeBtn, 'click', closeStockModal);
14729        }
14730        
14731        if (reloadBtn) {
14732            addEventListener(reloadBtn, 'click', function() {
14733                closeStockModal();
14734                window.location.reload();
14735            });
14736        }
14737
14738        // Close modal when clicking overlay
14739        addEventListener(modal, 'click', function(event) {
14740            if (event.target === modal) {
14741                closeStockModal();
14742            }
14743        });
14744
14745        // Auto-close after 8 seconds
14746        setTimeout(() => {
14747            closeStockModal();
14748        }, 8000);
14749    }
14750
14751    /**
14752     * Get icon SVG based on type
14753     */
14754    function getIconSVG(type) {
14755        const icons = {
14756            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>',
14757            warning: '<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
14757<path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>',
14758            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>',
14759            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>'
14760        };
14761        return icons[type] || icons.info;
14762    }
14763
14764    /**
14765     * Close stock modal
14766     */
14767    function closeStockModal() {
14768        const modal = document.getElementById('stock-modal-overlay');
14769        if (modal) {
14770            modal.classList.remove('show');
14771            setTimeout(() => {
14772                modal.remove();
14773            }, 200);
14774        }
14775    }
14776
14777    // Make closeStockModal globally available
14778    if (typeof window !== 'undefined') {
14779        window.closeStockModal = closeStockModal;
14780    }
14781
14782    /**
14783     * Get element data attribute with fallback
14784     */
14785    function getDataAttribute(element, attr) {
14786        if (element.dataset) {
14787            return element.dataset[attr];
14788        } else {
14789            return element.getAttribute('data-' + attr);
14790        }
14791    }
14792
14793    /**
14794     * Validate stock before cart action
14795     */
14796    async function validateBeforeCart(productId, variantId, qty, action = 'add') {
14797        try {
14798            // SMART VALIDATION: For simple products, check if we already have cached stock info
14799            // If buttons are enabled and cached info shows stock available, skip API validation
14800            if (window._currentStockInfo && window._currentStockInfo.can_add_to_cart) {
14801        
14802                return true;
14803            }
14804            
14805            // For variant products or when cached info is not available, do API validation
14806            const stockInfo = await checkStock(productId, variantId, qty);
14807            
14808            if (!stockInfo.ok) {
14809                showStockModal({ 
14810                    message: stockInfo.error || 'Gagal mengecek stok',
14811                    is_active: false 
14812                }, action);
14813                return false;
14814            }
14815
14816            if (!stockInfo.can_add_to_cart) {
14817                showStockModal(stockInfo, action);
14818                return false;
14819            }
14820
14821            return true; // Stock available, proceed
14822
14823        } catch (error) {
14824            console.error('Stock validation error:', error);
14825            showStockModal({ 
14826                message: 'Terjadi kesalahan saat mengecek stok. Silakan coba lagi.',
14827                is_active: false 
14828            }, action);
14829            return false;
14830        }
14831    }
14832
14833    /**
14834     * Handle button click with stock validation
14835     */
14836    function handleButtonClick(event, action) {
14837        const button = event.target;
14838        
14839        
14840        // FIRST CHECK: Is button marked as stock-blocked? (Anti-tampering)
14841        if (button.hasAttribute('data-stock-blocked')) {
14842            event.preventDefault();
14843            event.stopImmediatePropagation();
14844            
14845            const reason = button.getAttribute('data-stock-reason') || 'Stok tidak tersedia';
14846            showStockModal(reason);
14847            
14848            // Show security warning
14849            if (window.showToast) {
14850                window.showToast({
14851                    type: 'warning',
14852                    title: 'Aksi Tidak Diizinkan',
14853                    message: 'Produk ini sedang habis stok. Silakan pilih varian lain.',
14854                    duration: 3000
14855                });
14856            }
14857            return false;
14858        }
14859        
14860        // SECOND CHECK: Real-time stock validation against cached stock info
14861        if (window._currentStockInfo && !window._currentStockInfo.can_add_to_cart) {
14862            event.preventDefault();
14863            event.stopImmediatePropagation();
14864            
14865            showStockModal(window._currentStockInfo.message || 'Stok tidak tersedia');
14866            
14867            // Re-enforce button disabling
14868            button.disabled = true;
14869            button.style.opacity = '0.5';
14870            button.style.cursor = 'not-allowed';
14871            button.setAttribute('data-stock-blocked', 'true');
14872            
14873            return false;
14874        }
14875        
14876        // Skip if already processing
14877        if (button.classList.contains(config.loadingClass)) {
14878            event.preventDefault();
14879            return false;
14880        }
14881
14882        // Get product data from button attributes or form
14883        let productId = parseInt(getDataAttribute(button, 'productId') || getDataAttribute(button, 'product-id') || 0);
14884        let variantId = parseInt(getDataAttribute(button, 'variantId') || getDataAttribute(button, 'variant-id') || 0);
14885        let qty = parseInt(getDataAttribute(button, 'qty') || 1);
14886
14887        // Try to get from global variables (set by product detail page)
14888        if (productId <= 0 && typeof window.PD_ID !== 'undefined') {
14889            productId = parseInt(window.PD_ID || 0);
14890        }
14891
14892        // Try to get from parent form if not in button
14893        if (productId <= 0) {
14894            const form = button.closest ? button.closest('form') : null;
14895            if (form) {
14896                const pidAttr = form.getAttribute('data-pid');
14897                const pidInput = form.querySelector('[name="product_id"]');
14898                const vidInput = form.querySelector('[name="variant_id"]');
14899                const qtyInput = form.querySelector('[name="qty"]');
14900                
14901                if (pidAttr) productId = parseInt(pidAttr);
14902                if (pidInput) productId = parseInt(pidInput.value || 0);
14903                if (vidInput) variantId = parseInt(vidInput.value || 0);
14904                if (qtyInput) qty = parseInt(qtyInput.value || 1);
14905            }
14906        }
14907
14908        // CRITICAL: Always get current variant from selector (overrides button attributes)
14909        // This ensures we validate against the currently selected variant, not cached button data
14910        const variantSelector = document.getElementById('pd-variant-id');
14911        if (variantSelector) {
14912            const currentVariantId = parseInt(variantSelector.value || 0);
14913            if (currentVariantId > 0) {
14914                variantId = currentVariantId;
14915            }
14916        }
14917
14918
14919        if (productId <= 0) {
14920            return true; // Let default handler deal with it
14921        }
14922
14923        // Prevent default and validate stock
14924        event.preventDefault();
14925        event.stopPropagation();
14926        
14927        // Show loading state
14928        button.classList.add(config.loadingClass);
14929        const originalDisabled = button.disabled;
14930        button.disabled = true;
14931        
14932        
14933        validateBeforeCart(productId, variantId, qty, action)
14934            .then(isValid => {
14935                if (isValid) {
14936                    // Stock OK, proceed with original action
14937                    if (action === 'cart' && typeof window.addToCart === 'function') {
14938                        window.addToCart(productId, qty, variantId);
14939                    } else if (action === 'buy' && typeof window.buyNow === 'function') {
14940                        // Call buyNow from pd-buynow.js without parameters (it gets data internally)
14941                        window.buyNow();
14942                    } else {
14943                        // Add fallback cart/buy logic here if needed
14944                    }
14945                }
14946            })
14947            .catch(error => {
14948            })
14949            .finally(() => {
14950                // Remove loading state
14951                button.classList.remove(config.loadingClass);
14952                button.disabled = originalDisabled;
14953            });
14954
14955        return false;
14956    }
14957
14958    /**
14959     * Handle mobile button click with stock validation
14960     */
14961    function handleMobileButtonClick(event, action) {
14962        const button = event.target;
14963        
14964        
14965        // FIRST CHECK: Is button marked as stock-blocked? (Anti-tampering)
14966        if (button.hasAttribute('data-stock-blocked')) {
14967            event.preventDefault();
14968            event.stopImmediatePropagation();
14969            
14970            const reason = button.getAttribute('data-stock-reason') || 'Stok tidak tersedia';
14971            showStockModal(reason);
14972            
14973            // Show security warning
14974            if (window.showToast) {
14975                window.showToast({
14976                    type: 'warning',
14977                    title: 'Aksi Tidak Diizinkan',
14978                    message: 'Produk ini sedang habis stok. Silakan pilih varian lain.',
14979                    duration: 3000
14980                });
14981            }
14982            return false;
14983        }
14984        
14985        // SECOND CHECK: Real-time stock validation against cached stock info
14986        if (window._currentStockInfo && !window._currentStockInfo.can_add_to_cart) {
14987            event.preventDefault();
14988            event.stopImmediatePropagation();
14989            
14990            showStockModal(window._currentStockInfo.message || 'Stok tidak tersedia');
14991            
14992            // Re-enforce button disabling
14993            button.disabled = true;
14994            button.style.opacity = '0.5';
14995            button.style.cursor = 'not-allowed';
14996            button.setAttribute('data-stock-blocked', 'true');
14997            
14998            return false;
14999        }
15000        
15001        // Skip if already processing
15002        if (button.classList.contains(config.loadingClass)) {
15003            event.preventDefault();
15004            return false;
15005        }
15006
15007        // Get product data from mobile FAB container
15008        let productId = 0;
15009        let variantId = 0;
15010        let qty = 1;
15011
15012        // Try to get from mobile FAB data-pid
15013        const mobileFab = document.getElementById('pd-mobile-fab');
15014        if (mobileFab) {
15015            const pidAttr = getDataAttribute(mobileFab, 'pid');
15016            if (pidAttr) productId = parseInt(pidAttr);
15017        }
15018
15019        // Try to get from global variables (set by product detail page)
15020        if (productId <= 0 && typeof window.PD_ID !== 'undefined') {
15021            productId = parseInt(window.PD_ID || 0);
15022        }
15023
15024        // CRITICAL: Always get current variant from selector (real-time)
15025        // This ensures we validate against the currently selected variant
15026        const variantSelector = document.getElementById('pd-variant-id');
15027        if (variantSelector) {
15028            variantId = parseInt(variantSelector.value || 0);
15029        }
15030
15031        // Try to get quantity from quantity input
15032        const qtySelector = document.querySelector('[name="qty"]');
15033        if (qtySelector) {
15034            qty = parseInt(qtySelector.value || 1);
15035        }
15036        
15037        // Ensure minimum quantity is 1
15038        qty = Math.max(1, qty);
15039
15040
15041        if (productId <= 0) {
15042            return true; // Let default handler deal with it
15043        }
15044
15045        // Event already prevented at interceptor level, just proceed with validation
15046        
15047        // Show loading state
15048        button.classList.add(config.loadingClass);
15049        const originalDisabled = button.disabled;
15050        const originalText = button.textContent;
15051        button.disabled = true;
15052        button.textContent = action === 'cart' ? 'Validasi...' : 'Validasi...';
15053        
15054        
15055        // PREEMPTIVE BLOCKING: Set blocking flags BEFORE validation to prevent race conditions
15056        if (window.addToCart && typeof window.addToCart === 'function') {
15057            window.addToCart._stockValidatorBlocked = true;
15058        }
15059        
15060        // Also preemptively block other cart functions
15061        const originalAddToCartSubmit = window.addToCartSubmit;
15062        const originalPdAddToCartSubmit = window._pdAddToCartSubmit;
15063        
15064        // Temporarily block ALL cart functions during validation
15065        window.addToCartSubmit = (...args) => {
15066            return Promise.resolve(false);
15067        };
15068        
15069        if (originalPdAddToCartSubmit) {
15070            window._pdAddToCartSubmit = (...args) => {
15071                return Promise.resolve(false);
15072            };
15073        }
15074        
15075        validateBeforeCart(productId, variantId, qty, action)
15076            .then(isValid => {
15077                if (isValid) {
15078                    // Stock OK, proceed with original action
15079                    
15080                    if (action === 'cart') {
15081                        // For mobile cart, we need to recreate the original behavior
15082                        // Strategy: Try multiple methods to ensure cart add + toast success
15083                        
15084                        
15085                        let cartAddSuccess = false;
15086                        let errorMessage = '';
15087                        
15088                        // Method 1: Try original addToCart function
15089                        const originalAddToCart = window.addToCart && window.addToCart._original;
15090                        if (originalAddToCart && !cartAddSuccess) {
15091                            try {
15092                                const result = originalAddToCart(productId, qty, variantId);
15093                                
15094                                // Handle both sync and async returns
15095                                if (result && typeof result.then === 'function') {
15096                                    result.then(() => {
15097                                        cartAddSuccess = true;
15098                                    }).catch(err => {
15099                                        errorMessage = err.message || 'addToCart failed';
15100                                    });
15101                                } else {
15102                                    cartAddSuccess = true;
15103                                }
15104                            } catch (error) {
15105                                errorMessage = error.message || 'addToCart exception';
15106                            }
15107                        }
15108                        
15109                        // Method 2: Try pd-addtocart.js function (backup method)
15110                        if (typeof window._pdAddToCartSubmit === 'function' && !cartAddSuccess) {
15111                            try {
15112                                
15113                                // Create fake form for pd-addtocart.js
15114                                const fakeForm = document.createElement('form');
15115                                const qtyInput = document.createElement('input');
15116                                qtyInput.name = 'qty';
15117                                qtyInput.value = qty;
15118                                fakeForm.appendChild(qtyInput);
15119                                
15120                                // Set variant selector value for pd-addtocart to read
15121                                const variantSelector = document.getElementById('pd-variant-id');
15122                                if (variantSelector) {
15123                                    variantSelector.value = variantId;
15124                                }
15125                                
15126                                const fakeEvent = { 
15127                                    preventDefault: () => {},
15128                                    target: fakeForm
15129                                };
15130                                
15131                                // Call pd-addtocart.js function (this should handle toast internally)
15132                                const result = window._pdAddToCartSubmit(fakeEvent, productId);
15133                                if (result && typeof result.then === 'function') {
15134                                    result.then(() => {
15135                                        cartAddSuccess = true;
15136                                    });
15137                                } else {
15138                                    cartAddSuccess = true;
15139                                }
15140                            } catch (error) {
15141                            }
15142                        }
15143                        
15144                        // Method 3: Direct addToCart call (fallback)
15145                        if (typeof window.addToCart === 'function' && !cartAddSuccess) {
15146                            try {
15147                                const result = window.addToCart(productId, qty, variantId);
15148                                if (result && typeof result.then === 'function') {
15149                                    result.then(() => {
15150                                        cartAddSuccess = true;
15151                                    });
15152                                } else {
15153                                    cartAddSuccess = true;
15154                                }
15155                            } catch (error) {
15156                            }
15157                        }
15158                        
15159                        // Always show success toast for mobile (regardless of which method worked)
15160                        // This ensures user gets feedback for successful cart addition
15161                        setTimeout(() => {
15162                            if (window.showToast) {
15163                                
15164                                // Get product details for toast
15165                                const getVariantSummaryText = () => {
15166                                    const vs = document.getElementById('pd-variant-summary');
15167                                    if (!vs) return '';
15168                                    const items = Array.from(vs.querySelectorAll('.vs-item strong'))
15169                                        .map(el => el.textContent.trim())
15170                                        .filter(Boolean);
15171                                    return items.length ? items.join(' / ') : '';
15172                                };
15173                                
15174                                const escapeHtml = (s) => String(s||'').replace(/[&<>"']/g, m=>({
15175                                    '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'
15176                                }[m]));
15177                                
15178                                const variantText = getVariantSummaryText();
15179                                const skuText = (function(){ 
15180                                    const el = document.getElementById('pd-sku'); 
15181                                    return el ? el.textContent.trim() : (window.PD_SKU || 'N/A'); 
15182                                })();
15183                                const productName = window.PD_NAME || 'Product';
15184                                
15185                                window.showToast({
15186                                    type: 'success',
15187                                    title: 'Berhasil ditambahkan ke Keranjang',
15188                                    message: `<strong>${escapeHtml(productName)}</strong>${variantText ? ` · <em>${escapeHtml(variantText)}</em>` : ''}<br>SKU: ${escapeHtml(skuText)}
15188 · Qty: ${qty}`,
15189                                    duration: 5000,
15190                                    actions: [
15191                                        { 
15192                                            label: 'Lihat Keranjang', 
15193                                            onClick: () => { window.location.href = '/keranjang'; }
15194                                        },
15195                                        { 
15196                                            label: 'Lanjut Belanja', 
15197                                            variant: 'alt', 
15198                                            onClick: () => {} 
15199                                        }
15200                                    ]
15201                                });
15202                            } else {
15203                            }
15204                        }, 100); // Small delay to ensure cart operation completes first
15205                        
15206                    } else if (action === 'buy') {
15207                        // For mobile buy, call buyNow
15208                        if (typeof window.buyNow === 'function') {
15209                            window.buyNow();
15210                        }
15211                    }
15212                } else {
15213                    // Stock validation FAILED - absolutely NO cart function calls allowed
15214                    
15215                    // DO NOT CALL ANY CART FUNCTIONS - only show modal (already handled by validateBeforeCart)
15216                }
15217                
15218                // Always restore functions after validation (success or failure)
15219                setTimeout(() => {
15220                    // Remove blocking flag
15221                    if (window.addToCart && typeof window.addToCart === 'function') {
15222                        window.addToCart._stockValidatorBlocked = false;
15223                    }
15224                    
15225                    // Restore other functions
15226                    if (originalAddToCartSubmit && typeof originalAddToCartSubmit === 'function') {
15227                        window.addToCartSubmit = originalAddToCartSubmit;
15228                    }
15229                    if (originalPdAddToCartSubmit && typeof originalPdAddToCartSubmit === 'function') {
15230                        window._pdAddToCartSubmit = originalPdAddToCartSubmit;
15231                    }
15232                    
15233                }, 1000); // Shorter timeout since we want to restore quickly after success
15234            })
15235            .catch(error => {
15236                // On error, also block the action
15237            })
15238            .finally(() => {
15239                // Remove loading state
15240                button.classList.remove(config.loadingClass);
15241                button.disabled = originalDisabled;
15242                button.textContent = originalText;
15243            });
15244
15245        return false;
15246    }
15247
15248    /**
15249     * Intercept cart buttons
15250     */
15251    function interceptCartButtons() {
15252
15253        
15254        // CRITICAL: Remove existing mobile cart handlers from product-detail.php
15255        setTimeout(() => {
15256            const mobileCartBtn = document.getElementById('mf-cart');
15257            if (mobileCartBtn) {
15258                // Clone the button to remove ALL existing event listeners
15259                const newBtn = mobileCartBtn.cloneNode(true);
15260                mobileCartBtn.parentNode.replaceChild(newBtn, mobileCartBtn);
15261
15262                
15263                // Add our own handler to the new button
15264                newBtn.addEventListener('click', function(event) {
15265                    event.preventDefault();
15266                    event.stopPropagation();
15267                    event.stopImmediatePropagation();
15268                    handleMobileButtonClick(event, 'cart');
15269                    return false;
15270                }, true);
15271            }
15272        }, 50);
15273        
15274        // Note: Add to cart buttons are now handled via form submission override (addToCartSubmit)
15275        // This provides better integration with existing pd-addtocart.js toast system
15276
15277        // Buy now buttons - updated selectors (desktop + mobile)
15278        addEventListener(document, 'click', function(event) {
15279            const target = event.target;
15280            
15281            // Desktop buy buttons
15282            if (target.classList.contains('btn-buy') ||           // Product detail buy button
15283                target.classList.contains('btn-buy-now') || 
15284                target.classList.contains('buy-now-btn') || 
15285                target.id === 'btn-buy' ||                        // Specific ID match
15286                getDataAttribute(target, 'action') === 'buy-now' ||
15287                getDataAttribute(target, 'action') === 'buy') {
15288                
15289                handleButtonClick(event, 'buy');
15290            }
15291            
15292            // Mobile buy buttons
15293            else if (target.classList.contains('mf-buy') ||       // Mobile fab buy button
15294                     target.id === 'mf-buy') {                   // Mobile buy button ID
15295                
15296                handleMobileButtonClick(event, 'buy');
15297            }
15298            
15299            // Mobile cart buttons - ENHANCED BLOCKING
15300            else if (target.classList.contains('mf-cart') ||      // Mobile fab cart button
15301                     target.id === 'mf-cart') {                  // Mobile cart button ID
15302                
15303                
15304                // Immediately prevent all event propagation
15305                event.preventDefault();
15306                event.stopPropagation();
15307                event.stopImmediatePropagation();
15308                
15309                handleMobileButtonClick(event, 'cart');
15310                
15311                // Return false to ensure no other handlers run
15312                return false;
15313            }
15314        }, true); // Use capturing phase to ensure we intercept before other handlers
15315        
15316        // Additional protection: Override addToCart function to add mobile button state checking
15317        const originalAddToCart = typeof window.addToCart === 'function' ? window.addToCart : null;
15318        if (originalAddToCart) {
15319            window.addToCart = async function(...args) {
15320                // Check if this call is coming from a mobile button that's currently being validated
15321                const mobileCartBtn = document.getElementById('mf-cart');
15322                if (mobileCartBtn && mobileCartBtn.classList.contains('loading-stock')) {
15323                    return Promise.reject(new Error('Mobile validation in progress - cart action blocked'));
15324                }
15325                
15326                // Check if functions are temporarily blocked (during stock failure)
15327                if (window.addToCart._stockValidatorBlocked) {
15328                    return Promise.reject(new Error('Stock validation failed - cart action blocked'));
15329                }
15330                
15331                // Allow normal calls
15332                return originalAddToCart.apply(this, args);
15333            };
15334            
15335            // Store reference to original for restoration
15336            window.addToCart._original = originalAddToCart;
15337        }
15338    }
15339
15340    /**
15341     * Setup global click interceptor for security
15342     * This catches ALL clicks on cart/buy buttons and validates stock BEFORE any other handler
15343     */
15344    function setupGlobalClickInterceptor() {
15345        // Add capture-phase listener to catch clicks before other handlers
15346        document.addEventListener('click', function(event) {
15347            const target = event.target;
15348            
15349            // Check if this is a cart/buy button
15350            const isCartButton = target.matches('.btn-add-cart, .mf-cart, #mf-cart, #btn-add-cart') ||
15351                                target.closest('.btn-add-cart, .mf-cart, #mf-cart, #btn-add-cart');
15352            const isBuyButton = target.matches('.btn-buy-now, .mf-buy, #mf-buy, #btn-buy') ||
15353                               target.closest('.btn-buy-now, .mf-buy, #mf-buy, #btn-buy');
15354                               
15355            if (!isCartButton && !isBuyButton) {
15356                return; // Not our target, allow normal processing
15357            }
15358            
15359            const button = isCartButton ? 
15360                (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')) :
15361                (target.matches('.btn-buy-now, .mf-buy, #mf-buy, #btn-buy') ? target : target.closest('.btn-buy-now, .mf-buy, #mf-buy, #btn-buy'));
15362            
15363            // IMMEDIATE BLOCK: Prevent any processing first
15364            event.preventDefault();
15365            event.stopImmediatePropagation();
15366            
15367            // SECURITY CHECK 1: Is button marked as stock-blocked?
15368            if (button && button.hasAttribute('data-stock-blocked')) {
15369                
15370                const reason = button.getAttribute('data-stock-reason') || 'Stok tidak tersedia';
15371                showStockModal(reason);
15372                
15373                // Show security alert
15374                if (window.showToast) {
15375                    window.showToast({
15376                        type: 'error',
15377                        title: 'Aksi Diblokir',
15378                        message: 'Produk ini sedang habis stok dan tidak dapat ditambahkan ke keranjang.',
15379                        duration: 4000
15380                    });
15381                }
15382                
15383                return false;
15384            }
15385            
15386            // SECURITY CHECK 2: Real-time validation against cached stock info
15387            if (window._currentStockInfo && !window._currentStockInfo.can_add_to_cart) {
15388                
15389                showStockModal(window._currentStockInfo.message || 'Stok tidak tersedia');
15390                
15391                // Re-enforce button disabling
15392                if (button) {
15393                    button.disabled = true;
15394                    button.style.opacity = '0.5';
15395                    button.style.cursor = 'not-allowed';
15396                    button.setAttribute('data-stock-blocked', 'true');
15397                    button.setAttribute('data-stock-reason', window._currentStockInfo.message || 'Stok tidak tersedia');
15398                }
15399                
15400                return false;
15401            }
15402            
15403            // SECURITY CHECK 3: Smart client-side validation
15404            const productId = window.PD_ID ? parseInt(window.PD_ID) : 0;
15405            const variantSelector = document.getElementById('pd-variant-id');
15406            const variantId = variantSelector ? parseInt(variantSelector.value || 0) : 0;
15407            
15408            if (productId > 0) {
15409                // SMART VALIDATION: If we already have cached stock info showing stock is available,
15410                // and buttons are enabled, proceed immediately without re-validation
15411                if (window._currentStockInfo && window._currentStockInfo.can_add_to_cart && 
15412                    button && !button.disabled && !button.hasAttribute('data-stock-blocked')) {
15413                    
15414                    // Allow the action to proceed immediately with toast
15415                    if (isCartButton) {
15416                        // Trigger cart action with proper toast handling
15417                        if (typeof window._pdAddToCartSubmit === 'function') {
15418                            // Use pd-addtocart.js function for proper toast handling
15419                            const form = document.getElementById('pd-buy-form') || button.closest('form');
15420                            if (form) {
15421                                const fakeEvent = { 
15422                                    target: form,
15423                                    preventDefault: () => {}
15424                                };
15425                                window._pdAddToCartSubmit(fakeEvent, productId);
15426                            }
15427                        } else if (typeof window.addToCartSubmit === 'function') {
15428                            // Fallback to addToCartSubmit
15429                            const form = document.getElementById('pd-buy-form') || button.closest('form');
15430                            if (form) {
15431                                const fakeEvent = { 
15432                                    target: form,
15433                                    preventDefault: () => {}
15434                                };
15435                                window.addToCartSubmit(fakeEvent, productId);
15436                            }
15437                        } else if (typeof window.addToCart === 'function') {
15438                            // Direct addToCart call
15439                            window.addToCart(productId, 1, variantId);
15440                        }
15441                    } else if (isBuyButton) {
15442                        // Trigger buy action
15443                        if (typeof window.buyNow === 'function') {
15444                            window.buyNow();
15445                        }
15446                    }
15447                    
15448                    return false; // Action completed
15449                }
15450                
15451                // Get stock info from embedded variant data (for variant products)
15452                const stockInfo = getStockFromEmbeddedData(variantId);
15453                
15454                if (stockInfo && stockInfo.hasStock) {
15455                    
15456                    // Update global stock cache
15457                    window._currentStockInfo = {
15458                        can_add_to_cart: true,
15459                        is_in_stock: true,
15460                        available_stock: stockInfo.stock,
15461                        message: 'Stok tersedia'
15462                    };
15463                    
15464                    // IMPORTANT: Check if button was explicitly blocked by variant logic (e.g., pre-order exhausted)
15465                    // If data-stock-blocked is set, respect it and don't enable the button
15466                    const isExplicitlyBlocked = button.getAttribute('data-stock-blocked') === 'true';
15467                    
15468                    if (!isExplicitlyBlocked) {
15469                        // Remove stock blocking if it was set and enable button
15470                        button.removeAttribute('data-stock-blocked');
15471                        button.removeAttribute('data-stock-reason');
15472                        button.disabled = false;
15473                        button.style.opacity = '';
15474                        button.style.cursor = '';
15475                        button.classList.remove(config.disabledClass);
15476                    } else {
15477                        // Button is blocked by variant logic, show reason
15478                        const reason = button.getAttribute('data-stock-reason') || 'Tidak tersedia';
15479                        console.log('Button blocked by variant logic:', reason);
15480                        e.preventDefault();
15481                        e.stopPropagation();
15482                        return false;
15483                    }
15484                    
15485                    // Allow the action to proceed immediately with toast
15486                    if (isCartButton) {
15487                        // Trigger cart action with proper toast handling
15488                        if (typeof window._pdAddToCartSubmit === 'function') {
15489                            // Use pd-addtocart.js function for proper toast handling
15490                            const form = document.getElementById('pd-buy-form') || button.closest('form');
15491                            if (form) {
15492                                const fakeEvent = { 
15493                                    target: form,
15494                                    preventDefault: () => {}
15495                                };
15496                                window._pdAddToCartSubmit(fakeEvent, productId);
15497                            }
15498                        } else if (typeof window.addToCartSubmit === 'function') {
15499                            // Fallback to addToCartSubmit
15500                            const form = document.getElementById('pd-buy-form') || button.closest('form');
15501                            if (form) {
15502                                const fakeEvent = { 
15503                                    target: form,
15504                                    preventDefault: () => {}
15505                                };
15506                                window.addToCartSubmit(fakeEvent, productId);
15507                            }
15508                        } else if (typeof window.addToCart === 'function') {
15509                            // Direct addToCart call
15510                            window.addToCart(productId, 1, variantId);
15511                        }
15512                    } else if (isBuyButton) {
15513                        // Trigger buy action
15514                        if (typeof window.buyNow === 'function') {
15515                            window.buyNow();
15516                        }
15517                    }
15518                } else if (stockInfo && !stockInfo.hasStock) {
15519                    
15520                    // Update global stock cache
15521                    window._currentStockInfo = {
15522                        can_add_to_cart: false,
15523                        is_in_stock: false,
15524                        available_stock: stockInfo.stock,
15525                        message: `Stok tidak tersedia${stockInfo.sku ? ` untuk ${stockInfo.sku}` : ''}`
15526                    };
15527                    
15528                    // Force block button
15529                    button.disabled = true;
15530                    button.style.opacity = '0.5';
15531                    button.style.cursor = 'not-allowed';
15532                    button.setAttribute('data-stock-blocked', 'true');
15533                    button.setAttribute('data-stock-reason', window._currentStockInfo.message);
15534                    
15535                    // Show modal with variant info
15536                    const modalMessage = stockInfo ? 
15537                        `Varian yang dipilih (${stockInfo.sku}) sedang habis stok. Silakan pilih varian lain.` :
15538                        'Produk ini sedang habis stok. Silakan pilih varian lain.';
15539                    
15540                    showStockModal(modalMessage);
15541                    
15542                    // Show security warning
15543                    if (window.showToast) {
15544                        window.showToast({
15545                            type: 'warning',
15546                            title: 'Stok Tidak Tersedia',
15547                            message: 'Varian yang dipilih sedang habis stok. Silakan pilih varian lain.',
15548                            duration: 3000
15549                        });
15550                    }
15551                } else {
15552                    // stockInfo is null (simple product) - this means embedded data is not available
15553                    // For simple products, if buttons are enabled, we should proceed (trust the API response from updateStockDisplay)
15554                    if (button && !button.disabled && !button.hasAttribute('data-stock-blocked')) {
15555                        
15556                        // Allow the action to proceed immediately with toast
15557                        if (isCartButton) {
15558                            // Trigger cart action with proper toast handling
15559                            if (typeof window._pdAddToCartSubmit === 'function') {
15560                                // Use pd-addtocart.js function for proper toast handling
15561                                const form = document.getElementById('pd-buy-form') || button.closest('form');
15562                                if (form) {
15563                                    const fakeEvent = { 
15564                                        target: form,
15565                                        preventDefault: () => {}
15566                                    };
15567                                    window._pdAddToCartSubmit(fakeEvent, productId);
15568                                }
15569                            } else if (typeof window.addToCartSubmit === 'function') {
15570                                // Fallback to addToCartSubmit
15571                                const form = document.getElementById('pd-buy-form') || button.closest('form');
15572                                if (form) {
15573                                    const fakeEvent = { 
15574                                        target: form,
15575                                        preventDefault: () => {}
15576                                    };
15577                                    window.addToCartSubmit(fakeEvent, productId);
15578                                }
15579                            } else if (typeof window.addToCart === 'function') {
15580                                // Direct addToCart call
15581                                window.addToCart(productId, 1, variantId);
15582                            }
15583                        } else if (isBuyButton) {
15584                            // Trigger buy action
15585                            if (typeof window.buyNow === 'function') {
15586                                window.buyNow();
15587                            }
15588                        }
15589                    } else {
15590                        // Button is disabled, don't allow action
15591                        showStockModal('Produk ini sedang tidak tersedia.');
15592                    }
15593                }
15594            } else {
15595                // No product ID, block action
15596                showStockModal('Data produk tidak valid.');
15597            }
15598            
15599            return false; // Always block at capture phase
15600            
15601        }, true); // Use capture phase to run BEFORE other listeners
15602        
15603
15604    }
15605
15606    /**
15607     * Initialize button data attributes from current page
15608     */
15609    function initializeButtonAttributes() {
15610        // Get product data from page globals or form
15611        let productId = 0, variantId = 0;
15612        
15613        // Try to get from global variables (set by PHP)
15614        if (window.PD_ID) productId = parseInt(window.PD_ID);
15615        
15616        // Try to get from form
15617        const form = document.getElementById('pd-buy-form');
15618        if (form) {
15619            const pidAttr = form.getAttribute('data-pid');
15620            if (pidAttr) productId = parseInt(pidAttr);
15621            
15622            const varInput = document.getElementById('pd-variant-id');
15623            if (varInput) variantId = parseInt(varInput.value || 0);
15624        }
15625        
15626        // Try to get from mobile FAB
15627        const mobileFab = document.getElementById('pd-mobile-fab');
15628        if (mobileFab && productId <= 0) {
15629            const pidAttr = getDataAttribute(mobileFab, 'pid');
15630            if (pidAttr) productId = parseInt(pidAttr);
15631        }
15632        
15633        // Set attributes on desktop buttons
15634        const desktopButtons = document.querySelectorAll('.btn-add-cart, .btn-buy-now, #btn-add-cart, #btn-buy, #btn-buy-voucher');
15635        desktopButtons.forEach(button => {
15636            if (productId > 0) {
15637                button.setAttribute('data-product-id', productId);
15638                if (variantId > 0) {
15639                    button.setAttribute('data-variant-id', variantId);
15640                }
15641            }
15642        });
15643        
15644        // Set attributes on mobile buttons
15645        const mobileButtons = document.querySelectorAll('.mf-cart, .mf-buy, #mf-cart, #mf-buy');
15646        mobileButtons.forEach(button => {
15647            if (productId > 0) {
15648                button.setAttribute('data-product-id', productId);
15649                if (variantId > 0) {
15650                    button.setAttribute('data-variant-id', variantId);
15651                }
15652            }
15653        });
15654        
15655
15656        
15657        // Initial stock display update on page load
15658        if (productId > 0) {
15659            setTimeout(() => {
15660                updateStockDisplay(productId, variantId);
15661
15662            }, 100);
15663        }
15664    }
15665
15666    /**
15667     * Update button attributes when variant changes
15668     */
15669    function updateButtonAttributesForVariant(variantId) {
15670        const productId = window.PD_ID ? parseInt(window.PD_ID) : 0;
15671        
15672        if (productId <= 0) return;
15673        
15674        // Update desktop buttons
15675        const desktopButtons = document.querySelectorAll('.btn-add-cart, .btn-buy-now, #btn-add-cart, #btn-buy, #btn-buy-voucher');
15676        desktopButtons.forEach(button => {
15677            button.setAttribute('data-product-id', productId);
15678            if (variantId > 0) {
15679                button.setAttribute('data-variant-id', variantId);
15680            } else {
15681                button.removeAttribute('data-variant-id');
15682            }
15683        });
15684        
15685        // Update mobile buttons
15686        const mobileButtons = document.querySelectorAll('.mf-cart, .mf-buy, #mf-cart, #mf-buy');
15687        mobileButtons.forEach(button => {
15688            button.setAttribute('data-product-id', productId);
15689            if (variantId > 0) {
15690                button.setAttribute('data-variant-id', variantId);
15691            } else {
15692                button.removeAttribute('data-variant-id');
15693            }
15694        });
15695        
15696
15697    }
15698
15699    /**
15700     * Setup variant change integration with existing product-detail.php handlers
15701     */
15702    function setupVariantChangeIntegration() {
15703        // Strategy: Hook into existing applyVariant function instead of adding new listener
15704        // This prevents conflicts with existing variant change functionality (gallery, pricing, etc.)
15705        
15706        // Method 1: Hook into existing applyVariant function
15707        setTimeout(() => {
15708            if (typeof window.applyVariant === 'function') {
15709                const originalApplyVariant = window.applyVariant;
15710                window.applyVariant = function(v, focusImage) {
15711                    // Call original function first
15712                    const result = originalApplyVariant.call(this, v, focusImage);
15713                    
15714                // Then add our stock validator logic
15715                const variantId = v ? (v.id || v.ID || 0) : 0;
15716                
15717                // Update button attributes for stock validator
15718                updateButtonAttributesForVariant(variantId);
15719                
15720                // Update stock display using embedded data (instant)
15721                const productId = window.PD_ID ? parseInt(window.PD_ID) : 0;
15722                if (productId > 0) {
15723                    updateStockDisplay(productId, variantId);
15724                }                    return result;
15725                };
15726                
15727
15728            } else {
15729
15730                setupVariantObserver();
15731            }
15732        }, 500);
15733        
15734        // Method 2: Direct observation of hidden input value changes (fallback)
15735        setTimeout(() => {
15736            setupVariantObserver();
15737        }, 1000);
15738    }
15739
15740    /**
15741     * Setup variant observer as fallback when applyVariant hook fail
15741s
15742     */
15743    function setupVariantObserver() {
15744        const variantInput = document.getElementById('pd-variant-id');
15745        if (variantInput) {
15746            let lastVariantId = parseInt(variantInput.value || '0');
15747            
15748            // Use MutationObserver to watch for value changes
15749            if (window.MutationObserver) {
15750                const observer = new MutationObserver((mutations) => {
15751                    mutations.forEach((mutation) => {
15752                        if (mutation.type === 'attributes' && mutation.attributeName === 'value') {
15753                            const newVariantId = parseInt(variantInput.value || '0');
15754                            if (newVariantId !== lastVariantId) {
15755                    
15756                                updateButtonAttributesForVariant(newVariantId);
15757                                
15758                                // Update stock display with embedded data
15759                                const productId = window.PD_ID ? parseInt(window.PD_ID) : 0;
15760                                if (productId > 0) {
15761                                    updateStockDisplay(productId, newVariantId);
15762                                }
15763                                
15764                                lastVariantId = newVariantId;
15765                            }
15766                        }
15767                    });
15768                });
15769                
15770                observer.observe(variantInput, { 
15771                    attributes: true, 
15772                    attributeFilter: ['value'] 
15773                });
15774                
15775
15776            }
15777            
15778            // Additional polling as ultimate fallback
15779            setInterval(() => {
15780                const currentVariantId = parseInt(variantInput.value || '0');
15781                if (currentVariantId !== lastVariantId) {
15782        
15783                    updateButtonAttributesForVariant(currentVariantId);
15784                    
15785                    // Update stock display with embedded data
15786                    const productId = window.PD_ID ? parseInt(window.PD_ID) : 0;
15787                    if (productId > 0) {
15788                        updateStockDisplay(productId, currentVariantId);
15789                    }
15790                    
15791                    lastVariantId = currentVariantId;
15792                }
15793            }, 1000);
15794            
15795
15796        }
15797    }
15798
15799    /**
15800     * Update stock display and enforce stock-based button blocking using embedded data
15801     */
15802    function updateStockDisplay(productId, variantId = 0) {
15803
15804        
15805        // Get stock info from embedded data (instant)
15806        const embeddedStockInfo = getStockFromEmbeddedData(variantId);
15807        
15808        let stockInfo;
15809        if (embeddedStockInfo) {
15810            // Convert embedded data to standard format
15811            stockInfo = {
15812                can_add_to_cart: embeddedStockInfo.hasStock,
15813                is_in_stock: embeddedStockInfo.hasStock,
15814                available_stock: embeddedStockInfo.stock,
15815                message: embeddedStockInfo.hasStock ? 'Stok tersedia' : `Stok habis${embeddedStockInfo.sku ? ` untuk ${embeddedStockInfo.sku}` : ''}`,
15816                product_name: window.PD_NAME || 'Produk',
15817                sku: embeddedStockInfo.sku
15818            };
15819            
15820
15821        } else {
15822            // Fallback to API call if embedded data not available
15823
15824            checkStock(productId, variantId, 1)
15825                .then(apiStockInfo => {
15826                    updateStockDisplayWithInfo(apiStockInfo, productId, variantId);
15827                })
15828                .catch(error => {
15829                    console.error('Failed to update stock display via API:', error);
15830                });
15831            return;
15832        }
15833        
15834        updateStockDisplayWithInfo(stockInfo, productId, variantId);
15835    }
15836
15837    /**
15838     * Update stock display with provided stock info
15839     */
15840    function updateStockDisplayWithInfo(stockInfo, productId, variantId) {
15841        // Store current stock status globally for blocking checks
15842        window._currentStockInfo = stockInfo;
15843        
15844        // Update stock indicators
15845        const stockIndicators = document.querySelectorAll('.stock-indicator');
15846        stockIndicators.forEach(indicator => {
15847            if (stockInfo.is_in_stock) {
15848                indicator.classList.add('in-stock');
15849                indicator.classList.remove('out-of-stock');
15850            } else {
15851                indicator.classList.add('out-of-stock');
15852                indicator.classList.remove('in-stock');
15853            }
15854        });
15855        
15856        // Update stock count
15857        const stockCounts = document.querySelectorAll('.stock-count');
15858        stockCounts.forEach(count => {
15859            count.textContent = stockInfo.available_stock || 0;
15860        });
15861        
15862        // CRITICAL: For digital products, DO NOT modify button states here
15863        // PHP already handles button disable/enable based on LICENSE_POOL count
15864        // Only physical products need JavaScript stock validation
15865        const isDigitalProduct = window.IS_DIGITAL === true || window.IS_DIGITAL === 'true';
15866        
15867        if (isDigitalProduct) {
15868            // For digital products: ONLY update global stock info, don't touch buttons
15869            // Let PHP-rendered disabled state remain authoritative
15870            console.log('[Stock Validator] Digital product detected - skipping button state modification');
15871            return; // Exit early, preserve PHP-set button states
15872        }
15873        
15874        // ENHANCED: Force disable buttons for out-of-stock with anti-tampering (PHYSICAL PRODUCTS ONLY)
15875        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');
15876        allButtons.forEach(button => {
15877            if (stockInfo.can_add_to_cart) {
15878                // STOCK AVAILABLE - ENABLE BUTTONS
15879                button.classList.remove(config.disabledClass);
15880                button.disabled = false;
15881                button.style.opacity = '';
15882                button.style.cursor = '';
15883                
15884                // CRITICAL: Remove stock blocking attributes when stock becomes available
15885                button.removeAttribute('data-stock-blocked');
15886                button.removeAttribute('data-stock-reason');
15887                
15888
15889            } else {
15890                // OUT OF STOCK - FORCE DISABLE WITH ANTI-TAMPERING
15891                button.classList.add(config.disabledClass);
15892                button.disabled = true;
15893                button.style.opacity = '0.5';
15894                button.style.cursor = 'not-allowed';
15895                
15896                // Mark as stock-blocked to prevent re-enabling
15897                button.setAttribute('data-stock-blocked', 'true');
15898                button.setAttribute('data-stock-reason', stockInfo.message || 'Stok tidak tersedia');
15899                
15900
15901            }
15902        });
15903        
15904        // Start aggressive button monitoring for out-of-stock variants
15905        if (!stockInfo.can_add_to_cart) {
15906            enforceStockDisabling(productId, variantId);
15907        }
15908        
15909
15910    }
15911
15912    /**
15913     * Aggressively enforce button disabling for out-of-stock items
15914     * CRITICAL: For digital products, DO NOT modify button states (PHP is authoritative)
15915     */
15916    function enforceStockDisabling(productId, variantId) {
15917        // CRITICAL: Skip enforcement for digital products - PHP handles button states
15918        const isDigitalProduct = window.IS_DIGITAL === true || window.IS_DIGITAL === 'true';
15919        if (isDigitalProduct) {
15920            console.log('[Stock Validator] Digital product - skipping button enforcement');
15921            return; // Exit early, let PHP-rendered disabled state remain
15922        }
15923        
15924        const monitorInterval = setInterval(() => {
15925            // Check if variant changed
15926            const currentVariantSelector = document.getElementById('pd-variant-id');
15927            const currentVariantId = currentVariantSelector ? parseInt(currentVariantSelector.value || 0) : 0;
15928            
15929            if (currentVariantId !== variantId) {
15930                // Variant changed, stop monitoring this variant
15931                clearInterval(monitorInterval);
15932
15933                return;
15934            }
15935            
15936            // Check current stock status using embedded data (real-time check)
15937            const currentStockInfo = getStockFromEmbeddedData(currentVariantId);
15938            
15939            if (currentStockInfo && !currentStockInfo.hasStock) {
15940                // Still out of stock - continue monitoring for tampering
15941                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');
15942                buttons.forEach(button => {
15943                    // Check if button was manually enabled (inspect attack)
15944                    if (!button.disabled || button.style.opacity !== '0.5') {
15945                        
15946                        // Force re-disable
15947                        button.disabled = true;
15948                        button.style.opacity = '0.5';
15949                        button.style.cursor = 'not-allowed';
15950                        button.setAttribute('data-stock-blocked', 'true');
15951                        button.setAttribute('data-stock-reason', `Stok tidak tersedia${currentStockInfo.sku ? ` untuk ${currentStockInfo.sku}` : ''}`);
15952                        
15953                        // Show warning toast
15954                        if (window.showToast) {
15955                            window.showToast({
15956                                type: 'warning',
15957                                title: 'Aksi Tidak Diizinkan',
15958                                message: 'Produk ini sedang habis stok. Silakan pilih varian lain.',
15959                                duration: 3000
15960                            });
15961                        }
15962                    }
15963                });
15964            } else {
15965                // Stock available OR embedded data shows stock - stop monitoring
15966                clearInterval(monitorInterval);
15967
15968                // For PHYSICAL products only, enable buttons
15969                // For DIGITAL products, skip (PHP is authoritative)
15970                const isDigitalProduct = window.IS_DIGITAL === true || window.IS_DIGITAL === 'true';
15971                
15972                if (!isDigitalProduct && currentStockInfo && currentStockInfo.hasStock) {
15973                    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');
15974                    buttons.forEach(button => {
15975                        button.disabled = false;
15976                        button.style.opacity = '';
15977                        button.style.cursor = '';
15978                        button.removeAttribute('data-stock-blocked');
15979                        button.removeAttribute('data-stock-reason');
15980                        button.classList.remove(config.disabledClass);
15981                    });
15982
15983                }
15984            }
15985        }, 500); // Check every 500ms for tampering attempts
15986        
15987        // Auto-stop monitoring after 30 seconds to prevent memory leaks
15988        setTimeout(() => {
15989            clearInterval(monitorInterval);
15990
15991        }, 30000);
15992    }
15993
15994    /**
15995     * Override addToCartSubmit function to add stock validation
15996     * This function now works with pd-addtocart.js integration
15997     */
15998    function overrideAddToCartSubmitFunction() {
15999        // Check if pd-addtocart.js is already loaded
16000        const pdAddToCartExists = typeof window.addToCartSubmit === 'function' && 
16001                                  window.addToCartSubmit.toString().includes('showToast');
16002        
16003        if (pdAddToCartExists) {
16004
16005            
16006            // Store the pd-addtocart.js function
16007            const pdAddToCartSubmit = window.addToCartSubmit;
16008            window._pdAddToCartSubmit = pdAddToCartSubmit;
16009            
16010            // Override with stock validation that calls pd-addtocart.js
16011            window.addToCartSubmit = async function(e, productId) {
16012                
16013                // Don't prevent default here - let pd-addtocart handle it if stock is valid
16014                
16015                try {
16016                    // Extract product data from form (same way as pd-addtocart.js)
16017                    const form = e.target;
16018                    const qty = parseInt(form.querySelector('[name=qty]')?.value || '1', 10) || 1;
16019                    
16020                    // CRITICAL: Always get current variant from selector (real-time)
16021                    let variantId = 0;
16022                    const variantSelector = document.getElementById('pd-variant-id');
16023                    if (variantSelector) {
16024                        variantId = parseInt(variantSelector.value || '0', 10) || 0;
16025                    }
16026                    
16027                    if (!productId || productId <= 0) {
16028                        return pdAddToCartSubmit(e, productId);
16029                    }
16030
16031
16032                    // Validate stock before proceeding
16033                    const isValid = await validateBeforeCart(productId, variantId, qty, 'cart');
16034                    
16035                    if (isValid) {
16036                        // Stock OK, call pd-addtocart.js function (it will handle toast)
16037                        return pdAddToCartSubmit(e, productId);
16038                    } else {
16039                        // Prevent the form submission
16040                        e.preventDefault();
16041                        // Stock validation failed, modal already shown by validateBeforeCart
16042                        return false;
16043                    }
16044                    
16045                } catch (error) {
16046                    e.preventDefault();
16047                    // On error, show modal and don't proceed
16048                    showStockModal({ 
16049                        message: 'Terjadi kesalahan saat memvalidasi stok. Silakan coba lagi.',
16050                        is_active: false 
16051                    }, 'cart');
16052                    return false;
16053                }
16054            };
16055        } else {
16056            // Original logic for when pd-addtocart.js is not present
16057            const originalAddToCartSubmit = typeof window.addToCartSubmit === 'function' ? window.addToCartSubmit : null;
16058            
16059            if (originalAddToCartSubmit) {
16060                window._originalAddToCartSubmit = originalAddToCartSubmit;
16061            }
16062            
16063            // Override with stock validation
16064            window.addToCartSubmit = async function(e, productId) {
16065                
16066                e.preventDefault();
16067                
16068                try {
16069                    const form = e.target;
16070                    const qty = parseInt(form.querySelector('[name=qty]')?.value || '1', 10) || 1;
16071                    
16072                    // CRITICAL: Always get current variant from selector (real-time)
16073                    let variantId = 0;
16074                    const variantSelector = document.getElementById('pd-variant-id');
16075                    if (variantSelector) {
16076                        variantId = parseInt(variantSelector.value || '0', 10) || 0;
16077                    }
16078                    
16079                    if (!productId || productId <= 0) {
16080                        if (originalAddToCartSubmit) return originalAddToCartSubmit(e, productId);
16081                        return false;
16082                    }
16083
16084
16085                    const isValid = await validateBeforeCart(productId, variantId, qty, 'cart');
16086                    
16087                    if (isValid) {
16088                        if (originalAddToCartSubmit) {
16089                            return originalAddToCartSubmit(e, productId);
16090                        } else {
16091                            if (typeof window.addToCart === 'function') {
16092                                await window.addToCart(productId, qty, variantId);
16093                            }
16094                        }
16095                    } else {
16096                    }
16097                    
16098                } catch (error) {
16099                    showStockModal({ 
16100                        message: 'Terjadi kesalahan saat memvalidasi stok. Silakan coba lagi.',
16101                        is_active: false 
16102                    }, 'cart');
16103                }
16104                
16105                return false;
16106            };
16107        }
16108
16109
16110    }
16111
16112    /**
16113     * Override buyNow function to add stock validation
16114     */
16115    function overrideBuyNowFunction() {
16116        // Store original buyNow function if it exists
16117        const originalBuyNow = typeof window.buyNow === 'function' ? window.buyNow : null;
16118        
16119        // Override with stock validation
16120        window.buyNow = async function(e) {
16121            
16122            try {
16123                // Get product data (same way as pd-buynow.js)
16124                const form = document.getElementById('pd-buy-form');
16125                if (!form) {
16126                    if (originalBuyNow) return originalBuyNow(e);
16127                    return;
16128                }
16129
16130                const productId = parseInt(form.dataset.pid || '0', 10);
16131                const qty = parseInt(form.querySelector('[name=qty]')?.value || '1', 10) || 1;
16132                
16133                // CRITICAL: Always get current variant from selector (real-time)
16134                let variantId = 0;
16135                const variantSelector = document.getElementById('pd-variant-id');
16136                if (variantSelector) {
16137                    variantId = parseInt(variantSelector.value || '0', 10) || 0;
16138                }
16139                
16140                if (!productId || productId <= 0) {
16141                    if (originalBuyNow) return originalBuyNow(e);
16142                    return;
16143                }
16144
16145
16146                // Validate stock before proceeding
16147                const isValid = await validateBeforeCart(productId, variantId, qty, 'buy');
16148                
16149                if (isValid) {
16150                    // Stock OK, call original buyNow function
16151                    if (originalBuyNow) {
16152                        return originalBuyNow(e);
16153                    } else {
16154                    }
16155                } else {
16156                    // Stock validation failed, modal already shown by validateBeforeCart
16157                }
16158                
16159            } catch (error) {
16160                // On error, show modal and don't proceed
16161                showStockModal({ 
16162                    message: 'Terjadi kesalahan saat memvalidasi stok. Silakan coba lagi.',
16163                    is_active: false 
16164                }, 'buy');
16165            }
16166        };
16167
16168
16169    }
16170
16171    /**
16172     * Initialize stock validation
16173     */
16174    function init() {
16175        // Wait for DOM ready
16176        if (document.readyState === 'loading') {
16177            addEventListener(document, 'DOMContentLoaded', function() {
16178        initializeButtonAttributes();
16179        setupVariantChangeIntegration(); // Setup variant change integration
16180        interceptCartButtons();
16181        setupGlobalClickInterceptor(); // Setup global click security interceptor                // Override functions after other scripts load
16182                // Use multiple timeouts to catch late-loading scripts like pd-addtocart.js
16183                setTimeout(() => {
16184                    overrideAddToCartSubmitFunction();
16185                    overrideBuyNowFunction();
16186                }, 100);
16187                
16188                setTimeout(() => {
16189                    // Re-check and re-override if pd-addtocart.js loaded later
16190                    overrideAddToCartSubmitFunction();
16191                }, 500);
16192                
16193                setTimeout(() => {
16194                    // Final check for very late loading scripts
16195                    overrideAddToCartSubmitFunction();
16196                }, 1000);
16197                
16198
16199            });
16200        } else {
16201            initializeButtonAttributes();
16202            setupVariantChangeIntegration(); // Setup variant change integration
16203            interceptCartButtons();
16204            setupGlobalClickInterceptor(); // Setup global click security interceptor
16205            
16206            // Override functions after other scripts load
16207            // Use multiple timeouts to catch late-loading scripts like pd-addtocart.js
16208            setTimeout(() => {
16209                overrideAddToCartSubmitFunction();
16210                overrideBuyNowFunction();
16211            }, 100);
16212            
16213            setTimeout(() => {
16214                // Re-check and re-override if pd-addtocart.js loaded later
16215                overrideAddToCartSubmitFunction();
16216            }, 500);
16217            
16218            setTimeout(() => {
16219                // Final check for very late loading scripts
16220                overrideAddToCartSubmitFunction();
16221            }, 1000);
16222            
16223
16224        }
16225    }
16226
16227    // Public API
16228    return {
16229        init: init,
16230        checkStock: checkStock,
16231        validateBeforeCart: validateBeforeCart,
16232        updateStockDisplay: updateStockDisplay,
16233        showStockModal: showStockModal,
16234        config: config
16235    };
16236})();
16237
16238// Auto-initialize
16239if (typeof window !== 'undefined') {
16240    StockValidator.init();
16241}
16242
16243</script>
vendor: 1 bytes, line 16243
16243
16244<script data-inlined="pd-addtocart.js">
16245(function(){
16246  function esc(s){ return String(s||'').replace(/[&<>"']/g, m=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;' }[m])); }
16247  function getVariantSummaryText(){
16248    const vs = document.getElementById('pd-variant-summary');
16249    if (!vs) return '';
16250    const items = Array.from(vs.querySelectorAll('.vs-item strong')).map(el => el.textContent.trim()).filter(Boolean);
16251    return items.length ? items.join(' / ') : '';
16252  }
16253  window.addToCartSubmit = async function(e, productId){
16254    e.preventDefault();
16255    try {
16256      const form = e.target;
16257      const qty = parseInt(form.querySelector('[name=qty]')?.value || '1', 10) || 1;
16258      const variantId = parseInt(document.getElementById('pd-variant-id')?.value || '0', 10) || 0;
16259      await (window.addToCart ? window.addToCart(productId, qty, variantId) : Promise.reject(new Error('addToCart tidak tersedia')));
16260
16261      if (window.PlazaAnalytics) {
16262        window.PlazaAnalytics.track('add_to_cart', { slug: String(window._pit_slug || '') });
16263      }
16264
16265      const name = (window.PD_NAME !== undefined) ? String(window.PD_NAME) : (document.getElementById('pd-title')?.textContent || '');
16266      const sku  = (function(){ const el=document.getElementById('pd-sku'); return el?el.textContent.trim(): (window.PD_SKU||''); })();
16267      const variantText = getVariantSummaryText();
16268
16269      if (window.showToast) {
16270        window.showToast({
16271          type: 'success',
16272          title: 'Berhasil ditambahkan ke Keranjang',
16273          message: `<strong>${esc(name)}</strong>${variantText?` · <em>${esc(variantText)}</em>`:''}<br>SKU: ${esc(sku)} · Qty: ${qty}`,
16274          duration: 5000,
16275          actions: [
16276            { label: 'Lihat Keranjang', onClick: ()=> { window.location.href = '/keranjang'; } },
16277            { label: 'Lanjut Belanja', variant: 'alt', onClick: ()=>{} }
16278          ]
16279        });
16280      }
16281    } catch (err) {
16282      if (window.showToast) {
16283        window.showToast({ type:'warn', title:'Gagal menambahkan', message: String(err?.message||'Terjadi kesalahan'), duration: 4500 });
16284      } else {
16285        console.error(err);
16286      }
16287    }
16288    return false;
16289  };
16290})();
16291</script>
vendor: 1 bytes, line 16291
16291
16292<script data-inlined="pd-buynow.js">
16293(function(){
16294  
16295  // CSRF Token Helper
16296  function getCsrfToken() {
16297    return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
16298  }
16299  
16300  // Utility functions
16301  function esc(s){ return String(s||'').replace(/[&<>"']/g, m=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;' }[m])); }
16302  
16303  function getVariantSummaryText(){
16304    const vs = document.getElementById('pd-variant-summary');
16305    if (!vs) return '';
16306    const items = Array.from(vs.querySelectorAll('.vs-item strong')).map(el => el.textContent.trim()).filter(Boolean);
16307    return items.length ? items.join(' / ') : '';
16308  }
16309
16310  function getCurrentProductData(){
16311    const form = document.getElementById('pd-buy-form');
16312    if (!form) return null;
16313
16314    const productId = parseInt(form.dataset.pid || '0', 10);
16315    const qty = parseInt(form.querySelector('[name=qty]')?.value || '1', 10) || 1;
16316    const variantId = parseInt(document.getElementById('pd-variant-id')?.value || '0', 10) || 0;
16317    
16318    if (!productId || productId <= 0) {
16319      throw new Error('Product ID tidak valid');
16320    }
16321
16322    if (qty <= 0) {
16323      throw new Error('Quantity harus lebih dari 0');
16324    }
16325
16326    return { productId, qty, variantId };
16327  }
16328
16329  function createBuyNowPayload(productId, qty, variantId) {
16330    // Get product metadata
16331    const name = (window.PD_NAME !== undefined) ? String(window.PD_NAME) : (document.getElementById('pd-title')?.textContent || '');
16332    
16333    // Get correct price, SKU, and weight from selected variant data instead of DOM elements
16334    let price = 0;
16335    let sku = '';
16336    let weight = 0;
16337    let dimensions = null;
16338    
16339    if (variantId > 0 && typeof window.variants !== 'undefined' && Array.isArray(window.variants)) {
16340      // Find the selected variant in the variants array
16341      const selectedVariant = window.variants.find(v => v.id === variantId);
16342      if (selectedVariant) {
16343        price = selectedVariant.final || 0;
16344        sku = selectedVariant.sku || '';
16345        weight = selectedVariant.weight || 0;
16346        
16347        // Include dimensions if available
16348        if (selectedVariant.length_cm || selectedVariant.width_cm || selectedVariant.height_cm) {
16349          dimensions = {
16350            length_cm: selectedVariant.length_cm || 0,
16351            width_cm: selectedVariant.width_cm || 0,
16352            height_cm: selectedVariant.height_cm || 0
16353          };
16354        }
16355        
16356        // Debug info to verify fix is working
16357        if (window.console && window.console.debug) {
16358          console.debug('BuyNow: Using variant data', {
16359            variantId: variantId,
16360            price: price,
16361            sku: sku,
16362            weight: weight,
16363            dimensions: dimensions,
16364            variantData: selectedVariant
16365          });
16366        }
16367      }
16368    }
16369    
16370    // Fallback to DOM elements if variant data not found
16371    if (price === 0) {
16372      const priceTxt = document.getElementById('pd-price-current')?.textContent || '';
16373      price = parseInt(String(priceTxt).replace(/[^\d]/g,''), 10) || 0;
16374      
16375      if (window.console && window.console.debug) {
16376        console.debug('BuyNow: Using DOM price fallback', { 
16377          priceTxt: priceTxt, 
16378          price: price,
16379          variantId: variantId,
16380          reason: variantId > 0 ? 'variant_data_not_found' : 'no_variant_selected'
16381        });
16382      }
16383    }
16384    
16385    if (!sku) {
16386      const skuEl = document.getElementById('pd-sku');
16387      sku = skuEl ? skuEl.textContent.trim() : (window.PD_SKU || '');
16388    }
16389    
16390    const imgEl = document.getElementById('pd-main-img');
16391    const image_url = imgEl ? (imgEl.getAttribute('src') || '') : '';
16392
16393    const variantText = getVariantSummaryText();
16394    
16395    // Get pre-order data if product is pre-order
16396    const preorderData = window.PD_PREORDER_DATA || null;
16397    
16398    return {
16399      product_id: productId,
16400      qty: qty,
16401      variant_id: variantId,
16402      meta: {
16403        name,
16404        sku,
16405        price,
16406        image_url,
16407        variant_text: variantText,
16408        weight: weight,
16409        dimensions: dimensions
16410      },
16411      is_preorder: preorderData ? 'Y' : 'N',
16412      preorder_estimated_arrival: preorderData ? preorderData.estimated_arrival : null,
16413      preorder_note: preorderData ? preorderData.note : ''
16414    };
16415  }
16416
16417  /**
16418   * Main Buy Now function
16419   * Performs validation and redirects to checkout with buy_now mode
16420   */
16421  window.buyNow = async function(e) {
16422    if (e) e.preventDefault();
16423    
16424    try {
16425      // Check user login status
16426      const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true' ||
16427                        document.body.classList.contains('logged-in') ||
16428                        window.userLoggedIn === true ||
16429                        document.body.dataset.userLoggedIn === 'true';
16430      
16431      if (!isLoggedIn) {
16432        // Redirect to login with return URL to current page
16433        const currentUrl = encodeURIComponent(window.location.pathname + window.location.search);
16434        window.location.href = '/login?return=' + currentUrl;
16435        return;
16436      }
16437
16438      // Get current product data
16439      const { productId, qty, variantId } = getCurrentProductData();
16440      
16441      // Create payload for buy now
16442      const payload = createBuyNowPayload(productId, qty, variantId);
16443      
16444      // Store buy now data in sessionStorage for checkout page
16445      sessionStorage.setItem('buyNowData', JSON.stringify(payload));
16446
16447      // Track add_to_cart (buy now counts as purchase intent)
16448      if (window.PlazaAnalytics) {
16449        window.PlazaAnalytics.track('add_to_cart', { slug: String(window._pit_slug || '') });
16450      }
16451
16452      // Redirect to checkout with buy_now mode
16453      window.location.href = '/checkout?mode=buy_now';
16454      
16455    } catch (err) {
16456      console.error('Buy Now error:', err);
16457      
16458      if (window.showToast) {
16459        window.showToast({ 
16460          type: 'warn', 
16461          title: 'Gagal memproses', 
16462          message: String(err?.message || 'Terjadi kesalahan saat memproses pembelian'), 
16463          duration: 4500 
16464        });
16465      } else {
16466        alert(err.message || 'Terjadi kesalahan saat memproses pembelian');
16467      }
16468    }
16469  };
16470
16471  /**
16472   * Alternative Buy Now with confirmation dialog
16473   */
16474  window.buyNowWithConfirm = async function(e) {
16475    if (e) e.preventDefault();
16476    
16477    try {
16478      const { productId, qty, variantId } = getCurrentProductData();
16479      
16480      const name = (window.PD_NAME !== undefined) ? String(window.PD_NAME) : (document.getElementById('pd-title')?.textContent || '');
16481      const variantText = getVariantSummaryText();
16482      const priceTxt = document.getElementById('pd-price-current')?.textContent || 'Rp0';
16483      
16484      const confirmMessage = `Lanjut ke checkout?\n\nProduk: ${name}${variantText ? `\nVariant: ${variantText}` : ''}\nQty: ${qty}\nHarga: ${priceTxt}`;
16485      
16486      if (!confirm(confirmMessage)) {
16487        return;
16488      }
16489      
16490      // Proceed with buy now
16491      await window.buyNow(e);
16492      
16493    } catch (err) {
16494      console.error('Buy Now with confirm error:', err);
16495      alert(err.message || 'Terjadi kesalahan');
16496    }
16497  };
16498
16499  /**
16500   * Attach event listener for Buy with Voucher button
16501   */
16502  function setupBuyButtons() {
16503    // Setup desktop buy button (regular buy now without voucher)
16504    const buyBtn = document.getElementById('btn-buy');
16505    if (buyBtn) {
16506      buyBtn.addEventListener('click', function(e) {
16507        e.preventDefault();
16508        if (buyBtn.disabled) return;
16509        if (window.buyNow) {
16510          window.buyNow(e);
16511        }
16512      });
16513    }
16514    
16515    // Setup desktop buy with voucher button
16516    const voucherBtn = document.getElementById('btn-buy-voucher');
16517    if (voucherBtn) {
16518      voucherBtn.addEventListener('click', function(e) {
16519        e.preventDefault();
16520        
16521        // Validate stock availability before proceeding
16522        if (this.disabled || this.getAttribute('data-stock-blocked') === 'true') {
16523          const stockReason = this.getAttribute('data-stock-reason') || 'Stok tidak tersedia';
16524          
16525          if (window.showToast) {
16526            window.showToast({
16527              type: 'warn',
16528              title: 'Stok Tidak Tersedia',
16529              message: stockReason,
16530              duration: 3500
16531            });
16532          } else {
16533            alert(stockReason);
16534          }
16535          return;
16536        }
16537        
16538        const voucherCode = this.dataset.voucherCode;
16539        if (voucherCode) {
16540          window.buyNowWithVoucher(e, voucherCode);
16541        }
16542      });
16543    }
16544    
16545  }
16546  
16547  // CRITICAL: Attach mobile button handler at DOCUMENT level with CAPTURE phase
16548  // This is the ONLY way to guarantee it fires regardless of other scripts
16549  document.addEventListener('click', function(e) {
16550    // Check if click is on mobile buy button or its children
16551    const target = e.target.closest('#mf-buy');
16552    if (!target) return; // Not our button
16553    
16554    e.preventDefault();
16555    e.stopPropagation();
16556    e.stopImmediatePropagation();
16557    
16558    if (target.disabled) return;
16559    
16560    // Get voucher code from parent FAB's data attribute
16561    const mobileFab = document.getElementById('pd-mobile-fab');
16562    const voucherCode = mobileFab ? (mobileFab.getAttribute('data-voucher-code') || '') : '';
16563    
16564    if (voucherCode && voucherCode.trim() !== '' && window.buyNowWithVoucher) {
16565      window.buyNowWithVoucher(e, voucherCode);
16566    } else if (window.buyNow) {
16567      window.buyNow(e);
16568    }
16569  }, true); // CAPTURE PHASE - fires BEFORE any bubble phase handlers
16570  
16571  // Execute immediately if DOM already loaded, otherwise wait
16572  if (document.readyState === 'loading') {
16573    document.addEventListener('DOMContentLoaded', setupBuyButtons);
16574  } else {
16575    setupBuyButtons();
16576  }
16577
16578  /**
16579   * Buy Now with Auto-Applied Voucher
16580   * Applies voucher to session before redirecting to checkout (like cart does)
16581   * 
16582   * @param {Event} e - Click event
16583   * @param {string} voucherCode - Voucher code to apply
16584   */
16585  window.buyNowWithVoucher = async function(e, voucherCode) {
16586    if (e) e.preventDefault();
16587    
16588    console.log('[buyNowWithVoucher] Called with voucher code:', voucherCode);
16589    
16590    try {
16591      // CRITICAL: Check stock availability first
16592      const voucherBtn = document.getElementById('btn-buy-voucher');
16593      if (voucherBtn) {
16594        if (voucherBtn.disabled || voucherBtn.getAttribute('data-stock-blocked') === 'true') {
16595          const stockReason = voucherBtn.getAttribute('data-stock-reason') || 'Stok tidak tersedia';
16596          
16597          if (window.showToast) {
16598            window.showToast({
16599              type: 'warn',
16600              title: 'Stok Tidak Tersedia',
16601              message: stockReason,
16602              duration: 3500
16603            });
16604          } else {
16605            alert(stockReason);
16606          }
16607          return;
16608        }
16609      }
16610      
16611      // Check login status first
16612      const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true' ||
16613                        document.body.classList.contains('logged-in') ||
16614                        window.userLoggedIn === true ||
16615                        document.body.dataset.userLoggedIn === 'true';
16616      
16617      console.log('[buyNowWithVoucher] User logged in:', isLoggedIn);
16618      
16619      if (!isLoggedIn) {
16620        const currentUrl = encodeURIComponent(window.location.pathname + window.location.search);
16621        window.location.href = '/login?return=' + currentUrl;
16622        return;
16623      }
16624
16625      // Get current product data
16626      const { productId, qty, variantId } = getCurrentProductData();
16627      const payload = createBuyNowPayload(productId, qty, variantId);
16628      
16629      console.log('[buyNowWithVoucher] Product data:', { productId, qty, variantId });
16630      
16631      // Get category path for voucher validation
16632      const categoryPath = window.PD_CATEGORY_PATH || '';
16633      
16634      // Step 1: Clear any existing voucher first (important for clean state)
16635      console.log('[buyNowWithVoucher] Step 1: Clearing existing voucher...');
16636      try {
16637        await fetch('/api/voucher/remove.php', {
16638          method: 'POST',
16639          headers: { 
16640            'X-CSRF-Token': getCsrfToken()
16641          },
16642          body: JSON.stringify({
16643            csrf_token: getCsrfToken()
16644          }),
16645          credentials: 'same-origin'
16646        });
16647      } catch (e) {
16648        // Ignore error if no voucher exists
16649        console.debug('No existing voucher to remove or removal failed');
16650      }
16651      
16652      // Step 2: Apply voucher to session (like cart does)
16653      console.log('[buyNowWithVoucher] Step 2: Applying voucher to session...');
16654      const applyRes = await fetch('/api/voucher/apply.php', {
16655        method: 'POST',
16656        headers: { 
16657          'Content-Type': 'application/json',
16658          'X-CSRF-Token': getCsrfToken()
16659        },
16660        credentials: 'same-origin',
16661        body: JSON.stringify({
16662          csrf_token: getCsrfToken(),
16663          code: voucherCode,
16664          items: [{
16665            pid: productId,
16666            price: payload.meta.price,
16667            qty: qty,
16668            category_path: categoryPath
16669          }],
16670          shipping_cost: null // Will be calculated in checkout
16671        })
16672      });
16673      
16674      const applyData = await applyRes.json();
16675      
16676      console.log('[buyNowWithVoucher] Voucher apply response:', applyData);
16677      
16678      if (!applyData.success) {
16679        // Voucher invalid - show error and fallback to regular buy now
16680        console.warn('[buyNowWithVoucher] Voucher application failed:', applyData.message);
16681        if (window.showToast) {
16682          window.showToast({
16683            type: 'error',
16684            title: 'Voucher Tidak Valid',
16685            message: applyData.message || 'Voucher tidak dapat diterapkan untuk produk ini',
16686            duration: 4000
16687          });
16688        } else {
16689          alert(applyData.message || 'Voucher tidak dapat diterapkan');
16690        }
16691        
16692        // Fallback to regular buy now without voucher
16693        console.log('Voucher failed, proceeding with regular buy now');
16694        return window.buyNow(e);
16695      }
16696      
16697      // Step 3: Store buy now data in sessionStorage
16698      console.log('[buyNowWithVoucher] Step 3: Storing buy now data in sessionStorage');
16699      sessionStorage.setItem('buyNowData', JSON.stringify(payload));
16700      
16701      // Step 4: Redirect to checkout
16702      // Voucher sudah di session, checkout akan auto-load via loadAppliedVoucher()
16703      console.log('[buyNowWithVoucher] Step 4: Redirecting to checkout with voucher applied');
16704      window.location.href = '/checkout?mode=buy_now';
16705      
16706    } catch (err) {
16707      console.error('Buy Now with Voucher error:', err);
16708      
16709      if (window.showToast) {
16710        window.showToast({
16711          type: 'error',
16712          title: 'Terjadi Kesalahan',
16713          message: 'Gagal memproses pembelian dengan voucher. Silakan coba lagi.',
16714          duration: 3000
16715        });
16716      } else {
16717        alert('Gagal memproses pembelian. Silakan coba lagi.');
16718      }
16719    }
16720  };
16721})();
16722</script>
vendor: 1 bytes, line 16722
16722
16723<script data-inlined="pd-voucher-recalc.js">
16724/**
16725 * Product Detail - Voucher Discount Recalculation
16726 * 
16727 * Handles dynamic voucher discount recalculation when variant price changes
16728 * Supports both PERCENTAGE and FIXED_AMOUNT voucher types
16729 */
16730
16731(function() {
16732  'use strict';
16733
16734  /**
16735   * Calculate voucher discount based on type and price
16736   * 
16737   * @param {string} type - Voucher type: 'PERCENTAGE' or 'FIXED_AMOUNT'
16738   * @param {number} value - Voucher value (percentage or fixed amount)
16739   * @param {number} price - Current variant price
16740   * @param {number} maxDiscount - Maximum discount for percentage vouchers (0 = no limit)
16741   * @return {number} Calculated discount amount
16742   */
16743  function calculateVoucherDiscount(type, value, price, maxDiscount) {
16744    if (type === 'PERCENTAGE') {
16745      // Calculate percentage discount
16746      let discount = (price * value) / 100;
16747      
16748      // Apply max discount cap if set
16749      if (maxDiscount > 0 && discount > maxDiscount) {
16750        discount = maxDiscount;
16751      }
16752      
16753      return Math.round(discount);
16754    } else {
16755      // FIXED_AMOUNT - discount doesn't change with price
16756      return Math.round(value);
16757    }
16758  }
16759
16760  /**
16761   * Format number as Rupiah without 'Rp' prefix
16762   * 
16763   * @param {number} num - Number to format
16764   * @return {string} Formatted number (e.g., "20.000")
16765   */
16766  function formatNumber(num) {
16767    return Math.round(num).toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.');
16768  }
16769
16770  /**
16771   * Update voucher button discount label
16772   * 
16773   * @param {number} newPrice - New variant price
16774   */
16775  function updateVoucherDiscount(newPrice) {
16776    const voucherBtn = document.getElementById('btn-buy-voucher');
16777    if (!voucherBtn) return;
16778
16779    // Get voucher data from button attributes
16780    const voucherType = voucherBtn.dataset.voucherType;
16781    const voucherValue = parseFloat(voucherBtn.dataset.voucherValue) || 0;
16782    const voucherMax = parseFloat(voucherBtn.dataset.voucherMax) || 0;
16783
16784    if (!voucherType || voucherValue === 0) return;
16785
16786    // Calculate new discount
16787    const newDiscount = calculateVoucherDiscount(voucherType, voucherValue, newPrice, voucherMax);
16788
16789    // Update the discount label
16790    const saveLabel = voucherBtn.querySelector('.bv-save');
16791    if (saveLabel) {
16792      saveLabel.textContent = '-Rp' + formatNumber(newDiscount);
16793    }
16794
16795    // Also update mobile FAB voucher display if exists
16796    updateMobileFabVoucher(newDiscount);
16797  }
16798
16799  /**
16800   * Update mobile FAB voucher value
16801   * 
16802   * @param {number} discountAmount - New discount amount
16803   */
16804  function updateMobileFabVoucher(discountAmount) {
16805    const fab = document.getElementById('pd-mobile-fab');
16806    if (fab) {
16807      fab.dataset.voucherValue = discountAmount.toString();
16808    }
16809  }
16810
16811  /**
16812   * Listen to variant change events and recalculate discount
16813   */
16814  function initVoucherRecalculation() {
16815    window.addEventListener('pd-variant-changed', function(e) {
16816      const detail = e.detail || {};
16817      const newPrice = detail.price || 0;
16818
16819      if (newPrice > 0) {
16820        updateVoucherDiscount(newPrice);
16821      }
16822    });
16823  }
16824
16825  // Initialize when DOM is ready
16826  if (document.readyState === 'loading') {
16827    document.addEventListener('DOMContentLoaded', initVoucherRecalculation);
16828  } else {
16829    initVoucherRecalculation();
16830  }
16831
16832})();
16833
16834</script>
vendor: 1 bytes, line 16834
16834
16835<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>
16835
16836
16837<script nonce="se3iyzEHMc9FQEB4wSyd0w==">
16838(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'}})}}
16839
16840function _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()}})();
16841
16842(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()}})})();
16843</script>
16843<footer class="site-footer footer-compact variant-stripes" role="contentinfo">
16844  <div class="ft-top ft-collapsible" id="ftTop">
16845    <div class="container ft-grid">
16846
16847      <div class="ft-col ft-help">
16848        <h4 class="ft-title">Butuh Bantuan?</h4>
16849        <p class="ft-desc">Hubungi kami melalui Official WhatsApp interaktif sekarang.</p>
16850        <a class="ft-wa-btn" href="https://mikale.me/whatsappBERLINTOTO" target="_blank" rel="nofollow noopener">
16851          <span class="ico" aria-hidden="true">
16852            <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>
16853          </span>
16854          Kirim Pesan
16855        </a>
16856
16857        <p class="ft-desc sm-gap">Ikuti kami:</p>
16858        <div class="ft-social">
16859          <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>
16860          <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
16860V7.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>
16861          <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>
16862        </div>
16863      </div>
16864
16865            <!-- Collapsible Content Wrapper (Non-Homepage Only) -->
16866      <div class="ft-collapsible-content" id="ftCollapsible">
16867      
16868      <div class="ft-col">
16869        <h4 class="ft-title">Tentang BERLINTOTO</h4>
16870        <ul class="ft-links">
16871          <li><a href="https://www.bim.miami/">Tentang BERLINTOTO</a></li>
16872          <li><a href="https://www.bim.miami/">Kontak BERLINTOTO</a></li>
16873          <li><a href="https://www.bim.miami/">Lokasi Toko</a></li>
16874          <li><a href="https://www.bim.miami/">FAQ</a></li>
16875        </ul>
16876        <div class="ft-address">
16877          <p class="company">COSTEL</p>
16878                      <p>JL. Harapan Blok Melati No. 27, Kel. Pademangan Barat,</p>
16879                      <p>Kec. Jakarta, Kota Jakarta, DKI JAKARTA, 11752</p>
16880                  </div>
16881      </div>
16882
16883      <div class="ft-col">
16884        <h4 class="ft-title">Informasi</h4>
16885        <ul class="ft-links">
16886          <li><a href="/syarat-dan-ketentuan">Syarat &amp; Ketentuan</a></li>
16887          <li><a href="/kebijakan-privasi">Kebijakan Privasi</a></li>
16888          <li><a href="/kebijakan-cookie">Kebijakan Cookie</a></li>
16889          <li><a href="/pengiriman-dan-pengembalian">Pengiriman &amp; Pengembalian</a></li>
16890          <li><a href="/pembayaran-dan-cicilan">Pembayaran &amp; Cicilan</a></li>
16891          <li><a href="/garansi-dan-layanan">Garansi &amp; Layanan</a></li>
16892        </ul>
16893
16894        <div class="ft-payments payments-small" aria-label="Metode Pembayaran">
16895          <div class="pay-row">
16896            <img src="https://imgmahasuhu.io/images/2026/05/20/wopslot-mandiri.png" alt="Mandiri" loading="lazy">
16897            <img src="https://imgmahasuhu.io/images/2026/05/20/wopslot-bni.gif" alt="BNI" loading="lazy">
16898            <img src="https://imgmahasuhu.io/images/2026/05/20/wopslot-bri.gif" alt="BRI" loading="lazy">
16899            <img src="https://imgmahasuhu.io/images/2026/05/20/wopslot-qris.gif" alt="QRIS" loading="lazy">
16900            <img src="https://imgmahasuhu.io/images/2026/05/20/wopslot-bca.gif" alt="BCA" loading="lazy">
16901          </div>
16902        </div>
16903      </div>
16904
16905            </div>
16906      <!-- End Collapsible Content -->
16907      
16908      <!-- Expand Button (Mobile Only) -->
16909      <button class="ft-expand-btn" id="ftExpandBtn" aria-label="Lihat semua informasi footer">
16910        <span class="expand-text">Selengkapnya</span>
16911        <svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
16912          <path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/>
16913        </svg>
16914      </button>
16915      
16916    </div>
16917  </div>
16918  <div class="ft-bottom">
16919    <div class="container ft-bottom-inner">
16920      <div class="ft-copy">
16921        <span>ANGIN-RIBUT &copy; <span id="yearNow">2026</span></span>
16922        <span>PT ANGIN-RIBUT. All rights reserved.</span>
16923      </div>
16924    </div>
16925  </div>
16926  
16927          <style nonce="se3iyzEHMc9FQEB4wSyd0w==">
16928            /* Chat Widget Styles - Lightweight & Minimal */
16929            .chat-widget {
16930                position: fixed;
16931                bottom: 20px;
16932                right: 20px;
16933                z-index: 9999;
16934                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
16935            }
16936            
16937            /* Hide floating button on mobile - use bottom nav instead */
16938            @media (max-width: 640px) {
16939                .chat-toggle-btn {
16940                    display: none !important;
16941                }
16942                
16943                /* Ensure popup is still visible on mobile even when button is hidden */
16944                .chat-widget {
16945                    display: block !important;
16946                }
16947                
16948                .chat-popup {
16949                    display: none; /* Hidden by default, shown via JS */
16950                }
16951                
16952                .chat-popup[style*="display: block"],
16953                .chat-popup[style*="display:block"] {
16954                    display: block !important;
16955                }
16956            }
16957            
16958            .chat-toggle-btn {
16959                width: 60px;
16960                height: 60px;
16961                border-radius: 50%;
16962                background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
16963                border: none;
16964                color: white;
16965                cursor: pointer;
16966                box-shadow: 0 4px 12px rgba(242, 140, 26, 0.3);
16967                position: relative;
16968                transition: transform 0.2s, box-shadow 0.2s;
16969            }
16970            
16971            .chat-toggle-btn:hover {
16972                transform: scale(1.05);
16973                box-shadow: 0 6px 20px rgba(242, 140, 26, 0.4);
16974            }
16975            
16976            #chat-toggle-btn .chat-icon {
16977                position: absolute;
16978                top: 50%;
16979                left: 50%;
16980                transform: translate(-50%, -50%);
16981                width: 28px !important;
16982                height: 28px !important;
16983            }
16984            
16985            .chat-unread-badge {
16986                position: absolute;
16987                top: -5px;
16988                right: -5px;
16989                background: #ef4444;
16990                color: white;
16991                border-radius: 12px;
16992                padding: 2px 6px;
16993                font-size: 11px;
16994                font-weight: 600;
16995                min-width: 20px;
16996                text-align: center;
16997            }
16998            
16999            .chat-popup {
17000                position: absolute;
17001                bottom: 80px;
17002                right: 0;
17003                width: 380px;
17004                max-width: calc(100vw - 40px);
17005                height: 500px;
17006                max-height: calc(100vh - 120px);
17007                background: white;
17008                border-radius: 12px;
17009                box-shadow: 0 10px 40px rgba(0,0,0,0.2);
17010                display: flex;
17011                flex-direction: column;
17012                overflow: hidden;
17013            }
17014            
17015            .chat-popup-header {
17016                background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
17017                color: white;
17018                padding: 16px 20px;
17019                display: flex;
17020                justify-content: space-between;
17021                align-items: center;
17022                flex-shrink: 0;
17023            }
17024            
17025            .chat-header-title {
17026                display: flex;
17027                flex-direction: column;
17028                gap: 2px;
17029                min-height: 0;
17030            }
17031            
17032            .chat-popup-header h3 {
17033                margin: 0;
17034                font-size: 18px;
17035                font-weight: 600;
17036                line-height: 1.2;
17037            }
17038            
17039            .chat-operating-hours {
17040                font-size: 11px;
17041                opacity: 0.9;
17042                font-weight: 400;
17043                display: flex;
17044                align-items: center;
17045                gap: 4px;
17046                line-height: 1.2;
17047            }
17048            
17049            .chat-operating-hours svg {
17050                width: 12px;
17051                height: 12px;
17052                opacity: 0.8;
17053                flex-shrink: 0;
17054            }
17055            
17056            .chat-header-actions {
17057                display: flex;
17058                align-items: center;
17059                gap: 8px;
17060            }
17061            
17062            /* Notification Toggle Button */
17063            .btn-notif-toggle {
17064                background: none;
17065                border: none;
17066                color: white;
17067                font-size: 18px;
17068                cursor: pointer;
17069                padding: 4px 8px;
17070                transition: all 0.2s;
17071                opacity: 0.8;
17072            }
17073            
17074            .btn-notif-toggle:hover {
17075                opacity: 1;
17076                transform: scale(1.1);
17077            }
17078            
17079            /* Icon states */
17080            .btn-notif-toggle[data-status="not-subscribed"] svg {
17081                opacity: 0.7;
17082            }
17083            
17084            .btn-notif-toggle[data-status="not-subscribed"]:hover svg {
17085                opacity: 1;
17086            }
17087            
17088            .btn-notif-toggle[data-status="subscribed"] svg {
17089                opacity: 1;
17090                animation: bellRing 2s ease-in-out;
17091            }
17092            
17093            .btn-notif-toggle[data-status="blocked"] svg {
17094                color: #ff6b6b;
17095            }
17096            
17097            .btn-notif-toggle[data-status="not-supported"] {
17098                display: none !important;
17099            }
17100            
17101            @keyframes bellRing {
17102                0%, 100% { transform: rotate(0deg); }
17103                10%, 30% { transform: rotate(-10deg); }
17104                20%, 40% { transform: rotate(10deg); }
17105            }
17106            
17107            /* Notification Info Bar */
17108            .chat-notif-info-bar {
17109                position: absolute;
17110                top: 64px;
17111                left: 0;
17112                right: 0;
17113                background: linear-gradient(to right, #e3f2fd, #f5f5f5);
17114                border-bottom: 1px solid #bbdefb;
17115                padding: 8px 12px;
17116                display: flex;
17117                align-items: center;
17118                gap: 8px;
17119                font-size: 12px;
17120                color: #616161;
17121                z-index: 10;
17122                animation: slideDown 0.3s ease;
17123            }
17124            
17125            .chat-notif-info-bar svg {
17126                flex-shrink: 0;
17127            }
17128            
17129            .chat-notif-info-bar .small {
17130                flex: 1;
17131                line-height: 1.4;
17132            }
17133            
17134            .chat-notif-info-bar .btn-dismiss {
17135                background: none;
17136                border: none;
17137                color: #9e9e9e;
17138                cursor: pointer;
17139                padding: 0 4px;
17140                font-size: 16px;
17141                transition: color 0.2s;
17142            }
17143            
17144            .chat-notif-info-bar .btn-dismiss:hover {
17145                color: #616161;
17146            }
17147            
17148            @keyframes slideDown {
17149                from {
17150                    max-height: 0;
17151                    opacity: 0;
17152                }
17153                to {
17154                    max-height: 50px;
17155                    opacity: 1;
17156                }
17157            }
17158            
17159            .chat-close-btn {
17160                background: none;
17161                border: none;
17162                color: white;
17163                font-size: 28px;
17164                line-height: 1;
17165                cursor: pointer;
17166                padding: 0;
17167                width: 30px;
17168                height: 30px;
17169            }
17170            
17171            .chat-popup-body {
17172                flex: 1 1 auto;
17173                display: flex;
17174                flex-direction: column;
17175                min-height: 0;
17176                overflow: hidden;
17177                height: calc(100% - 64px);
17178                position: relative;
17179                transition: padding-top 0.3s ease;
17180            }
17181            
17182            /* Add padding when info bar is visible */
17183            .chat-popup-body.has-info-bar {
17184                padding-top: 40px;
17185            }
17186            
17187            .chat-view {
17188                flex: 1 1 auto;
17189                display: none;
17190                flex-direction: column;
17191                min-height: 0;
17192                overflow: hidden;
17193            }
17194            
17195            #chat-list-view {
17196                display: flex;
17197            }
17198            
17199            #chat-messages-view {
17200                display: none;
17201            }
17202            
17203            #chat-messages-view.active {
17204                display: flex;
17205                flex-direction: column;
17206            }
17207            
17208            #chat-new-view {
17209                display: none;
17210            }
17211            
17212            #chat-new-view.active {
17213                display: flex;
17214            }
17215            
17216            .chat-conversations-list {
17217                flex: 1;
17218                overflow-y: auto;
17219                padding: 0;
17220                background: white;
17221            }
17222            
17223            /* Custom Scrollbar for Conversations List */
17224            .chat-conversations-list::-webkit-scrollbar {
17225                width: 6px;
17226            }
17227            
17228            .chat-conversations-list::-webkit-scrollbar-track {
17229                background: #f1f1f1;
17230            }
17231            
17232            .chat-conversations-list::-webkit-scrollbar-thumb {
17233                background: #F28C1A;
17234                border-radius: 3px;
17235            }
17236            
17237            .chat-conversations-list::-webkit-scrollbar-thumb:hover {
17238                background: #D97809;
17239            }
17240            
17241            /* Firefox */
17242            .chat-conversations-list {
17243                scrollbar-width: thin;
17244                scrollbar-color: #F28C1A #f1f1f1;
17245            }
17246            
17247            .chat-conversation-item {
17248                padding: 12px 16px;
17249                cursor: pointer;
17250                display: flex;
17251                gap: 12px;
17252                align-items: flex-start;
17253                transition: background 0.15s ease;
17254                color: #33371f;
17255                border-bottom: 1px solid #f0f0f0;
17256            }
17257            
17258            .chat-conversation-item:hover {
17259                background: #f5f5f5;
17260            }
17261            
17262            .chat-conversation-item.active {
17263                background: #FFF5EB;
17264            }
17265            
17266            /* WhatsApp-like Avatar */
17267            .conversation-avatar {
17268                width: 48px;
17269                height: 48px;
17270                border-radius: 50%;
17271                background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
17272                flex-shrink: 0;
17273                display: flex;
17274                align-items: center;
17275                justify-content: center;
17276                color: white;
17277                font-weight: 600;
17278                font-size: 18px;
17279            }
17280            
17281            .conversation-avatar svg {
17282                width: 24px;
17283                height: 24px;
17284            }
17285            
17286            /* WhatsApp-like Content */
17287            .conversation-content {
17288                flex: 1;
17289                min-width: 0;
17290                display: flex;
17291                flex-direction: column;
17292                gap: 4px;
17293            }
17294            
17295            .conversation-header {
17296                display: flex;
17297                justify-content: space-between;
17298                align-items: center;
17299                gap: 8px;
17300            }
17301            
17302            .conversation-name {
17303                font-weight: 600;
17304                font-size: 15px;
17305                color: #111;
17306                white-space: nowrap;
17307                overflow: hidden;
17308                text-overflow: ellipsis;
17309            }
17310            
17311            .conversation-time {
17312                font-size: 12px;
17313                color: #7d8166;
17314                flex-shrink: 0;
17315            }
17316            
17317            .conversation-preview {
17318                display: flex;
17319                justify-content: space-between;
17320                align-items: center;
17321                gap: 8px;
17322            }
17323            
17324            .conversation-message {
17325                font-size: 14px;
17326                color: #667781;
17327                white-space: nowrap;
17328                overflow: hidden;
17329                text-overflow: ellipsis;
17330                margin: 0;
17331                flex: 1;
17332            }
17333            
17334            .conversation-unread {
17335                background: #25D366;
17336                color: white;
17337                border-radius: 12px;
17338                padding: 2px 8px;
17339                font-size: 12px;
17340                font-weight: 600;
17341                min-width: 20px;
17342                text-align: center;
17343                flex-shrink: 0;
17344            }
17345            
17346            /* Active item styling */
17347            .chat-conversation-item.active .conversation-name {
17348                color: #F28C1A;
17349            }
17350            
17351            .chat-conversation-item.active .conversation-time {
17352                color: #F28C1A;
17353            }
17354            
17355            .chat-new-btn {
17356                margin: 12px;
17357                padding: 12px;
17358                background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
17359                color: white;
17360                border: none;
17361                border-radius: 8px;
17362                cursor: pointer;
17363                font-weight: 500;
17364                font-size: 14px;
17365                display: flex;
17366                align-items: center;
17367                justify-content: center;
17368                gap: 8px;
17369                transition: all 0.2s;
17370                flex-shrink: 0;
17371            }
17372            
17373            .chat-new-btn:hover {
17374                transform: translateY(-1px);
17375                box-shadow: 0 4px 8px rgba(242, 140, 26, 0.3);
17376            }
17377            
17378            .chat-conversation-title {
17379                flex: 1;
17380                font-weight: 600;
17381                font-size: 16px;
17382                color: #1f2937;
17383            }
17384            
17385            .chat-messages-container {
17386                flex: 1;
17387                overflow-y: auto;
17388                padding: 16px;
17389                display: flex;
17390                flex-direction: column;
17391                gap: 8px;
17392                min-height: 0;
17393                background: #E5DDD5;
17394            }
17395            
17396            /* Custom Scrollbar for Messages Container */
17397            .chat-messages-container::-webkit-scrollbar {
17398                width: 6px;
17399            }
17400            
17401            .chat-messages-container::-webkit-scrollbar-track {
17402                background: rgba(0, 0, 0, 0.05);
17403            }
17404            
17405            .chat-messages-container::-webkit-scrollbar-thumb {
17406                background: rgba(242, 140, 26, 0.6);
17407                border-radius: 3px;
17408            }
17409            
17410            .chat-messages-container::-webkit-scrollbar-thumb:hover {
17411                background: #F28C1A;
17412            }
17413            
17414            /* Firefox */
17415            .chat-messages-container {
17416                scrollbar-width: thin;
17417                scrollbar-color: rgba(242, 140, 26, 0.6) rgba(0, 0, 0, 0.05);
17418            }
17419            
17420            /* WhatsApp Group-like Messages */
17421            .chat-message {
17422                display: flex;
17423                margin-bottom: 4px;
17424                animation: fadeIn 0.2s ease;
17425            }
17426            
17427            @keyframes fadeIn {
17428                from { opacity: 0; transform: translateY(10px); }
17429                to { opacity: 1; transform: translateY(0); }
17430            }
17431            
17432            .chat-message.mine {
17433                justify-content: flex-end;
17434            }
17435            
17436            .chat-message-bubble {
17437                max-width: 80%;
17438                padding: 6px 8px 8px 10px;
17439                border-radius: 8px;
17440                position: relative;
17441                box-shadow: 0 1px 0.5px rgba(0,0,0,0.13);
17442            }
17443            
17444            /* Bubble from others (Admin/CS) */
17445            .chat-message:not(.mine) .chat-message-bubble {
17446                background: #FFFFFF;
17447                border-radius: 0 8px 8px 8px;
17448            }
17449            
17450            /* Bubble tail for others */
17451            .chat-message:not(.mine) .chat-message-bubble::before {
17452                content: '';
17453                position: absolute;
17454                top: 0;
17455                left: -8px;
17456                width: 0;
17457                height: 0;
17458                border-style: solid;
17459                border-width: 0 8px 13px 0;
17460                border-color: transparent #FFFFFF transparent transparent;
17461            }
17462            
17463            /* Bubble from user (mine) */
17464            .chat-message.mine .chat-message-bubble {
17465                background: #e4f5d0; /* Light blue for user messages */
17466                border-radius: 8px 0 8px 8px;
17467            }
17468            
17469            /* Bubble tail for mine */
17470            .chat-message.mine .chat-message-bubble::before {
17471                content: '';
17472                position: absolute;
17473                top: 0;
17474                right: -8px;
17475                width: 0;
17476                height: 0;
17477                border-style: solid;
17478                border-width: 0 0 13px 8px;
17479                border-color: transparent transparent transparent #eaf5d0;
17480            }
17481            
17482            .message-sender {
17483                font-size: 12.5px;
17484                font-weight: 600;
17485                color: #F28C1A !important;
17486                margin-bottom: 4px;
17487            }
17488            
17489            .chat-message.mine .message-sender {
17490                display: none;
17491            }
17492            
17493            /* Message text */
17494            .message-text {
17495                font-size: 14px;
17496                line-height: 19px;
17497                color: #ffffff !important;
17498                word-wrap: break-word;
17499                white-space: pre-wrap;
17500                margin-bottom: 4px;
17501            }
17502            
17503            /* Message metadata (time + status) */
17504            .message-meta {
17505                display: flex;
17506                align-items: center;
17507                justify-content: flex-end;
17508                gap: 4px;
17509                margin-top: 2px;
17510            }
17511            
17512            .message-time {
17513                font-size: 11px;
17514                color: #728166 !important;
17515                line-height: 15px;
17516            }
17517            
17518            /* Use multiple selectors to override global styles */
17519            .chat-message-bubble .message-status,
17520            .message-meta .message-status,
17521            .chat-message .message-status {
17522                width: 16px !important;
17523                height: 16px !important;
17524                min-width: 16px !important;
17525                min-height: 16px !important;
17526                max-width: 16px !important;
17527                max-height: 16px !important;
17528                flex-shrink: 0;
17529                color: #728166 !important;
17530                fill: currentColor !important;
17531            }
17532            
17533            .chat-message:not(.mine) .message-status {
17534                display: none;
17535            }
17536            
17537            /* Loading dots animation */
17538            @keyframes loadingDots {
17539                0%, 20% { content: '.'; }
17540                40% { content: '..'; }
17541                60%, 100% { content: '...'; }
17542            }
17543            
17544            .message-status.loading::after {
17545                content: '...';
17546                animation: loadingDots 1.5s infinite;
17547                font-size: 16px;
17548                line-height: 16px;
17549            }
17550            
17551            .message-status.loading path {
17552                display: none;
17553            }
17554            
17555            .message-status.failed {
17556                color: #ef4444 !important;
17557                cursor: pointer;
17558            }
17559            
17560            .message-status.sent {
17561                color: #667781 !important;
17562            }
17563            
17564            .chat-new-form {
17565                flex: 1;
17566                overflow-y: auto;
17567                padding: 16px;
17568                display: flex;
17569                flex-direction: column;
17570            }
17571            
17572            .chat-new-form form {
17573                display: flex;
17574                flex-direction: column;
17575                gap: 16px;
17576                flex: 1;
17577            }
17578            
17579            .chat-new-form .form-group {
17580                display: flex;
17581                flex-direction: column;
17582                gap: 6px;
17583            }
17584            
17585            .chat-new-form label {
17586                font-size: 14px;
17587                font-weight: 500;
17588                color: #374151;
17589            }
17590            
17591            .chat-new-form .form-control {
17592                width: 100%;
17593                padding: 10px 12px;
17594                border: 1px solid #d1d5db;
17595                border-radius: 8px;
17596                font-size: 14px;
17597                font-family: inherit;
17598                transition: border-color 0.2s;
17599                box-sizing: border-box;
17600            }
17601            
17602            .chat-new-form .form-control:focus {
17603                outline: none;
17604                border-color: #F28C1A;
17605                box-shadow: 0 0 0 3px rgba(242, 140, 26, 0.1);
17606            }
17607            
17608            .chat-new-form textarea.form-control {
17609                resize: vertical;
17610                min-height: 100px;
17611            }
17612            
17613            .chat-new-form .btn {
17614                padding: 12px 20px;
17615                border: none;
17616                border-radius: 8px;
17617                font-size: 15px;
17618                font-weight: 500;
17619                cursor: pointer;
17620                transition: all 0.2s;
17621                text-align: center;
17622            }
17623            
17624            .chat-new-form .btn-primary {
17625                background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
17626                color: white;
17627            }
17628            
17629            .chat-new-form .btn-primary:hover {
17630                transform: translateY(-1px);
17631                box-shadow: 0 4px 8px rgba(242, 140, 26, 0.3);
17632            }
17633            
17634            .chat-new-form .btn-primary:active {
17635                transform: scale(0.98);
17636            }
17637            
17638            .chat-new-form .btn-block {
17639                width: 100%;
17640                margin-top: auto;
17641            }
17642            
17643            .chat-message-form {
17644                padding: 12px;
17645                border-top: 1px solid #e5e7eb;
17646                display: flex;
17647                flex-wrap: wrap;
17648                gap: 8px;
17649                align-items: flex-end;
17650                flex-shrink: 0;
17651                background: white;
17652            }
17653            
17654            .chat-product-card {
17655                flex: 1 1 100%;
17656                max-width: 100%;
17657                display: flex;
17658                gap: 12px;
17659                padding: 12px;
17660                background: #f3f4f6;
17661                border-radius: 8px;
17662                margin-bottom: 4px;
17663                border: 1px solid #e5e7eb;
17664            }
17665            
17666            .chat-product-card img {
17667                width: 60px;
17668                height: 60px;
17669                object-fit: cover;
17670                border-radius: 6px;
17671                flex-shrink: 0;
17672            }
17673            
17674            .chat-product-card-content {
17675                flex: 1;
17676                min-width: 0;
17677            }
17678            
17679            .chat-product-card-name {
17680                font-weight: 500;
17681                font-size: 14px;
17682                color: #1f2937;
17683                margin-bottom: 4px;
17684                white-space: nowrap;
17685                overflow: hidden;
17686                text-overflow: ellipsis;
17687            }
17688            
17689            .chat-product-card-variant {
17690                font-size: 12px;
17691                color: #74806b;
17692                margin-bottom: 4px;
17693            }
17694            
17695            .chat-product-card-price {
17696                font-size: 13px;
17697                color: #8bea66;
17698                font-weight: 600;
17699            }
17700            
17701            .chat-product-card-remove {
17702                align-self: flex-start;
17703                background: none;
17704                border: none;
17705                color: #9ca3af;
17706                cursor: pointer;
17707                padding: 4px;
17708                font-size: 18px;
17709                line-height: 1;
17710                transition: color 0.2s;
17711            }
17712            
17713            .chat-product-card-remove:hover {
17714                color: #ef4444;
17715            }
17716            
17717            /* Product card in messages (read-only display) */
17718            .message-product-card {
17719                display: flex;
17720                gap: 10px;
17721                padding: 10px;
17722                background: rgba(255, 255, 255, 0.5);
17723                border-radius: 8px;
17724                margin-bottom: 6px;
17725                border: 1px solid rgba(0, 0, 0, 0.08);
17726                align-items: center;
17727            }
17728            
17729            .chat-message.mine .message-product-card {
17730                background: rgba(255, 255, 255, 0.7);
17731            }
17732            
17733            .message-product-card img {
17734                width: 50px;
17735                height: 50px;
17736                object-fit: cover;
17737                border-radius: 6px;
17738                flex-shrink: 0;
17739            }
17740            
17741            .message-product-info {
17742                flex: 1;
17743                min-width: 0;
17744            }
17745            
17746            .message-product-name {
17747                font-weight: 500;
17748                font-size: 13px;
17749                color: #33371f;
17750                margin-bottom: 2px;
17751                overflow: hidden;
17752                text-overflow: ellipsis;
17753                display: -webkit-box;
17754                -webkit-line-clamp: 2;
17755                -webkit-box-orient: vertical;
17756                line-height: 1.3;
17757            }
17758            
17759            .message-product-variant {
17760                font-size: 11px;
17761                color: #78806b;
17762                margin-bottom: 2px;
17763            }
17764            
17765            .message-product-price {
17766                font-size: 12px;
17767                color: #c7ea66;
17768                font-weight: 600;
17769            }
17770            
17771            .message-product-link {
17772                flex-shrink: 0;
17773                width: 32px;
17774                height: 32px;
17775                display: flex;
17776                align-items: center;
17777                justify-content: center;
17778                background: rgba(102, 126, 234, 0.1);
17779                border-radius: 6px;
17780                color: #c7ea66;
17781                text-decoration: none;
17782                transition: all 0.2s;
17783            }
17784            
17785            .message-product-link:hover {
17786                background: #b7ea66;
17787                color: white;
17788            }
17789            
17790            .message-product-link svg {
17791                width: 16px;
17792                height: 16px;
17793            }
17794            
17795            .chat-message-input {
17796                flex: 1;
17797                border: 1px solid #d1d5db;
17798                border-radius: 20px;
17799                padding: 10px 16px;
17800                resize: none;
17801                max-height: 100px;
17802                font-family: inherit;
17803                font-size: 14px;
17804                line-height: 1.5;
17805                box-sizing: border-box;
17806                overflow-y: hidden;
17807            }
17808            
17809            .chat-message-input:focus {
17810                outline: none;
17811                border-color: #F28C1A;
17812                box-shadow: 0 0 0 3px rgba(242, 140, 26, 0.1);
17813            }
17814            
17815            .chat-send-btn {
17816                width: 40px;
17817                height: 40px;
17818                border-radius: 50%;
17819                background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
17820                border: none;
17821                color: white;
17822                cursor: pointer;
17823                flex-shrink: 0;
17824                display: flex;
17825                align-items: center;
17826                justify-content: center;
17827                transition: all 0.2s;
17828            }
17829            
17830            .chat-send-btn:hover {
17831                transform: scale(1.05);
17832                box-shadow: 0 2px 8px rgba(242, 140, 26, 0.3);
17833            }
17834            
17835            .chat-send-btn:active {
17836                transform: scale(0.95);
17837            }
17838            
17839            .chat-loading {
17840                text-align: center;
17841                padding: 20px;
17842                color: #6b7280;
17843            }
17844            
17845            /* Date Separator (WhatsApp-style) */
17846            .date-separator {
17847                display: flex;
17848                align-items: center;
17849                gap: 12px;
17850                margin: 24px 0 16px;
17851                opacity: 0;
17852                animation: fadeInSeparator 0.3s ease forwards;
17853            }
17854
17855            @keyframes fadeInSeparator {
17856                from {
17857                    opacity: 0;
17858                    transform: translateY(-10px);
17859                }
17860                to {
17861                    opacity: 1;
17862                    transform: translateY(0);
17863                }
17864            }
17865
17866            .date-separator-line {
17867                flex: 1;
17868                height: 1px;
17869                background: linear-gradient(90deg, transparent, rgba(0, 0, 0, 0.1) 50%, transparent);
17870            }
17871
17872            .date-separator-text {
17873                flex-shrink: 0;
17874                padding: 4px 12px;
17875                background: #e9ecef;
17876                color: #6c757d;
17877                font-size: 11px;
17878                font-weight: 600;
17879                text-transform: uppercase;
17880                letter-spacing: 0.5px;
17881                border-radius: 12px;
17882                box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
17883            }
17884            
17885            /* Mobile responsive */
17886            @media (max-width: 640px) {
17887                .chat-popup {
17888                    position: fixed !important; /* Override absolute positioning */
17889                    bottom: 45px !important; /* Above bottom nav */
17890                    right: 0 !important;
17891                    left: 0 !important;
17892                    width: 100% !important;
17893                    max-width: 100% !important;
17894                    /* Use dvh (dynamic viewport height) for browser UI awareness */
17895                    height: calc(100dvh - 45px) !important;
17896                    max-height: calc(100dvh - 45px) !important;
17897                    /* Fallback for browsers that don't support dvh */
17898                    height: calc(var(--viewport-height, 100vh) - 45px) !important;
17899                    max-height: calc(var(--viewport-height, 100vh) - 45px) !important;
17900                    border-radius: 0 !important;
17901                    z-index: 9999 !important;
17902                }
17903                
17904                .chat-popup-header {
17905                    border-radius: 0;
17906                }
17907                
17908                .chat-messages-container,
17909                .chat-conversations-list {
17910                    /* Adjust for mobile keyboard using dynamic viewport */
17911                    max-height: calc(100dvh - 190px);
17912                    /* Fallback for browsers that don't support dvh */
17913                    max-height: calc(var(--viewport-height, 100vh) - 190px);
17914                }
17915            }
17916        </style>
17917                <!-- Chat Widget Button -->
17918        <div id="chat-widget" class="chat-widget" 
17919             data-user-id=""
17920             data-is-logged-in="false"
17921             data-csrf-token="df5d776748b1ed8c8dd0d987bae99b1ee3dea6c6dcbe0eedb1bea766c56d569a">
17922            <button id="chat-toggle-btn" class="chat-toggle-btn" aria-label="Toggle chat">
17923                <svg class="chat-icon" width="28" height="28" viewBox="0 0 640 640" fill="currentColor">
17924                    <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"/>
17925                </svg>
17926                <span id="chat-unread-badge" class="chat-unread-badge" style="display: none;">0</span>
17927            </button>
17928            
17929            <div id="chat-popup" class="chat-popup" style="display: none;">
17930                <div class="chat-popup-header">
17931                    <div class="chat-header-title">
17932                        <h3>Bantuan</h3>
17933                        <span id="chatOperatingHours" class="chat-operating-hours" style="display: none;"></span>
17934                    </div>
17935                    <div class="chat-header-actions">
17936                        <button id="chatNotifToggle" class="btn-notif-toggle" data-status="not-supported" title="Push notification" style="display: none;">
17937                            <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
17938                                <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"/>
17939                            </svg>
17940                        </button>
17941                        <button id="chat-close-btn" class="chat-close-btn" aria-label="Close chat">×</button>
17942                    </div>
17943                </div>
17944                
17945                <!-- Notification Info Bar (conditional) -->
17946                <div class="chat-notif-info-bar" id="chatNotifInfoBar" style="display:none;">
17947                    <svg width="14" height="14" viewBox="0 0 24 24" fill="#ffc107">
17948                        <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"/>
17949                    </svg>
17950                    <span class="small">Aktifkan notifikasi untuk pemberitahuan balasan</span>
17951                    <button class="btn-dismiss" onclick="ChatNotification.dismissInfo()">✕</button>
17952                </div>
17953                
17954                <div class="chat-popup-body">
17955                    <!-- Conversations List View -->
17956                    <div id="chat-list-view" class="chat-view">
17957                        <div id="chat-conversations-list" class="chat-conversations-list">
17958                            <div class="chat-loading">Memuat...</div>
17959                            <!-- Conversation Item Structure (rendered by JS):
17960                            <div class="chat-conversation-item">
17961                                <div class="conversation-avatar">
17962                                    <svg>...</svg>
17963                                </div>
17964                                <div class="conversation-content">
17965                                    <div class="conversation-header">
17966                                        <span class="conversation-name">Admin Support</span>
17967                                        <span class="conversation-time">10:30</span>
17968                                    </div>
17969                                    <div class="conversation-preview">
17970                                        <p class="conversation-message">Terima kasih telah menghubungi kami...</p>
17971                                        <span class="conversation-unread">2</span>
17972                                    </div>
17973                                </div>
17974                            </div>
17975                            -->
17976                        </div>
17977                        <button id="chat-new-btn" class="chat-new-btn">
17978                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
17979                                <line x1="12" y1="5" x2="12" y2="19"></line>
17980                                <line x1="5" y1="12" x2="19" y2="12"></line>
17981                            </svg>
17982                            Percakapan Baru
17983                        </button>
17984                    </div>
17985                    
17986                    <!-- Chat Messages View -->
17987                    <div id="chat-messages-view" class="chat-view" style="display: none;">
17988                        <div id="chat-messages-container" class="chat-messages-container">
17989                            <div class="chat-loading">Memuat pesan...</div>
17990                            <!-- Message Structure (rendered by JS):
17991                            <div class="chat-message"> (or .chat-message.mine)
17992                                <div class="chat-message-bubble">
17993                                    <div class="message-sender">Admin Support</div> (only for non-mine)
17994                                    <div class="message-text">Halo, ada yang bisa dibantu?</div>
17995                                    <div class="message-meta">
17996                                        <span class="message-time">10:30</span>
17997                                        <svg class="message-status">...</svg> (only for mine)
17998                                    </div>
17999                                </div>
18000                            </div>
18001                            -->
18002                        </div>
18003                        
18004                        <form id="chat-message-form" class="chat-message-form">
18005                            <input type="hidden" id="chat-current-conversation-id" value="">
18006                            <textarea 
18007                                id="chat-message-input" 
18008                                class="chat-message-input" 
18009                                placeholder="Ketik pesan..."
18010                                rows="1"
18011                                maxlength="5000"
18012                            ></textarea>
18013                            <button type="submit" class="chat-send-btn" aria-label="Send message">
18014                                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
18015                                    <line x1="22" y1="2" x2="11" y2="13"></line>
18016                                    <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
18017                                </svg>
18018                            </button>
18019                        </form>
18020                    </div>
18021                    
18022                    <!-- New Chat View -->
18023                    <div id="chat-new-view" class="chat-view" style="display: none;">
18024                        <div class="chat-messages-header">
18025                            <button id="chat-new-back-btn" class="chat-back-btn" aria-label="Back to list">
18026                                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
18027                                    <path d="M19 12H5M12 19l-7-7 7-7"/>
18028                                </svg>
18029                            </button>
18030                            <div class="chat-conversation-title">Percakapan Baru</div>
18031                        </div>
18032                        
18033                        <div class="chat-new-form">
18034                            <form id="chat-start-form">
18035                                <div class="form-group">
18036                                    <label for="chat-new-subject">Subjek</label>
18037                                    <input 
18038                                        type="text" 
18039                                        id="chat-new-subject" 
18040                                        class="form-control" 
18041                                        placeholder="Bantuan Pelanggan"
18042                                        maxlength="255"
18043                                    >
18044                                </div>
18045                                <div class="form-group">
18046                                    <label for="chat-new-message">Pesan</label>
18047                                    <textarea 
18048                                        id="chat-new-message" 
18049                                        class="form-control" 
18050                                        rows="4" 
18051                                        placeholder="Tulis pertanyaan Anda..."
18052                                        required
18053                                        maxlength="5000"
18054                                    ></textarea>
18055                                </div>
18056                                <button type="submit" class="btn btn-primary btn-block">Kirim</button>
18057                            </form>
18058                        </div>
18059                    </div>
18060                </div>
18061            </div>
18062        </div>
18063        
18064        <!-- Login Required Modal -->
18065        <div class="modal-overlay" id="chatLoginModal">
18066            <div class="modal-dialog">
18067                <div class="modal-header">
18068                    <h3 class="modal-title">
18069                        <div class="modal-icon info">
18070                            <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18071                                <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"/>
18072                            </svg>
18073                        </div>
18074                        Login Diperlukan
18075                    </h3>
18076                </div>
18077                <div class="modal-body">
18078                    <p class="modal-message">
18078Anda harus login terlebih dahulu untuk menggunakan fitur chat bantuan pelanggan.</p>
18079                    <div class="modal-actions">
18080                        <button class="modal-btn modal-btn-secondary" id="chatLoginCancel">Batal</button>
18081                        <button class="modal-btn modal-btn-primary" id="chatLoginConfirm">Login Sekarang</button>
18082                    </div>
18083                </div>
18084            </div>
18085        </div>
18086        
18086<script src="https://plazait.co.id/assets/js/chat-widget.js" nonce="se3iyzEHMc9FQEB4wSyd0w=="></script>
18086<script data-inlined="chat-notification.js">
18087/**
18088 * Customer Chat Push Notification Manager
18089 * Handles browser push notification subscription for customers
18090 * Version: 1.0.0
18091 * Date: 2026-01-19
18092 */
18093
18094const ChatNotification = {
18095    // State
18096    status: 'not-supported', // not-supported, not-subscribed, subscribed, blocked
18097    subscription: null,
18098    vapidPublicKey: null, // Will be loaded from server
18099    
18100    /**
18101     * Initialize notification manager
18102     */
18103    async init() {
18104        // Check if user is logged in (only for logged-in users)
18105        if (!this.isUserLoggedIn()) {
18106            this.updateUI('not-supported');
18107            return;
18108        }
18109        
18110        // Check browser support
18111        if (!this.isSupported()) {
18112            this.updateUI('not-supported');
18113            return;
18114        }
18115        
18116        // Load VAPID key from server
18117        await this.loadVapidKey();
18118        
18119        if (!this.vapidPublicKey) {
18120            this.updateUI('not-supported');
18121            return;
18122        }
18123        
18124        // Register service worker if needed
18125        await this.registerServiceWorker();
18126        
18127        // Check current permission
18128        const permission = Notification.permission;
18129        
18130        if (permission === 'denied') {
18131            this.updateUI('blocked');
18132        } else if (permission === 'granted') {
18133            // Check if already subscribed
18134            const isSubscribed = await this.checkSubscriptionStatus();
18135            this.updateUI(isSubscribed ? 'subscribed' : 'not-subscribed');
18136        } else {
18137            // Default: not subscribed yet
18138            this.updateUI('not-subscribed');
18139            this.showInfoBar();
18140        }
18141    },
18142    
18143    /**
18144     * Check if user is logged in
18145     */
18146    isUserLoggedIn() {
18147        const metaTag = document.querySelector('meta[name="user-logged-in"]');
18148        return metaTag ? metaTag.getAttribute('content') === 'true' : false;
18149    },
18150    
18151    /**
18152     * Check if browser supports push notifications
18153     */
18154    isSupported() {
18155        return (
18156            'serviceWorker' in navigator &&
18157            'PushManager' in window &&
18158            'Notification' in window
18159        );
18160    },
18161    
18162    /**
18163     * Load VAPID public key from server
18164     */
18165    async loadVapidKey() {
18166        try {
18167            const response = await fetch('/api/chat/notifications/vapid_key', {
18168                method: 'GET',
18169                headers: {
18170                    'Accept': 'application/json'
18171                }
18172            });
18173            
18174            if (!response.ok) {
18175                return false;
18176            }
18177            
18178            const data = await response.json();
18179            if (data.success && data.data?.public_key) {
18180                this.vapidPublicKey = data.data.public_key;
18181                return true;
18182            }
18183            
18184            return false;
18185            
18186        } catch (error) {
18187            console.error('[ChatNotification] Error loading VAPID key:', error);
18188            return false;
18189        }
18190    },
18191    
18192    /**
18193     * Register service worker
18194     */
18195    async registerServiceWorker() {
18196        if (!('serviceWorker' in navigator)) return null;
18197        
18198        try {
18199            const registration = await navigator.serviceWorker.register('/sw-push.js', {
18200                scope: '/'
18201            });
18202            return registration;
18203        } catch (error) {
18204            return null;
18205        }
18206    },
18207    
18208    /**
18209     * Check subscription status from server
18210     */
18211    async checkSubscriptionStatus() {
18212        try {
18213            const response = await fetch('/api/chat/notifications/status', {
18214                method: 'GET',
18215                credentials: 'include',
18216                headers: {
18217                    'Accept': 'application/json'
18218                }
18219            });
18220            
18221            if (!response.ok) {
18222                return false;
18223            }
18224            
18225            const data = await response.json();
18226            return data.data?.subscribed || false;
18227            
18228        } catch (error) {
18229            console.error('[ChatNotification] Error checking subscription:', error);
18230            return false;
18231        }
18232    },
18233    
18234    /**
18235     * Update UI based on status
18236     */
18237    updateUI(status) {
18238        this.status = status;
18239        const btn = document.getElementById('chatNotifToggle');
18240        
18241        if (!btn) {
18242            // Retry after a short delay (button might not be in DOM yet)
18243            setTimeout(() => this.updateUI(status), 100);
18244            return;
18245        }
18246        
18247        btn.setAttribute('data-status', status);
18248        
18249        // Show/hide button based on support
18250        if (status === 'not-supported') {
18251            btn.style.display = 'none';
18252        } else {
18253            btn.style.display = 'block';
18254        }
18255        
18256        // Update SVG icon (bell vs bell-slash)
18257        const svg = btn.querySelector('svg');
18258        if (svg) {
18259            if (status === 'subscribed') {
18260                // Bell icon (active)
18261                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"/>';
18262            } else {
18263                // Bell slash icon (inactive/blocked)
18264                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"/>';
18265            }
18266        }
18267        
18268        // Update tooltip
18269        const tooltips = {
18270            'not-subscribed': 'Klik untuk aktifkan notifikasi',
18271            'subscribed': 'Notifikasi aktif',
18272            'blocked': 'Notifikasi diblokir'
18273        };
18274        
18275        if (tooltips[status]) {
18276            btn.title = tooltips[status];
18277        }
18278        
18279        // Hide info bar if subscribed or blocked
18280        if (status === 'subscribed' || status === 'blocked') {
18281            this.hideInfoBar();
18282        }
18283    },
18284    
18285    /**
18286     * Show info bar (if not dismissed)
18287     */
18288    showInfoBar() {
18289        if (localStorage.getItem('chat_notif_info_dismissed')) {
18290            return;
18291        }
18292        
18293        const infoBar = document.getElementById('chatNotifInfoBar');
18294        const popupBody = document.getElementById('chat-popup')?.querySelector('.chat-popup-body');
18295        
18296        if (infoBar) {
18297            infoBar.style.display = 'flex';
18298
18299            // Bind dismiss button here so it's always attached when bar is visible
18300            const btn = infoBar.querySelector('#chatNotifDismissBtn');
18301            if (btn && !btn._dismissBound) {
18302                btn._dismissBound = true;
18303                btn.addEventListener('click', () => {
18304                    infoBar.style.display = 'none';
18305                    localStorage.setItem('chat_notif_info_dismissed', '1');
18306                    if (popupBody) popupBody.classList.remove('has-info-bar');
18307                });
18308            }
18309        }
18310        
18311        if (popupBody) {
18312            popupBody.classList.add('has-info-bar');
18313        }
18314    },
18315    
18316    /**
18317     * Hide info bar
18318     */
18319    hideInfoBar() {
18320        const infoBar = document.getElementById('chatNotifInfoBar');
18321        const popupBody = document.getElementById('chat-popup')?.querySelector('.chat-popup-body');
18322        
18323        if (infoBar) {
18324            infoBar.style.display = 'none';
18325        }
18326        
18327        if (popupBody) {
18328            popupBody.classList.remove('has-info-bar');
18329        }
18330    },
18331    
18332    /**
18333     * Dismiss info bar permanently
18334     */
18335    dismissInfo() {
18336        this.hideInfoBar();
18337        localStorage.setItem('chat_notif_info_dismissed', '1');
18338    },
18339    
18340    /**
18341     * Handle bell icon click
18342     */
18343    async handleToggleClick() {
18344        if (this.status === 'not-subscribed') {
18345            await this.subscribe();
18346        } else if (this.status === 'subscribed') {
18347            await this.unsubscribe();
18348        } else if (this.status === 'blocked') {
18349            this.showUnblockHelp();
18350        }
18351    },
18352    
18353    /**
18354     * Subscribe to push notifications
18355     */
18356    async subscribe() {
18357        try {
18358            // Request permission
18359            const permission = await Notification.requestPermission();
18360            
18361            if (permission === 'denied') {
18362                this.updateUI('blocked');
18363                this.showToast('Notifikasi diblokir', 'error');
18364                return;
18365            }
18366            
18367            if (permission !== 'granted') {
18368                return;
18369            }
18370            
18371            // Get service worker registration
18372            const registration = await navigator.serviceWorker.ready;
18373            
18374            // Subscribe to push
18375            const subscription = await registration.pushManager.subscribe({
18376                userVisibleOnly: true,
18377                applicationServerKey: this.urlBase64ToUint8Array(this.vapidPublicKey)
18378            });
18379            
18380            // Send subscription to server
18381            const response = await fetch('/api/chat/notifications/subscribe', {
18382                method: 'POST',
18383                credentials: 'include',
18384                headers: {
18385                    'Content-Type': 'application/json',
18386                    'Accept': 'application/json'
18387                },
18388                body: JSON.stringify(subscription.toJSON())
18389            });
18390            
18391            if (!response.ok) {
18392                throw new Error('Failed to save subscription');
18393            }
18394            
18395            const result = await response.json();
18396            
18397            this.subscription = subscription;
18398            this.updateUI('subscribed');
18399            this.hideInfoBar();
18400            this.showToast('Notifikasi berhasil diaktifkan!', 'success');
18401            
18402            // Send test notification
18403            setTimeout(() => this.sendTestNotification(), 1000);
18404            
18405        } catch (error) {
18406            console.error('[ChatNotification] Subscribe error:', error);
18407            this.showToast('Gagal mengaktifkan notifikasi', 'error');
18408        }
18409    },
18410    
18411    /**
18412     * Unsubscribe from push notifications
18413     */
18414    async unsubscribe() {
18415        try {
18416            // Confirm first
18417            if (!confirm('Nonaktifkan notifikasi chat?')) {
18418                return;
18419            }
18420            
18421            // Get current subscription
18422            const registration = await navigator.serviceWorker.ready;
18423            const subscription = await registration.pushManager.getSubscription();
18424            
18425            if (subscription) {
18426                // Unsubscribe from browser
18427                await subscription.unsubscribe();
18428                
18429                // Tell server
18430                await fetch('/api/chat/notifications/unsubscribe', {
18431                    method: 'POST',
18432                    credentials: 'include',
18433                    headers: {
18434                        'Content-Type': 'application/json',
18435                        'Accept': 'application/json'
18436                    },
18437                    body: JSON.stringify(subscription.toJSON())
18438                });
18439            }
18440            
18441            this.subscription = null;
18442            this.updateUI('not-subscribed');
18443            this.showInfoBar();
18444            this.showToast('Notifikasi dinonaktifkan', 'info');
18445            
18446        } catch (error) {
18447            console.error('[ChatNotification] Unsubscribe error:', error);
18448            this.showToast('Gagal menonaktifkan notifikasi', 'error');
18449        }
18450    },
18451    
18452    /**
18453     * Send test notification
18454     */
18455    async sendTestNotification() {
18456        try {
18457            const response = await fetch('/api/chat/notifications/send_test', {
18458                method: 'POST',
18459                credentials: 'include',
18460                headers: {
18461                    'Accept': 'application/json'
18462                }
18463            });
18464            
18465            if (response.ok) {
18466                // Test notification sent successfully
18467            }
18468        } catch (error) {
18469            // Test notification failed silently
18470        }
18471    },
18472    
18473    /**
18474     * Show unblock help modal/message
18475     */
18476    showUnblockHelp() {
18477        const message = `
18478            Notifikasi diblokir oleh browser.\n\n
18479            Untuk mengaktifkan:\n
18480            1. Klik ikon gembok/info di address bar\n
18481            2. Cari "Notifications" atau "Notifikasi"\n
18482            3. Ubah menjadi "Allow" atau "Izinkan"\n
18483            4. Refresh halaman ini
18484        `.trim().replace(/\s+/g, ' ');
18485        
18486        alert(message);
18487    },
18488    
18489    /**
18490     * Show toast notification
18491     */
18492    showToast(message, type = 'info') {
18493        // Simple toast implementation
18494        const toast = document.createElement('div');
18495        toast.className = `chat-toast chat-toast-${type}`;
18496        toast.textContent = message;
18497        toast.style.cssText = `
18498            position: fixed;
18499            bottom: 80px;
18500            right: 20px;
18501            background: ${type === 'success' ? '#4caf50' : type === 'error' ? '#f44336' : '#2196f3'};
18502            color: white;
18503            padding: 12px 20px;
18504            border-radius: 4px;
18505            box-shadow: 0 2px 8px rgba(0,0,0,0.2);
18506            z-index: 10000;
18507            animation: slideInUp 0.3s ease;
18508        `;
18509        
18510        document.body.appendChild(toast);
18511        
18512        setTimeout(() => {
18513            toast.style.animation = 'slideOutDown 0.3s ease';
18514            setTimeout(() => toast.remove(), 300);
18515        }, 3000);
18516    },
18517    
18518    /**
18519     * Convert VAPID key to Uint8Array
18520     */
18521    urlBase64ToUint8Array(base64String) {
18522        const padding = '='.repeat((4 - base64String.length % 4) % 4);
18523        const base64 = (base64String + padding)
18524            .replace(/\-/g, '+')
18525            .replace(/_/g, '/');
18526        
18527        const rawData = window.atob(base64);
18528        const outputArray = new Uint8Array(rawData.length);
18529        
18530        for (let i = 0; i < rawData.length; ++i) {
18531            outputArray[i] = rawData.charCodeAt(i);
18532        }
18533        return outputArray;
18534    },
18535    
18536    /**
18537     * Check if push notification is currently active
18538     * @returns {Promise<boolean>}
18539     */
18540    async isPushActive() {
18541        try {
18542            // Check browser subscription
18543            if (!('serviceWorker' in navigator)) return false;
18544            
18545            const registration = await navigator.serviceWorker.getRegistration();
18546            if (!registration) return false;
18547            
18548            const subscription = await registration.pushManager.getSubscription();
18549            if (!subscription) return false;
18550            
18551            // Also check server status
18552            const serverStatus = await this.checkSubscriptionStatus();
18553            
18554            return serverStatus;
18555        } catch (error) {
18556            return false;
18557        }
18558    },
18559    
18560    /**
18561     * Show logout confirmation if push notification is active
18562     * Call this before logout action
18563     * @returns {Promise<boolean>} true if user confirms logout, false if cancelled
18564     */
18565    async confirmLogoutWithPushNotification() {
18566        const isActive = await this.isPushActive();
18567        
18568        if (!isActive) {
18569            // No active push, proceed with logout
18570            return true;
18571        }
18572        
18573        // Show modal confirmation using modal.css
18574        return new Promise((resolve) => {
18575            // Create modal HTML
18576            const modalHTML = `
18577                <div class="modal-overlay" id="logoutConfirmModal">
18578                    <div class="modal-dialog">
18579                        <div class="modal-header">
18580                            <h3 class="modal-title">
18581                                <span class="modal-icon warning">⚠️</span>
18582                                Konfirmasi Logout
18583                            </h3>
18584                        </div>
18585                        <div class="modal-body">
18586                            <p class="modal-message">
18587                                Anda memiliki <strong>notifikasi push yang aktif</strong>.<br><br>
18588                                Setelah logout, Anda tidak akan menerima notifikasi chat lagi di perangkat ini.<br><br>
18589                                Apakah Anda yakin ingin melanjutkan logout?
18590                            </p>
18591                        </div>
18592                        <div class="modal-actions" style="padding: 0 20px 20px;">
18593                            <button class="modal-btn modal-btn-secondary" id="logoutCancelBtn">
18594                                Batal
18595                            </button>
18596                            <button class="modal-btn modal-btn-danger" id="logoutConfirmBtn">
18597                                Lanjutkan Logout
18598                            </button>
18599                        </div>
18600                    </div>
18601                </div>
18602            `;
18603            
18604            // Append to body
18605            const modalContainer = document.createElement('div');
18606            modalContainer.innerHTML = modalHTML;
18607            document.body.appendChild(modalContainer.firstElementChild);
18608            
18609            const modal = document.getElementById('logoutConfirmModal');
18610            const confirmBtn = document.getElementById('logoutConfirmBtn');
18611            const cancelBtn = document.getElementById('logoutCancelBtn');
18612            
18613            // Show modal with animation
18614            requestAnimationFrame(() => {
18615                modal.classList.add('show');
18616            });
18617            
18618            // Handle confirm
18619            const handleConfirm = () => {
18620                cleanup();
18621                resolve(true);
18622            };
18623            
18624            // Handle cancel
18625            const handleCancel = () => {
18626                cleanup();
18627                resolve(false);
18628            };
18629            
18630            // Cleanup function
18631            const cleanup = () => {
18632                modal.classList.remove('show');
18633                setTimeout(() => {
18634                    modal.remove();
18635                }, 200);
18636                confirmBtn.removeEventListener('click', handleConfirm);
18637                cancelBtn.removeEventListener('click', handleCancel);
18638                modal.removeEventListener('click', handleBackdropClick);
18639            };
18640            
18641            // Handle backdrop click
18642            const handleBackdropClick = (e) => {
18643                if (e.target === modal) {
18644                    handleCancel();
18645                }
18646            };
18647            
18648            // Add event listeners
18649            confirmBtn.addEventListener('click', handleConfirm);
18650            cancelBtn.addEventListener('click', handleCancel);
18651            modal.addEventListener('click', handleBackdropClick);
18652        });
18653    }
18654};
18655
18656// Add CSS animations
18657const chatNotifStyle = document.createElement('style');
18658chatNotifStyle.textContent = `
18659    @keyframes slideInUp {
18660        from {
18661            transform: translateY(100%);
18662            opacity: 0;
18663        }
18664        to {
18665            transform: translateY(0);
18666            opacity: 1;
18667        }
18668    }
18669    
18670    @keyframes slideOutDown {
18671        from {
18672            transform: translateY(0);
18673            opacity: 1;
18674        }
18675        to {
18676            transform: translateY(100%);
18677            opacity: 0;
18678        }
18679    }
18680`;
18681document.head.appendChild(chatNotifStyle);
18682
18683// Auto-initialize when DOM is ready
18684if (document.readyState === 'loading') {
18685    document.addEventListener('DOMContentLoaded', () => ChatNotification.init());
18686} else {
18687    ChatNotification.init();
18688}
18689
18690// Expose to window for access from other scripts
18691window.ChatNotification = ChatNotification;
18692
18693</script>
18693  
18694    <!-- Footer Collapse Toggle Script -->
18695  
18695<script nonce="se3iyzEHMc9FQEB4wSyd0w==">
18696  document.addEventListener('DOMContentLoaded', function() {
18697    const ftTop = document.getElementById('ftTop');
18698    const expandBtn = document.getElementById('ftExpandBtn');
18699    
18700    if (ftTop && expandBtn) {
18701      const expandText = expandBtn.querySelector('.expand-text');
18702      
18703      expandBtn.addEventListener('click', function() {
18704        ftTop.classList.toggle('expanded');
18705        const isExpanded = ftTop.classList.contains('expanded');
18706        if (expandText) {
18707          expandText.textContent = isExpanded ? 'Sembunyikan' : 'Selengkapnya';
18708        }
18709      });
18710    }
18711  });
18712  </script>
18712
18713  </footer>
18714
18715<!-- Compare Sidebar Overlay -->
18716<div class="compare-overlay" id="compareOverlay"></div>
18717
18718<!-- Floating Compare Badge (Sticky) -->
18719<button class="compare-floating-badge" id="compareFloatingBadge" aria-label="Lihat produk yang dibandingkan" style="display:none;">
18720  <svg class="cfb-icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
18721    <path d="M9.01 14H2v2h7.01v3L13 15l-3.99-4v3zm5.98-1v-3H22V8h-7.01V5L11 9l3.99 4z"/>
18722  </svg>
18723  <span class="cfb-text">Bandingkan</span>
18724  <span class="cfb-count" id="cfbCount">0</span>
18725</button>
18726
18727<!-- AI Comparison Modal -->
18728<div class="ai-compare-modal" id="aiCompareModal">
18729  <div class="modal-backdrop"></div>
18730  <div class="modal-container">
18731    <div class="modal-header">
18732      <h3 class="modal-title">
18733        <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
18734          <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"/>
18735        </svg>
18736        Analisis AI: Perbandingan Produk
18737      </h3>
18738      <button class="modal-close" id="aiCompareModalClose" aria-label="Tutup">
18739        <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18740          <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"/>
18741        </svg>
18742      </button>
18743    </div>
18744    
18745    <div class="modal-content">
18746      <!-- Loading State -->
18747      <div class="ai-loading" id="aiLoading">
18748        <div class="loading-animation">
18749          <div class="typing-indicator">
18750            <span></span>
18751            <span></span>
18752            <span></span>
18753          </div>
18754          <p>AI sedang menganalisis produk...</p>
18755          <small>Proses ini membutuhkan waktu 10-30 detik</small>
18756        </div>
18757      </div>
18758      
18759      <!-- Error State -->
18760      <div class="ai-error" id="aiError" hidden>
18761        <div class="error-icon">
18762          <svg width="48" height="48" viewBox="0 0 24 24" fill="#ef4444">
18763            <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"/>
18764          </svg>
18765        </div>
18766        <h4>Analisis AI Gagal</h4>
18767        <p id="aiErrorMessage">Terjadi kesalahan saat memproses analisis.</p>
18768        <button class="btn btn-outline-primary" id="aiRetry">Coba Lagi</button>
18769      </div>
18770      
18771      <!-- Analysis Results -->
18772      <div class="ai-results" id="aiResults" hidden>
18773        <!-- Summary Section -->
18774        <div class="ai-summary-card">
18775          <h4>
18776            <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18777              <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"/>
18778            </svg>
18779            Ringkasan Analisis
18780          </h4>
18781          <p id="aiSummary" class="summary-text"></p>
18782          <div class="winner-badge" id="winnerBadge">
18783            <strong>Rekomendasi Terbaik:</strong> <span id="winnerProduct"></span>
18784            <small id="winnerReason"></small>
18785          </div>
18786        </div>
18787        
18788        <!-- Comparison Table -->
18789        <div class="ai-comparison-table">
18790          <h4>
18791            <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18792              <path d="M3,3H21V5H3V3M3,7H21V9H3V7M3,11H21V13H3V11M3,15H21V17H3V15M3,19H21V21H3V19Z"/>
18793            </svg>
18794            Perbandingan Detail
18795          </h4>
18796          <div id="comparisonTable" class="comparison-grid"></div>
18797        </div>
18798        
18799        <!-- Detailed Analysis -->
18800        <div class="ai-detailed-analysis">
18801          <h4>
18802            <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18803              <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"/>
18804            </svg>
18805            Analisis Mendalam
18806          </h4>
18807          <div id="detailedAnalysis" class="products-analysis"></div>
18808        </div>
18809        
18810        <!-- Recommendations -->
18811        <div class="ai-recommendations">
18812          <h4>
18813            <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18814              <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"/>
18815            </svg>
18816            Rekomendasi Berdasarkan Kebutuhan
18817          </h4>
18818          <div class="recommendations-grid">
18819            <div class="recommendation-card">
18820              <h5>💰 Budget Conscious</h5>
18821              <p id="budgetRecommendation"></p>
18822            </div>
18823            <div class="recommendation-card">
18824              <h5>⚡ Performance Focused</h5>
18825              <p id="performanceRecommendation"></p>
18826            </div>
18827            <div class="recommendation-card">
18828              <h5>⚖️ Balanced Choice</h5>
18829              <p id="balancedRecommendation"></p>
18830            </div>
18831          </div>
18832        </div>
18833        
18834        <!-- Buying Tips -->
18835        <div class="ai-tips">
18836          <h4>
18837            <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18838              <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
18838M5,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"/>
18839            </svg>
18840            Tips Pembelian
18841          </h4>
18842          <ul id="buyingTips" class="tips-list"></ul>
18843        </div>
18844      </div>
18845    </div>
18846  </div>
18847</div>
18848
18849<!-- Cart Login Modal -->
18850<div class="modal-overlay" id="cartLoginModal">
18851  <div class="modal-dialog">
18852    <div class="modal-header">
18853      <h3 class="modal-title">
18854        <div class="modal-icon info">
18855          <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18856            <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"/>
18857          </svg>
18858        </div>
18859        Login untuk Melanjutkan
18860      </h3>
18861    </div>
18862    <div class="modal-body">
18863      <p class="modal-message" style="margin-bottom: 16px;">Silakan login atau daftar untuk mengakses keranjang belanja dan menyelesaikan pesanan Anda.</p>
18864      <div style="background: #f8fafc; border-radius: 8px; padding: 12px; margin-bottom: 20px;">
18865        <p style="font-size: 14px; color: #646947; margin: 0; line-height: 1.6;">
18866          <strong style="color: #2a2a0f;">Keuntungan dengan akun:</strong><br>
18867          ✓ Simpan keranjang belanja Anda<br>
18868          ✓ Lacak status pesanan secara real-time<br>
18869          ✓ Checkout lebih cepat dengan data tersimpan<br>
18870          ✓ Dapatkan notifikasi promo & diskon eksklusif
18871        </p>
18872      </div>
18873      <div class="modal-actions">
18874        <button class="modal-btn modal-btn-secondary" id="cartLoginCancel">Batal</button>
18875        <button class="modal-btn modal-btn-primary" id="cartLoginConfirm" style="background: #ff6b35; border-color: #ff6b35;">Login</button>
18876        <button class="modal-btn modal-btn-primary" id="cartRegisterBtn" style="background: #f7931e; border-color: #f7931e;">Daftar</button>
18877      </div>
18878    </div>
18879  </div>
18880</div>
18881
18882
18883<script data-inlined="app.js">
18884(function initLiveSearch(){
18885  function setup() {
18886    const searchInput = document.getElementById('searchInput');
18887    const suggest = document.getElementById('searchSuggest');
18888    const searchBtn = document.getElementById('searchBtn');
18889    const aiToggleBtn = document.getElementById('aiToggleBtn');
18890    
18891    if (!searchInput || !suggest || !aiToggleBtn) {
18892      console.log('[LiveSearch] Elemen belum ada, retry...');
18893      return false;
18894    }
18895
18896    let debounceTimer = null;
18897    let lastQuery = '';
18898    let currentIndex = -1;
18899    let currentAbortController = null;
18900    let isAIModeEnabled = false; // AI mode OFF by default
18901    let isAIProcessing = false; // Flag to prevent closing suggest box during AI processing
18902    
18903    // Trending cache
18904    let trendingCache = null;
18905    let trendingLoading = false;
18906    
18907    // Trending products cache
18908    let trendingProductsCache = null;
18909    let trendingProductsLoading = false;
18910    
18911    const MIN_LEN = 2;
18912    const DEBOUNCE = 300;
18913    const AI_TIMEOUT = 15000; // 15 seconds timeout for AI endpoint
18914
18915    // Load AI mode preference from localStorage
18916    const savedAIMode = localStorage.getItem('plazait_ai_search_mode');
18917    if (savedAIMode === 'enabled') {
18918      isAIModeEnabled = false;
18919      aiToggleBtn.classList.remove('active');
18920    }
18921    
18922    // Update placeholder based on AI mode
18923    function updatePlaceholder() {
18924      if (isAIModeEnabled) {
18925        searchInput.placeholder = '🤖 Coba: "laptop HP core i3 untuk kerja" atau "tinta epson l3210"';
18926      } else {
18927        // Static placeholder for typing animation
18928        searchInput.placeholder = '';
18929      }
18930    }
18931    
18932    // Typing animation for placeholder
18933    let typingTimeout = null;
18934    let currentProductIndex = 0;
18935    let currentCharIndex = 0;
18936    let isTyping = false;
18937    let isDeleting = false;
18938    
18939    const popularProducts = [
18940      'Oppo A6',
18941      'Asus Ryzen 3',
18942      'Starlink V4',
18943      'HP Victus',
18944      'Lenovo RTX 5060',
18945      'ROG Strix',
18946      'iPhone 13',
18947      'Xpad 20',
18948      'Xiaomi 14',
18949      'Epson L3210',
18950      'Realme C85',
18951      'Brother T720',
18952      'Mouse Logitech'
18953    ];
18954    
18955    function typeWriter() {
18956      if (isAIModeEnabled || document.activeElement === searchInput) {
18957        return; // Skip typing if AI mode is on or input is focused
18958      }
18959      
18960      const currentProduct = popularProducts[currentProductIndex];
18961      
18962      if (!isDeleting) {
18963        // Typing forward
18964        if (currentCharIndex < currentProduct.length) {
18965          searchInput.placeholder = currentProduct.substring(0, currentCharIndex + 1);
18966          currentCharIndex++;
18967          typingTimeout = setTimeout(typeWriter, 100); // Typing speed
18968        } else {
18969          // Finished typing, wait before deleting
18970          typingTimeout = setTimeout(() => {
18971            isDeleting = true;
18972            typeWriter();
18973          }, 2000); // Pause before deleting
18974        }
18975      } else {
18976        // Deleting backward
18977        if (currentCharIndex > 0) {
18978          searchInput.placeholder = currentProduct.substring(0, currentCharIndex - 1);
18979          currentCharIndex--;
18980          typingTimeout = setTimeout(typeWriter, 50); // Deleting speed (faster)
18981        } else {
18982          // Finished deleting, move to next product
18983          isDeleting = false;
18984          currentProductIndex = (currentProductIndex + 1) % popularProducts.length;
18985          typingTimeout = setTimeout(typeWriter, 500); // Pause before typing next
18986        }
18987      }
18988    }
18989    
18990    function startTypingAnimation() {
18991      if (!isAIModeEnabled && !typingTimeout) {
18992        currentCharIndex = 0;
18993        isDeleting = false;
18994        typeWriter();
18995      }
18996    }
18997    
18998    function stopTypingAnimation() {
18999      if (typingTimeout) {
19000        clearTimeout(typingTimeout);
19001        typingTimeout = null;
19002      }
19003    }
19004    
19005    // Set initial placeholder
19006    updatePlaceholder();
19007    
19008    // Start typing animation for normal mode
19009    if (!isAIModeEnabled) {
19010      startTypingAnimation();
19011    }
19012
19013    function esc(str){
19014      if (str === null || str === undefined) return '';
19015      return String(str)
19016        .replace(/&/g,'&amp;')
19017        .replace(/</g,'&lt;')
19018        .replace(/>/g,'&gt;')
19019        .replace(/"/g,'&quot;')
19020        .replace(/'/g,'&#39;');
19021    }
19022
19023    function clearSuggest(){
19024      suggest.innerHTML = '';
19025      suggest.hidden = true;
19026      suggest.classList.remove('open', 'trending-mode');
19027      currentIndex = -1;
19028    }
19029
19030    // ==================== TRENDING FUNCTIONS ====================
19031    
19032    async function fetchTrending() {
19033      if (trendingCache) return trendingCache;
19034      if (trendingLoading) return null;
19035      
19036      trendingLoading = true;
19037      
19038      try {
19039        const res = await fetch('/api/trending_searches.php?timeframe=7d&limit=8');
19040        const data = await res.json();
19041        
19042        if (data.ok && Array.isArray(data.data)) {
19043          trendingCache = data.data;
19044          console.log('[Trending] Loaded', trendingCache.length, 'items');
19045          return trendingCache;
19046        }
19047      } catch (err) {
19048        console.error('[Trending] Fetch error:', err);
19049      } finally {
19050        trendingLoading = false;
19051      }
19052      
19053      return null;
19054    }
19055    
19056    async function fetchTrendingProducts() {
19057      if (trendingProductsCache) return trendingProductsCache;
19058      if (trendingProductsLoading) return null;
19059      
19060      trendingProductsLoading = true;
19061      
19062      try {
19063        const res = await fetch('/api/trending_products.php?timeframe=7d&limit=6');
19064        const data = await res.json();
19065        
19066        if (data.ok && Array.isArray(data.data)) {
19067          trendingProductsCache = data.data;
19068          console.log('[Trending Products] Loaded', trendingProductsCache.length, 'items');
19069          return trendingProductsCache;
19070        }
19071      } catch (err) {
19072        console.error('[Trending Products] Fetch error:', err);
19073      } finally {
19074        trendingProductsLoading = false;
19075      }
19076      
19077      return null;
19078    }
19079    
19080    function renderTrendingSuggestions(trends, products) {
19081      if ((!trends || trends.length === 0) && (!products || products.length === 0)) {
19082        clearSuggest();
19083        return;
19084      }
19085      
19086      let html = `<div class="trending-container">`;
19087      
19088      // Left column: Trending searches
19089      if (trends && trends.length > 0) {
19090        html += `
19091          <div class="trending-column trending-searches">
19092            <div class="trending-header">
19093              <span class="trending-icon">🔥</span>
19094              <span class="trending-title">Pencarian Trending</span>
19095            </div>
19096        `;
19097        
19098        html += trends.map((t, idx) => {
19099          const categoryHint = t.category_hint ? `<span class="trending-category">${esc(t.category_hint)}</span>` : '';
19100          return `
19101            <div class="trending-item" data-query="${esc(t.query)}" data-index="${idx}">
19102              <span class="trending-rank">
19103                <img src="/assets/img/uptrend.image" alt="trending" width="16" height="16" style="vertical-align: middle;">
19104              </span>
19105              <span class="trending-query">${esc(t.query)}</span>
19106              ${categoryHint}
19107            </div>
19108          `;
19109        }).join('');
19110        
19111        html += `</div>`;
19112      }
19113      
19114      // Right column: Trending products
19115      if (products && products.length > 0) {
19116        html += `
19117          <div class="trending-column trending-products">
19118            <div class="trending-header">
19119              <span class="trending-icon">⭐</span>
19120              <span class="trending-title">Produk Populer</span>
19121            </div>
19122        `;
19123        
19124        html += products.map((p, idx) => {
19125          return `
19126            <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}">
19127              <img src="${esc(p.image)}" alt="${esc(p.name)}" class="trending-product-thumb">
19128              <div class="trending-product-info">
19129                <div class="trending-product-name">${esc(p.name)}</div>
19130                <div class="trending-product-price">${esc(p.price_label)}</div>
19131              </div>
19132            </div>
19133          `;
19134        }).join('');
19135        
19136        html += `</div>`;
19137      }
19138      
19139      html += `</div>`;
19140      
19141      suggest.innerHTML = html;
19142      suggest.hidden = false;
19143      suggest.classList.add('open', 'trending-mode');
19144      suggest.style.display = 'block';
19145      
19146      console.log('[Trending] Rendered', trends?.length || 0, 'searches and', products?.length || 0, 'products');
19147    }
19148    
19149    function trackSearchClick(query, productId) {
19150      fetch('/api/track_search_click.php', {
19151        method: 'POST',
19152        headers: { 'Content-Type': 'application/json' },
19153        body: JSON.stringify({
19154          query: query,
19155          product_id: productId,
19156          click_type: 'suggestion'
19157        })
19158      }).catch(() => {}); // silent fail
19159    }
19160    
19161    function trackTrendingClick(query) {
19162      fetch('/api/track_search_click.php', {
19163        method: 'POST',
19164        headers: { 'Content-Type': 'application/json' },
19165        body: JSON.stringify({
19166          query: query,
19167          click_type: 'trending'
19168        })
19169      }).catch(() => {});
19170    }
19171    
19172    function trackTrendingProductClick(productId, query) {
19173      fetch('/api/track_search_click.php', {
19174        method: 'POST',
19175        headers: { 'Content-Type': 'application/json' },
19176        body: JSON.stringify({
19177          query: query || 'trending_product',
19178          product_id: productId,
19179          click_type: 'trending_product'
19180        })
19181      }).catch(() => {});
19182    }
19183    
19184    function trackSearchSubmit(query) {
19185      fetch('/api/track_search_click.php', {
19186        method: 'POST',
19187        headers: { 'Content-Type': 'application/json' },
19188        body: JSON.stringify({
19189          query: query,
19190          click_type: 'submit'
19191        })
19192      }).catch(() => {});
19193    }
19194    
19195    function logFinalSearch(query) {
19196      // Log the final search to analytics (not during typing)
19197      fetch('/api/search_products.php?q=' + encodeURIComponent(query) + '&log=1', {
19198        headers: { 'Accept': 'application/json' }
19199      }).catch(() => {});
19200    }
19201
19202    // ==================== END TRENDING FUNCTIONS ====================
19203
19204    function clearSuggest(){
19205      suggest.innerHTML = '';
19206      suggest.hidden = true;
19207      suggest.classList.remove('open', 'trending-mode');
19208      currentIndex = -1;
19209    }
19210
19211    // AI Toggle Handler
19212    aiToggleBtn.addEventListener('click', (e) => {
19213      e.preventDefault();
19214      e.stopPropagation();
19215      
19216      isAIModeEnabled = !isAIModeEnabled;
19217      
19218      if (isAIModeEnabled) {
19219        aiToggleBtn.classList.add('active');
19220        localStorage.setItem('plazait_ai_search_mode', 'enabled');
19221        updatePlaceholder(); // Update placeholder text
19222        stopTypingAnimation(); // Stop typing animation in AI mode
19223        
19224        // Show AI prompt if there's a query
19225        const query = searchInput.value.trim();
19226        if (query.length >= MIN_LEN) {
19227          showAISearchPrompt(query);
19228        } else {
19229          showAIActivationMessage();
19230        }
19231      } else {
19232        aiToggleBtn.classList.remove('active');
19233        localStorage.setItem('plazait_ai_search_mode', 'disabled');
19234        updatePlaceholder(); // Update placeholder text
19235        startTypingAnimation(); // Start typing animation in normal mode
19236        clearSuggest();
19237        
19238        // Switch to normal search if there's a query
19239        const query = searchInput.value.trim();
19240        if (query.length >= MIN_LEN) {
19241          performSearch(query);
19242        }
19243      }
19244    });
19245
19246    function showAIActivationMessage() {
19247      suggest.innerHTML = `
19248        <div class="ai-progress-container">
19249          <div class="ai-progress-header">
19250            <div class="ai-progress-icon">✨</div>
19251            <div class="ai-progress-text">
19252              <div class="ai-progress-title">AI Search Mode Activated!</div>
19253              <div class="ai-progress-subtitle">Ketik query Anda untuk pencarian cerdas</div>
19254            </div>
19255          </div>
19256        </div>
19257      `;
19258      suggest.hidden = false;
19259      suggest.classList.add('open');
19260      suggest.style.display = 'block';
19261    }
19262
19263    function renderAIInsights(insights){
19264      if (!insights) return '';
19265      
19266      // Generate AI summary via backend endpoint
19267      const summary = insights.ai_summary || generateAISummary(insights);
19268      
19269      if (!summary) return '';
19270      
19271      return `
19272        <div class="ai-result-banner">
19273          <div class="ai-result-icon">✨</div>
19274          <div class="ai-result-text">
19275            <div class="ai-result-title">AI Assistant</div>
19276            <div class="ai-result-description">${summary}</div>
19277          </div>
19278        </div>
19279      `;
19280    }
19281    
19282    function generateAISummary(insights) {
19283      // Fallback client-side summary generation if backend doesn't provide ai_summary
19284      const productName = insights.product_name || '';
19285      const categoryPath = insights.category_path || '';
19286      const priceRange = insights.price_range || {};
19287      const intent = insights.intent || '';
19288      
19289      let summary = '';
19290      
19291      // Parse category for natural display
19292      let categoryDisplay = '';
19293      if (categoryPath) {
19294        const categoryParts = categoryPath.split('/');
19295        categoryDisplay = categoryParts[categoryParts.length - 1] || categoryParts[categoryParts.length - 2] || categoryPath;
19296      }
19297      
19298      // Intent-based opening
19299      if (intent === 'cek_stok') {
19300        summary = `Berikut ketersediaan stok untuk <strong>${esc(productName)}</strong>. `;
19301      } else if (intent === 'rekomendasi_produk') {
19302        summary = `Berikut rekomendasi produk <strong>${esc(categoryDisplay || productName)}</strong> yang kami pilihkan untuk Anda. `;
19303      } else {
19304        // Default: cari_harga or general
19305        if (productName && categoryDisplay) {
19306          summary = `Kami menemukan <strong>${esc(productName)}</strong> di kategori ${esc(categoryDisplay)}`;
19307        } else if (productName) {
19308          summary = `Kami menemukan produk <strong>${esc(productName)}</strong>`;
19309        } else if (categoryDisplay) {
19310          summary = `Kami menemukan produk ${esc(categoryDisplay)}`;
19311        }
19312        
19313        // Add price context
19314        if (priceRange.min > 0 && priceRange.max > 0) {
19315          const minPrice = new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(priceRange.min);
19316          const maxPrice = new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(priceRange.max);
19317          summary += ` dengan kisaran harga ${minPrice} - ${maxPrice}`;
19318        } else if (priceRange.min > 0) {
19319          const minPrice = new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(priceRange.min);
19320          summary += ` dengan harga mulai ${minPrice}`;
19321        } else if (priceRange.max > 0) {
19322          const maxPrice = new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(priceRange.max);
19323          summary += ` dengan harga maksimal ${maxPrice}`;
19324        }
19325        
19326        summary += ' yang sesuai dengan kebutuhan Anda.';
19327      }
19328      
19329      return summary || esc(insights.explanation || '');
19330    }
19331
19332    function showAIProgressSteps() {
19333      return `
19334        <div class="ai-progress-container">
19335          <div class="ai-progress-header">
19336            <div class="ai-progress-icon">🤖</div>
19337            <div class="ai-progress-text">
19338              <div class="ai-progress-title">AI sedang menganalisis...</div>
19339              <div class="ai-progress-subtitle">Memproses query Anda dengan kecerdasan buatan</div>
19340            </div>
19341          </div>
19342          <div class="ai-progress-steps">
19343            <div class="ai-progress-step active">
19344              <div class="ai-step-icon">1</div>
19345              <div class="ai-step-text">Memahami maksud pencarian...</div>
19346            </div>
19347            <div class="ai-progress-step pending">
19348              <div class="ai-step-icon">2</div>
19349              <div class="ai-step-text">Mengekstrak parameter pencarian...</div>
19350            </div>
19351            <div class="ai-progress-step pending">
19352              <div class="ai-step-icon">3</div>
19353              <div class="ai-step-text">Mencari produk yang relevan...</div>
19354            </div>
19355          </div>
19356        </div>
19357      `;
19358    }
19359
19360    function updateAIProgressStep(step) {
19361      const steps = suggest.querySelectorAll('.ai-progress-step');
19362      
19363      steps.forEach((stepEl, idx) => {
19364        const icon = stepEl.querySelector('.ai-step-icon');
19365        
19366        if (idx < step) {
19367          stepEl.classList.remove('active', 'pending');
19368          stepEl.classList.add('completed');
19369          icon.textContent = '✓';
19370        } else if (idx === step) {
19371          stepEl.classList.remove('pending', 'completed');
19372          stepEl.classList.add('active');
19373        }
19374      });
19375    }
19376
19377    function renderSuggest(items, aiInsights){
19378      if (!items.length){
19379        clearSuggest();
19380        return;
19381      }
19382      
19383      let html = '';
19384      
19385      // Show AI insights if available
19386      if (aiInsights && aiInsights.explanation) {
19387        html += renderAIInsights(aiInsights);
19388      }
19389      
19390      // Section header for products with hint
19391      html += `
19392        <div class="suggest-section-header">
19393          <div class="suggest-header-main">
19394            <span class="suggest-header-icon">📦</span>
19395            <span class="suggest-header-text">Produk yang ditemukan (${items.length})</span>
19396          </div>
19397          <div class="suggest-header-hint">Tekan <kbd>Enter</kbd> untuk lihat semua hasil</div>
19398        </div>
19399      `;
19400      
19401      // Render product items
19402      html += items.map((it, idx)=>{
19403        // Normalisasi & fallback
19404        const id    = it.id != null ? it.id : '';
19405        const name  = esc(it.name);
19406        const slug  = esc(it.slug != null ? it.slug : it.id);
19407        const img   = esc(it.image || '/assets/img/no-image.png');
19408        const price = esc(it.price_label || '');
19409
19410       const url = '/product/' + encodeURIComponent(slug);
19411
19412        return `
19413          <div class="suggest-item" data-index="${idx}" data-url="${url}" data-id="${esc(id)}">
19414            <img src="${img}" alt="${name}" class="suggest-thumb">
19415            <div class="suggest-meta">
19416              <div class="suggest-name">${name}</div>
19417              <div class="suggest-price">${price}</div>
19418            </div>
19419          </div>
19420        `;
19421      }).join('');
19422      
19423      // Footer hint for more results - only show if 10 items (means there might be more)
19424      if (items.length >= 10) {
19425        html += `
19426          <div class="suggest-footer-hint">
19427            <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="vertical-align: -3px; margin-right: 6px;">
19428              <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"/>
19429            </svg>
19430            <span>Lihat hasil lengkap dengan menekan <kbd>Enter</kbd> atau <a href="#" class="suggest-footer-link" id="footerSearchLink">klik di sini</a></span>
19431          </div>
19432        `;
19433      }
19434      
19435      suggest.innerHTML = html;
19436      suggest.hidden = false;
19437      suggest.classList.add('open');
19438      suggest.style.display = 'block';
19439      suggest.style.visibility = 'visible';
19440      suggest.style.opacity = '1';
19441    }
19442
19443    function fetchRemote(q, shouldLog = false){
19444      // Cancel previous request if any
19445      if (currentAbortController) {
19446        currentAbortController.abort();
19447      }
19448      currentAbortController = new AbortController();
19449
19450      // Always use standard search for normal mode
19451      // Only add log=1 parameter when shouldLog is true (intentional search)
19452      const logParam = shouldLog ? '&log=1' : '';
19453      const url = '/api/search_products.php?q=' + encodeURIComponent(q) + logParam;
19454      const fetchOptions = {
19455        headers: {'Accept':'application/json'},
19456        signal: currentAbortController.signal
19457      };
19458
19459      return fetch(url, fetchOptions)
19460        .then(r=>r.json())
19461        .then(j=>{
19462          if (j.ok && Array.isArray(j.data)) {
19463            return j;
19464          }
19465          return { ok: false, data: [] };
19466        })
19467        .catch(err=>{
19468          if (err.name === 'AbortError') {
19469            return { ok: false, data: [] };
19470          }
19471          return { ok:false, data:[] };
19472        });
19473    }
19474
19475    function showNoResults(q, aiInsights) {
19476      suggest.innerHTML = `
19477        <div style="padding: 32px 24px; text-align: center; color: #64748b;">
19478          <div style="font-size: 56px; margin-bottom: 16px; opacity: 0.6;">🔍</div>
19479          <div style="font-size: 16px; font-weight: 600; color: #334155; margin-bottom: 8px;">
19480            Tidak ada hasil untuk "${esc(q)}"
19481          </div>
19482          ${aiInsights && aiInsights.explanation ? `
19483            <div style="font-size: 13px; margin: 16px auto; padding: 12px 16px; background: #f1f5f9; border-radius: 8px; max-width: 400px; color: #475569;">
19484              <strong style="color: #667eea;">🤖 AI memahami:</strong><br>
19485              ${esc(aiInsights.explanation)}
19486            </div>
19487          ` : ''}
19488          <div style="font-size: 13px; margin-top: 16px; opacity: 0.8;">
19489            Coba kata kunci lain atau hubungi kami untuk bantuan
19490          </div>
19491        </div>
19492      `;
19493      suggest.hidden = false;
19494      suggest.classList.add('open');
19495      suggest.style.display = 'block';
19496    }
19497
19498    function showErrorMessage() {
19499      suggest.innerHTML = `
19500        <div style="padding: 32px 24px; text-align: center; color: #dcce77;">
19501          <div style="font-size: 48px; margin-bottom: 12px;">⚠️</div>
19502          <div style="font-size: 14px; font-weight: 600; margin-bottom: 8px;">
19503            Pencarian tidak tersedia
19504          </div>
19505          <div style="font-size: 12px; opacity: 0.8;">
19506            Silakan coba lagi dalam beberapa saat
19507          </div>
19508        </div>
19509      `;
19510      suggest.hidden = false;
19511      suggest.classList.add('open');
19512      suggest.style.display = 'block';
19513    }
19514
19515    function showAIErrorState(q) {
19516      suggest.innerHTML = `
19517        <div class="ai-progress-container">
19518          <div class="ai-progress-header">
19519            <div class="ai-progress-icon" style="background: linear-gradient(135deg, #f59e0b 0%, #f97316 100%);">⚡</div>
19520            <div class="ai-progress-text">
19521              <div class="ai-progress-title">AI sedang memproses...</div>
19522              <div class="ai-progress-subtitle">Menganalisis query: "${esc(q)}"</div>
19523            </div>
19524          </div>
19525          <div style="padding: 16px; margin-top: 12px; background: #fef3c7; border-radius: 8px; border-left: 3px solid #f59e0b;">
19526            <div style="font-size: 13px; color: #92400e; line-height: 1.6;">
19527              <strong style="display: block; margin-bottom: 4px;">💡 Sedang mencari produk terbaik untuk Anda</strong>
19528              AI kami sedang menganalisis database untuk menemukan hasil yang paling relevan.
19529              Mohon tunggu sebentar...
19530            </div>
19531          </div>
19532          <div style="padding: 12px 16px; margin-top: 12px; text-align: center;">
19533            <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;">
19534              🔄 Refresh & Coba Lagi
19535            </button>
19536          </div>
19537        </div>
19538      `;
19539      suggest.hidden = false;
19540      suggest.classList.add('open');
19541      suggest.style.display = 'block';
19542    }
19543
19544    function triggerAISearch(q) {
19545      lastQuery = q; // Update lastQuery to prevent re-triggering
19546      isAIProcessing = true; // Set flag to prevent suggest box from closing
19547      
19548      // Show AI progress UI - keep suggest box visible
19549      suggest.innerHTML = showAIProgressSteps();
19550      suggest.hidden = false;
19551      suggest.classList.add('open');
19552      suggest.style.display = 'block';
19553      
19554      setTimeout(() => updateAIProgressStep(1), 1000);
19555      setTimeout(() => updateAIProgressStep(2), 2500);
19556
19557      // Cancel previous request if any
19558      if (currentAbortController) {
19559        currentAbortController.abort();
19560      }
19561      currentAbortController = new AbortController();
19562
19563      const url = '/api/search_ai.php?q=' + encodeURIComponent(q);
19564      const fetchOptions = {
19565        headers: {'Accept':'application/json'},
19566        signal: currentAbortController.signal
19567      };
19568      
19569      // Add timeout
19570      const timeoutId = setTimeout(() => {
19571        currentAbortController.abort();
19572        
19573        // Show fallback loading state
19574        suggest.innerHTML = `
19575          <div class="ai-progress-container">
19576            <div class="ai-progress-header">
19577              <div class="ai-progress-icon">⏳</div>
19578              <div class="ai-progress-text">
19579                <div class="ai-progress-title">AI timeout - switching to standard search</div>
19580                <div class="ai-progress-subtitle">Mencari dengan metode tradisional...</div>
19581              </div>
19582            </div>
19583          </div>
19584        `;
19585        
19586        // Fallback to traditional search
19587        fetch('/api/search_products.php?q=' + encodeURIComponent(q), {
19588          headers: { 'Accept': 'application/json' }
19589        })
19590          .then(r => r.json())
19591          .then(j => {
19592            isAIProcessing = false; // Reset flag
19593            if (j.ok && Array.isArray(j.data) && j.data.length) {
19594              renderSuggest(j.data, null);
19595            } else {
19596              showNoResults(q, null);
19597            }
19598          })
19599          .catch(() => {
19600            isAIProcessing = false; // Reset flag
19601            showAIErrorState(q);
19602          });
19603      }, AI_TIMEOUT);
19604
19605      fetch(url, fetchOptions)
19606        .then(r => r.json())
19607        .then(j => {
19608          clearTimeout(timeoutId);
19609          isAIProcessing = false; // Reset flag when done
19610          
19611          if (j.ok && Array.isArray(j.data)) {
19612            if (j.data.length > 0) {
19613              renderSuggest(j.data, j.ai_insights || null);
19614            } else {
19615              showNoResults(q, j.ai_insights || null);
19616            }
19617          } else {
19618            showAIErrorState(q);
19619          }
19620        })
19621        .catch(err => {
19622          clearTimeout(timeoutId);
19623          isAIProcessing = false; // Reset flag on error
19624          if (err.name !== 'AbortError') {
19625            showAIErrorState(q);
19626          }
19627        });
19628    }
19629
19630    function performSearch(q){
19631      if (q.length < MIN_LEN){
19632        clearSuggest();
19633        return;
19634      }
19635      if (q === lastQuery) return;
19636      lastQuery = q;
19637      
19638      // Normal mode only - AI mode uses triggerAISearch()
19639      fetchRemote(q).then(j=>{
19640        if (j.ok && Array.isArray(j.data)){
19641          if (j.data.length > 0) {
19642            renderSuggest(j.data, null);
19643          } else {
19644            showNoResults(q, null);
19645          }
19646        } else {
19647          clearSuggest();
19648        }
19649      }).catch(() => {
19650        clearSuggest();
19651      });
19652    }
19653
19654    function debounce(){
19655      const q = searchInput.value.trim();
19656      clearTimeout(debounceTimer);
19657      
19658      // If empty, show trending suggestions and products
19659      if (q.length === 0) {
19660        debounceTimer = setTimeout(async () => {
19661          const trends = trendingCache || await fetchTrending();
19662          const products = trendingProductsCache || await fetchTrendingProducts();
19663          if ((trends && trends.length > 0) || (products && products.length > 0)) {
19664            renderTrendingSuggestions(trends, products);
19665          } else {
19666            clearSuggest();
19667          }
19668        }, 100);
19669        return;
19670      }
19671      
19672      // For AI mode, just show search prompt (no auto search)
19673      if (isAIModeEnabled && q.length >= MIN_LEN) {
19674        showAISearchPrompt(q);
19675      } else if (!isAIModeEnabled && q.length >= MIN_LEN) {
19676        // Normal mode: auto search with debounce
19677        debounceTimer = setTimeout(()=> performSearch(q), DEBOUNCE);
19678      } else {
19679        clearSuggest();
19680      }
19681    }
19682
19683    function showAISearchPrompt(q) {
19684      suggest.innerHTML = `
19685        <div class="ai-search-prompt">
19686          <div class="ai-prompt-header">
19687            <div class="ai-prompt-icon">🤖</div>
19688            <div class="ai-prompt-text">
19689              <div class="ai-prompt-title">Pencarian AI Siap!</div>
19690              <div class="ai-prompt-subtitle">Klik tombol di bawah untuk mencari dengan AI</div>
19691            </div>
19692          </div>
19693          <div class="ai-prompt-query">
19694            <div class="ai-query-label">
19694Query Anda:</div>
19695            <div class="ai-query-text">"${esc(q)}"</div>
19696          </div>
19697          <button class="ai-search-trigger-btn" data-query="${esc(q)}">
19698            <span class="ai-btn-icon">✨</span>
19699            <span class="ai-btn-text">Cari dengan AI</span>
19700          </button>
19701          <div class="ai-prompt-footer">
19702            AI akan menganalisis query Anda untuk hasil yang lebih relevan
19703          </div>
19704        </div>
19705      `;
19706      suggest.hidden = false;
19707      suggest.classList.add('open');
19708      suggest.style.display = 'block';
19709    }
19710
19711    function moveHighlight(dir){
19712      if (suggest.hidden) return;
19713      const items = suggest.querySelectorAll('.suggest-item');
19714      if (!items.length) return;
19715      currentIndex += dir;
19716      if (currentIndex < 0) currentIndex = items.length - 1;
19717      if (currentIndex >= items.length) currentIndex = 0;
19718      items.forEach(it=> it.classList.remove('active'));
19719      const active = items[currentIndex];
19720      if (active){
19721        active.classList.add('active');
19722        active.scrollIntoView({block:'nearest'});
19723      }
19724    }
19725
19726    function activate(){
19727      const active = suggest.querySelector('.suggest-item.active');
19728      if (!active) return;
19729      window.location.href = active.getAttribute('data-url');
19730    }
19731
19732    searchInput.addEventListener('input', debounce);
19733    
19734    // Stop typing animation when focused & load trending if empty
19735    searchInput.addEventListener('focus', async () => {
19736      stopTypingAnimation();
19737      
19738      const q = searchInput.value.trim();
19739      
19740      // If empty, show trending suggestions and products
19741      if (q.length === 0) {
19742        const trends = trendingCache || await fetchTrending();
19743        const products = trendingProductsCache || await fetchTrendingProducts();
19744        if ((trends && trends.length > 0) || (products && products.length > 0)) {
19745          renderTrendingSuggestions(trends, products);
19746        }
19747        return;
19748      }
19749      
19750      // If AI mode and has query, show AI prompt
19751      if (isAIModeEnabled && q.length >= MIN_LEN && suggest.hidden) {
19752        showAISearchPrompt(q);
19753      }
19754    });
19755    
19756    // Resume typing animation when blurred (if suggest is closed)
19757    searchInput.addEventListener('blur', () => {
19758      // Add delay to allow click events to fire first
19759      setTimeout(() => {
19760        if (!isAIModeEnabled && suggest.hidden && searchInput.value.trim() === '') {
19761          startTypingAnimation();
19762        }
19763      }, 200);
19764    });
19765    
19766    searchInput.addEventListener('keydown', e=>{
19767      switch(e.key){
19768        case 'ArrowDown': moveHighlight(1); e.preventDefault(); break;
19769        case 'ArrowUp': moveHighlight(-1); e.preventDefault(); break;
19770        case 'Enter':
19771          if (!suggest.hidden){
19772            activate();
19773            e.preventDefault();
19774          } else {
19775            // Direct enter without suggestion - track search submit and log final search
19776            const q = searchInput.value.trim();
19777            if (q.length >= MIN_LEN) {
19778              trackSearchSubmit(q);
19779              logFinalSearch(q);
19780            }
19781          }
19782          break;
19783        case 'Escape': clearSuggest(); break;
19784      }
19785    });
19786
19787    suggest.addEventListener('click', e=>{
19788      // Check if footer link clicked
19789      const footerLink = e.target.closest('#footerSearchLink');
19790      if (footerLink) {
19791        e.preventDefault();
19792        const q = searchInput.value.trim();
19793        if (q.length >= MIN_LEN) {
19794          trackSearchSubmit(q);
19795          logFinalSearch(q);
19796          window.location.href = '/product/search/?q=' + encodeURIComponent(q);
19797        }
19798        return;
19799      }
19800      
19801      // Check if AI search button clicked
19802      const aiBtn = e.target.closest('.ai-search-trigger-btn');
19803      if (aiBtn) {
19804        const query = aiBtn.getAttribute('data-query');
19805        if (query) {
19806          triggerAISearch(query);
19807        }
19808        return;
19809      }
19810      
19811      // Handle trending item click
19812      const trendingItem = e.target.closest('.trending-item');
19813      if (trendingItem) {
19814        const query = trendingItem.getAttribute('data-query');
19815        if (query) {
19816          searchInput.value = query;
19817          trackTrendingClick(query);
19818          logFinalSearch(query); // Log final search when user clicks trending
19819          performSearch(query);
19820        }
19821        return;
19822      }
19823      
19824      // Handle trending product click
19825      const trendingProduct = e.target.closest('.trending-product-item');
19826      if (trendingProduct) {
19827        const productId = trendingProduct.getAttribute('data-product-id');
19828        const slug = trendingProduct.getAttribute('data-slug');
19829        const query = trendingProduct.getAttribute('data-query');
19830        
19831        if (productId && slug) {
19832          trackTrendingProductClick(productId, query);
19833          window.location.href = '/product/' + encodeURIComponent(slug);
19834        }
19835        return;
19836      }
19837      
19838      // Handle product item click
19839      const it = e.target.closest('.suggest-item');
19840      if (!it) return;
19841      
19842      const productId = it.getAttribute('data-id');
19843      trackSearchClick(lastQuery, productId);
19844      logFinalSearch(lastQuery); // Log final search when user clicks on suggestion
19845      
19846      window.location.href = it.getAttribute('data-url');
19847    });
19848
19849    document.addEventListener('click', e=>{
19850      // Don't close suggest box if AI is processing
19851      if (isAIProcessing) return;
19852      
19853      if (e.target === searchInput || e.target === aiToggleBtn || 
19854          suggest.contains(e.target) || aiToggleBtn.contains(e.target)) return;
19855      clearSuggest();
19856    });
19857
19858    if (searchBtn){
19859      searchBtn.addEventListener('click', ()=>{
19860        const q = searchInput.value.trim();
19861        if (q.length >= MIN_LEN){
19862          // Track search submit and log final search
19863          trackSearchSubmit(q);
19864          logFinalSearch(q);
19865          window.location.href = '/product/search/?q=' + encodeURIComponent(q);
19866        }
19867      });
19868    }
19869
19870    // Pastikan style dasar muncul
19871    if (!suggest.classList.contains('wired')) {
19872      suggest.classList.add('wired');
19873      suggest.style.position = 'absolute';
19874      suggest.style.zIndex = '9999';
19875      suggest.style.background = '#fff';
19876      suggest.style.border = '1px solid #ccc';
19877      suggest.style.boxShadow = '0 4px 12px rgba(0,0,0,0.12)';
19878      suggest.style.maxHeight = '360px';
19879      suggest.style.overflowY = 'auto';
19880    }
19881
19882    return true;
19883  }
19884
19885  if (!setup()) {
19886    if (document.readyState === 'loading') {
19887      document.addEventListener('DOMContentLoaded', setup);
19888    } else {
19889      setTimeout(setup, 80);
19890    }
19891  }
19892})();
19893</script>
vendor: 1 bytes, line 19893
19893
19894<script data-inlined="site-notice.js">
19895/**
19896 * Site Notice Banner - Interactive Behavior
19897 * Handles dismiss functionality with localStorage persistence
19898 * Supports dynamic notice IDs for different campaigns
19899 */
19900
19901(function() {
19902    'use strict';
19903
19904    const STORAGE_KEY = 'plazait_site_notice_dismissed';
19905    
19906    const banner = document.getElementById('holidayNotice');
19907    const closeBtn = document.getElementById('holidayNoticeClose');
19908    
19909    if (!banner || !closeBtn) return;
19910
19911    // Get notice ID from data attribute
19912    const noticeId = banner.dataset.noticeId;
19913    if (!noticeId) {
19914        console.warn('Notice ID not found. Banner will not persist dismiss state.');
19915    }
19916
19917    /**
19918     * Check if banner was previously dismissed
19919     */
19920    function wasDismissed() {
19921        if (!noticeId) return false;
19922        
19923        try {
19924            const dismissed = localStorage.getItem(STORAGE_KEY);
19925            return dismissed === noticeId;
19926        } catch (e) {
19927            console.warn('localStorage not available:', e);
19928            return false;
19929        }
19930    }
19931
19932    /**
19933     * Mark banner as dismissed
19934     */
19935    function markDismissed() {
19936        if (!noticeId) return;
19937        
19938        try {
19939            localStorage.setItem(STORAGE_KEY, noticeId);
19940        } catch (e) {
19941            console.warn('Could not save to localStorage:', e);
19942        }
19943    }
19944
19945    /**
19946     * Hide banner with animation
19947     */
19948    function hideBanner() {
19949        banner.classList.add('hidden');
19950        document.body.classList.remove('has-holiday-notice');
19951        
19952        // Remove from DOM after animation
19953        setTimeout(() => {
19954            banner.remove();
19955        }, 300);
19956        
19957        markDismissed();
19958        
19959        // Analytics tracking (optional)
19960        if (window.gtag) {
19961            window.gtag('event', 'banner_dismiss', {
19962                'event_category': 'Site Notice',
19963                'event_label': noticeId || 'unknown',
19964            });
19965        }
19966    }
19967
19968    /**
19969     * Initialize banner
19970     */
19971    function init() {
19972        // Check if already dismissed
19973        if (wasDismissed()) {
19974            banner.remove();
19975            return;
19976        }
19977        
19978        // Add body class for layout adjustment
19979        document.body.classList.add('has-holiday-notice');
19980        
19981        // Close button click
19982        closeBtn.addEventListener('click', hideBanner);
19983        
19984        // Keyboard support for close button
19985        closeBtn.addEventListener('keydown', (e) => {
19986            if (e.key === 'Enter' || e.key === ' ') {
19987                e.preventDefault();
19988                hideBanner();
19989            }
19990        });
19991        
19992        // Optional: Auto-hide after certain time (disabled by default)
19993        // setTimeout(hideBanner, 30000); // 30 seconds
19994    }
19995
19996    // Initialize when DOM is ready
19997    if (document.readyState === 'loading') {
19998        document.addEventListener('DOMContentLoaded', init);
19999    } else {
20000        init();
20001    }
20002
20003})();
20004
20005</script>
vendor: 1 bytes, line 20005
20005
20006<script data-inlined="guest-cart.js">
20007/**
20008 * Guest Cart Manager - localStorage based cart for guest users
20009 * 
20010 * Manages shopping cart items in browser localStorage for users who are not logged in.
20011 * Provides seamless transition to database when user logs in.
20012 */
20013
20014class GuestCart {
20015    constructor() {
20016        this.storageKey = 'plazait_guest_cart';
20017        this.apiBase = '/api/cart';
20018        
20019        // Initialize cart structure
20020        this.cart = this.loadFromStorage();
20021        
20022        // Bind methods
20023        this.add = this.add.bind(this);
20024        this.remove = this.remove.bind(this);
20025        this.update = this.update.bind(this);
20026        this.clear = this.clear.bind(this);
20027        this.getItems = this.getItems.bind(this);
20028        this.mergeToDatabase = this.mergeToDatabase.bind(this);
20029    }
20030    
20031    /**
20032     * Load cart from localStorage
20033     */
20034    loadFromStorage() {
20035        try {
20036            const stored = localStorage.getItem(this.storageKey);
20037            if (stored) {
20038                const parsed = JSON.parse(stored);
20039                if (parsed && typeof parsed === 'object' && parsed.items) {
20040                    return parsed;
20041                }
20042            }
20043        } catch (error) {
20044            // Silent error handling
20045        }
20046        
20047        return {
20048            items: {},
20049            updated_at: Date.now(),
20050            version: '1.0'
20051        };
20052    }
20053    
20054    /**
20055     * Save cart to localStorage
20056     */
20057    saveToStorage() {
20058        try {
20059            this.cart.updated_at = Date.now();
20060            localStorage.setItem(this.storageKey, JSON.stringify(this.cart));
20061            
20062            // Dispatch custom event for cart updates
20063            window.dispatchEvent(new CustomEvent('guestCartUpdated', {
20064                detail: {
20065                    itemsCount: this.getItemsCount(),
20066                    linesCount: this.getLinesCount(),
20067                    items: this.cart.items
20068                }
20069            }));
20070            
20071            return true;
20072        } catch (error) {
20073            return false;
20074        }
20075    }
20076    
20077    /**
20078     * Generate cart item key
20079     */
20080    generateKey(productId, variantId = 0) {
20081        return `p${productId}v${variantId}`;
20082    }
20083    
20084    /**
20085     * Add item to guest cart
20086     */
20087    add(productId, qty = 1, variantId = 0, meta = {}) {
20088        const key = this.generateKey(productId, variantId);
20089        
20090        // Validate inputs
20091        if (!productId || productId <= 0 || !qty || qty <= 0) {
20092            throw new Error('Invalid product ID or quantity');
20093        }
20094        
20095        qty = Math.max(1, Math.min(999, parseInt(qty)));
20096        
20097        // Initialize item if not exists
20098        if (!this.cart.items[key]) {
20099            this.cart.items[key] = {
20100                pid: parseInt(productId),
20101                vid: parseInt(variantId) || 0,
20102                qty: 0,
20103                name: meta.name || '',
20104                sku: meta.sku || '',
20105                price: parseInt(meta.price) || 0,
20106                image_url: meta.image_url || '',
20107                product_url: meta.product_url || '/product/' + parseInt(productId),
20108                added_at: Date.now()
20109            };
20110        }
20111        
20112        // Update quantity
20113        this.cart.items[key].qty = Math.max(1, Math.min(999, this.cart.items[key].qty + qty));
20114        
20115        // Update metadata if provided
20116        if (meta.name) this.cart.items[key].name = meta.name;
20117        if (meta.sku) this.cart.items[key].sku = meta.sku;
20118        if (meta.price) this.cart.items[key].price = parseInt(meta.price);
20119        if (meta.image_url) this.cart.items[key].image_url = meta.image_url;
20120        if (meta.product_url) this.cart.items[key].product_url = meta.product_url;
20121        
20122        this.saveToStorage();
20123        
20124        return {
20125            key,
20126            item: this.cart.items[key],
20127            itemsCount: this.getItemsCount(),
20128            linesCount: this.getLinesCount()
20129        };
20130    }
20131    
20132    /**
20133     * Remove item from guest cart
20134     */
20135    remove(productId, variantId = 0) {
20136        const key = this.generateKey(productId, variantId);
20137        
20138        if (this.cart.items[key]) {
20139            delete this.cart.items[key];
20140            this.saveToStorage();
20141            
20142            return true;
20143        }
20144        
20145        return false;
20146    }
20147    
20148    /**
20149     * Update item quantity
20150     */
20151    update(productId, qty, variantId = 0) {
20152        const key = this.generateKey(productId, variantId);
20153        
20154        if (!this.cart.items[key]) {
20155            return false;
20156        }
20157        
20158        qty = parseInt(qty);
20159        if (qty <= 0) {
20160            return this.remove(productId, variantId);
20161        }
20162        
20163        this.cart.items[key].qty = Math.max(1, Math.min(999, qty));
20164        this.saveToStorage();
20165        
20166        return true;
20167    }
20168    
20169    /**
20170     * Clear entire cart
20171     */
20172    clear() {
20173        this.cart = {
20174            items: {},
20175            updated_at: Date.now(),
20176            version: '1.0'
20177        };
20178        
20179        this.saveToStorage();
20180        
20181        return true;
20182    }
20183    
20184    /**
20185     * Get all cart items
20186     */
20187    getItems() {
20188        return this.cart.items;
20189    }
20190    
20191    /**
20192     * Get cart items as array
20193     */
20194    getItemsArray() {
20195        return Object.values(this.cart.items);
20196    }
20197    
20198    /**
20199     * Get total items count (sum of quantities)
20200     */
20201    getItemsCount() {
20202        return Object.values(this.cart.items).reduce((total, item) => total + (item.qty || 0), 0);
20203    }
20204    
20205    /**
20206     * Get lines count (number of different items)
20207     */
20208    getLinesCount() {
20209        return Object.keys(this.cart.items).length;
20210    }
20211    
20212    /**
20213     * Get cart summary
20214     */
20215    getSummary() {
20216        const itemsCount = this.getItemsCount();
20217        const linesCount = this.getLinesCount();
20218        const totalValue = Object.values(this.cart.items).reduce(
20219            (total, item) => total + ((item.qty || 0) * (item.price || 0)), 0
20220        );
20221        
20222        return {
20223            itemsCount,
20224            linesCount,
20225            totalValue,
20226            updatedAt: this.cart.updated_at
20227        };
20228    }
20229    
20230    /**
20231     * Check if cart has items
20232     */
20233    isEmpty() {
20234        return this.getLinesCount() === 0;
20235    }
20236    
20237    /**
20238     * Merge guest cart to database after user login
20239     */
20240    async mergeToDatabase() {
20241        const items = this.cart.items;
20242        
20243        if (Object.keys(items).length === 0) {
20244            return { ok: true, merged_count: 0, message: 'No items to merge' };
20245        }
20246        
20247        try {
20248            // Use new endpoint that handles localStorage format
20249            const response = await fetch(`${this.apiBase}/merge-localStorage.php`, {
20250                method: 'POST',
20251                headers: {
20252                    'Content-Type': 'application/json',
20253                },
20254                credentials: 'same-origin',
20255                body: JSON.stringify({
20256                    items: items  // Send localStorage format directly
20257                })
20258            });
20259            
20260            const result = await response.json();
20261            
20262            if (result.ok) {
20263                // Clear localStorage cart after successful merge
20264                this.clear();
20265                
20266                // Update cart counter
20267                if (window.updateCartCounter) {
20268                    try {
20269                        await window.updateCartCounter();
20270                    } catch (e) {
20271                        // Silent error handling
20272                    }
20273                }
20274                
20275                // Dispatch merge completion event
20276                window.dispatchEvent(new CustomEvent('guestCartMerged', {
20277                    detail: result
20278                }));
20279                
20280                return result;
20281            } else {
20282                throw new Error(result.error || 'Failed to merge cart');
20283            }
20284            
20285        } catch (error) {
20286            throw error;
20287        }
20288    }
20289    
20290    /**
20291     * Export cart data (for debugging or backup)
20292     */
20293    export() {
20294        return {
20295            ...this.cart,
20296            summary: this.getSummary()
20297        };
20298    }
20299    
20300    /**
20301     * Import cart data (for restore or testing)
20302     */
20303    import(cartData) {
20304        if (cartData && typeof cartData === 'object' && cartData.items) {
20305            this.cart = {
20306                items: cartData.items,
20307                updated_at: cartData.updated_at || Date.now(),
20308                version: cartData.version || '1.0'
20309            };
20310            
20311            this.saveToStorage();
20312            return true;
20313        }
20314        
20315        return false;
20316    }
20317}
20318
20319// Create global instance
20320window.guestCart = new GuestCart();
20321
20322// Auto-merge on page load if user is logged in
20323document.addEventListener('DOMContentLoaded', async function() {
20324    // Multiple ways to check if user is logged in
20325    const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true' ||
20326                      document.body.classList.contains('logged-in') ||
20327                      window.userLoggedIn === true ||
20328                      document.body.dataset.userLoggedIn === 'true';
20329    
20330    if (isLoggedIn && !window.guestCart.isEmpty()) {
20331        // Small delay to ensure page is fully loaded
20332        setTimeout(async () => {
20333            try {
20334                const result = await window.guestCart.mergeToDatabase();
20335            } catch (error) {
20336                // Silent error handling
20337            }
20338        }, 500);
20339    }
20340});
20341
20342// Also listen for login events (if login happens via AJAX)
20343window.addEventListener('userLoggedIn', async function() {
20344    if (!window.guestCart.isEmpty()) {
20345        try {
20346            await window.guestCart.mergeToDatabase();
20347        } catch (error) {
20348            // Silent error handling
20349        }
20350    }
20351});
20352</script>
vendor: 1 bytes, line 20352
20352
20353<script data-inlined="cart.js">
20354(function(){
20355  function $(s,ctx=document){ return ctx.querySelector(s); }
20356  function esc(s){ return String(s||'').replace(/[&<>"']/g, m=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;' }[m])); }
20357  
20358  // CSRF token helper
20359  function getCsrfToken() {
20360    const metaTag = document.querySelector('meta[name="csrf-token"]');
20361    return metaTag ? metaTag.getAttribute('content') : '';
20362  }
20363  
20364  function guessPdMeta(){
20365    const name = (window.PD_NAME !== undefined) ? String(window.PD_NAME) : ($('#pd-title')?.textContent || '');
20366    const sku  = (function(){ const el=$('#pd-sku'); return el ? el.textContent.trim() : (window.PD_SKU||''); })();
20367    // SECURITY FIX: Remove price from client-side metadata (will be validated server-side)
20368    const imgEl = $('#pd-main-img');
20369    const image_url = imgEl ? (imgEl.getAttribute('src') || '') : '';
20370    return { name, sku, image_url }; // price removed for security
20371  }
20372
20373  async function addToCart(productId, qty=1, variantId=0, metaOpt){
20374    const meta = Object.assign(guessPdMeta(), metaOpt||{});
20375    const payload = {
20376      product_id: parseInt(productId||0,10) || 0,
20377      qty: parseInt(qty||1,10) || 1,
20378      variant_id: parseInt(variantId||0,10) || 0,
20379      meta
20380    };
20381    
20382    // Check if user is logged in
20383    const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true' ||
20384                      document.body.classList.contains('logged-in') ||
20385                      window.userLoggedIn === true ||
20386                      document.body.dataset.userLoggedIn === 'true';
20387    
20388    // Skip localStorage untuk logged user - mereka pakai database
20389    if (!isLoggedIn && window.guestCart) {
20390      try {
20391        window.guestCart.add(payload.product_id, payload.qty, payload.variant_id, meta);
20392      } catch (e) {
20393        console.warn('Failed to add to localStorage cart:', e);
20394      }
20395    }
20396    
20397    const res = await fetch('/api/cart/add.php', {
20398      method: 'POST',
20399      headers: { 
20400        'Content-Type':'application/json',
20401        'X-CSRF-Token': getCsrfToken()
20402      },
20403      credentials: 'same-origin',
20404      body: JSON.stringify({
20405        ...payload,
20406        csrf_token: getCsrfToken()
20407      }),
20408    });
20409    const data = await res.json().catch(()=>({ ok:false, error:'Gagal menambahkan ke keranjang' }));
20410    if (!res.ok || !data.ok) {
20411      const msg = data && data.error ? data.error : 'Gagal menambahkan ke keranjang';
20412      throw new Error(msg);
20413    }
20414
20415    // Update badge di navbar dengan data dari response
20416    try {
20417      const el = document.querySelector('[data-role="cart-badge"]') || document.getElementById('cartCount');
20418      if (el && data.cart && typeof data.cart.items_count === 'number') {
20419        el.textContent = String(data.cart.items_count);
20420        el.classList.toggle('is-empty', data.cart.items_count <= 0);
20421      }
20422    } catch(_){}
20423
20424    // Siarkan event global (dipakai cart-badge.js dan halaman lain)
20425    try {
20426      document.dispatchEvent(new CustomEvent('cart:updated', { detail: data.cart || {} }));
20427    } catch(_){}
20428
20429    // Update CartBadge dengan data dari response (lebih reliable)
20430    try { 
20431      if (window.CartBadge && window.CartBadge.set) {
20432        window.CartBadge.set(data.cart.items_count||0);
20433      }
20434    } catch(_){}
20435
20436    // Tambahan: refresh cart counter setelah delay kecil untuk memastikan data tersinkronisasi
20437    setTimeout(() => {
20438      if (window.updateCartCounter) {
20439        window.updateCartCounter();
20440      }
20441    }, 100);
20442
20443    return data;
20444  }
20445
20446  // Function to update cart counter from server
20447  async function updateCartCounter(){
20448    try {
20449      const res = await fetch('/api/cart/get.php', { credentials:'same-origin' });
20450      const data = await res.json().catch(()=>({ok:false}));
20451      if (data.ok && typeof data.items_count === 'number') {
20452        const el = document.querySelector('[data-role="cart-badge"]') || document.getElementById('cartCount');
20453        if (el) {
20454          el.textContent = String(data.items_count);
20455          el.classList.toggle('is-empty', data.items_count <= 0);
20456        }
20457        // Dispatch event
20458        try {
20459          document.dispatchEvent(new CustomEvent('cart:updated', { detail: { items_count: data.items_count } }));
20460        } catch(_){}
20461      }
20462    } catch (err) {
20463      console.log('Could not update cart counter:', err);
20464    }
20465  }
20466
20467  // Function to manually merge guest cart (if needed)
20468  async function mergeGuestCart(){
20469    try {
20470      const res = await fetch('/api/cart/merge-guest.php', {
20471        method: 'POST',
20472        headers: { 
20473          'Content-Type': 'application/json',
20474          'X-CSRF-Token': getCsrfToken()
20475        },
20476        credentials: 'same-origin',
20477        body: JSON.stringify({ csrf_token: getCsrfToken() })
20478      });
20479      const data = await res.json().catch(()=>({ok:false}));
20480      
20481      if (data.ok && data.merged_items > 0) {
20482        console.log(`Guest cart merged: ${data.merged_items} items`);
20483        // Update cart counter after merge
20484        await updateCartCounter();
20485        return data;
20486      }
20487      return data;
20488    } catch (err) {
20489      console.log('Could not merge guest cart:', err);
20490      return { ok: false, error: err.message };
20491    }
20492  }
20493
20494  window.addToCart = addToCart;
20495  window.updateCartCounter = updateCartCounter;
20496  window.mergeGuestCart = mergeGuestCart;
20497
20498  // Optional: auto-bind untuk tombol apapun yang punya data-addtocart
20499  document.addEventListener('click', async (e)=>{
20500    const btn = e.target.closest('[data-addtocart]');
20501    if (!btn) return;
20502    e.preventDefault();
20503    if (btn.disabled) return;
20504
20505    const pid = parseInt(btn.getAttribute('data-pid')||'0',10);
20506    const vid = parseInt(btn.getAttribute('data-vid')||'0',10);
20507    const qty = parseInt(btn.getAttribute('data-qty')||'1',10) || 1;
20508
20509    try {
20510      const data = await addToCart(pid, qty, vid);
20511      if (window.showToast) {
20512        const meta = guessPdMeta();
20513        window.showToast({
20514          type:'success',
20515          title:'Berhasil ditambahkan',
20516          message:`<strong>${esc(meta.name)}</strong>${meta.sku?` · <em>SKU: ${esc(meta.sku)}</em>`:''}<br>Qty: ${qty}`,
20517          duration: 3800,
20518          actions: [
20519            { label:'Lihat Keranjang', onClick: ()=> { window.location.href='/keranjang'; } },
20520            { label:'Lanjut Belanja', variant:'alt', onClick: ()=>{} }
20521          ]
20522        });
20523      }
20524    } catch (err) {
20525      if (window.showToast) {
20526        window.showToast({ type:'warn', title:'Gagal', message: String(err?.message||'Gagal menambahkan'), duration: 4200 });
20527      } else {
20528        console.error(err);
20529      }
20530    }
20531  });
20532})();
20533</script>
vendor: 1 bytes, line 20533
20533
20534<script data-inlined="mega-menu-tiles.js">
20535// Tile Mega Menu — Enhanced 2025 (Stagger animations, product counts, loading states)
20536// Includes safe-area hover handling (no backdrop flicker)
20537
20538(function(){
20539  const trigger   = document.querySelector('[data-mm2="trigger"]');
20540  const panel     = document.querySelector('[data-mm2="panel"]');
20541  const topView   = document.querySelector('[data-mm2="top"]');
20542  const detailView= document.querySelector('[data-mm2="detail"]');
20543  const backdrop  = document.querySelector('[data-mm2="backdrop"]');
20544  if (!trigger || !panel || !topView || !detailView) return;
20545
20546  const apiUrl = '/api/categories_tree.php?top=16&child=18&grand=14';
20547  const isDesktop = () => window.matchMedia('(min-width: 961px)').matches;
20548
20549  let data = [];
20550  let hoverTimer, closeTimer;
20551  let isLoading = false;
20552
20553  const esc = s => (s+'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
20554  
20555  // Mobile close button HTML
20556  const mobileCloseBtn = () => `
20557    <button type="button" class="mm2-mobile-close" data-mm2="close" aria-label="Tutup menu">
20558      <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">
20559        <line x1="18" y1="6" x2="6" y2="18"></line>
20560        <line x1="6" y1="6" x2="18" y2="18"></line>
20561      </svg>
20562    </button>
20563  `;
20564  
20565  // Category-specific gradient colors (16 unique combinations)
20566  const categoryGradients = [
20567    { from: '#e0e7ff', to: '#fff', border: '#c7d2fe', color: '#4f46e5' },  // Indigo
20568    { from: '#dbeafe', to: '#fff', border: '#bfdbfe', color: '#1e40af' },  // Blue
20569    { from: '#d1fae5', to: '#fff', border: '#a7f3d0', color: '#059669' },  // Green
20570    { from: '#fef3c7', to: '#fff', border: '#fde68a', color: '#d97706' },  // Amber
20571    { from: '#fce7f3', to: '#fff', border: '#fbcfe8', color: '#db2777' },  // Pink
20572    { from: '#e9d5ff', to: '#fff', border: '#d8b4fe', color: '#9333ea' },  // Purple
20573    { from: '#ffedd5', to: '#fff', border: '#fed7aa', color: '#ea580c' },  // Orange
20574    { from: '#ccfbf1', to: '#fff', border: '#99f6e4', color: '#0d9488' },  // Teal
20575    { from: '#fef9c3', to: '#fff', border: '#fef08a', color: '#ca8a04' },  // Yellow
20576    { from: '#ffe4e6', to: '#fff', border: '#fecdd3', color: '#e11d48' },  // Rose
20577    { from: '#e0f2fe', to: '#fff', border: '#bae6fd', color: '#0284c7' },  // Sky
20578    { from: '#f3e8ff', to: '#fff', border: '#e9d5ff', color: '#a855f7' },  // Violet
20579    { from: '#dcfce7', to: '#fff', border: '#bbf7d0', color: '#16a34a' },  // Emerald
20580    { from: '#fef2f2', to: '#fff', border: '#fecaca', color: '#dcce77' },  // Red
20581    { from: '#eff6ff', to: '#fff', border: '#dbeafe', color: '#2563eb' },  // Blue-600
20582    { from: '#f5f3ff', to: '#fff', border: '#ede9fe', color: '#7c3aed' },  // Violet-600
20583  ];
20584  
20585  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>`;
20586
20587  // Recent (optional future use)
20588  const REC_KEY='mm2_recent';
20589  const saveRecent=(path,label)=>{ try{
20590    let list=JSON.parse(localStorage.getItem(REC_KEY)||'[]').filter(x=>x&&x.path!==path);
20591    list.unshift({path,label,t:Date.now()}); localStorage.setItem(REC_KEY, JSON.stringify(list.slice(0,8)));
20592  }catch{} };
20593
20594  function isToSafeArea(e){
20595    const t = e && e.relatedTarget ? e.relatedTarget : null;
20596    if (!t) return false;
20597    return (trigger.contains(t) || panel.contains(t) || (backdrop && backdrop.contains(t)));
20598  }
20599
20600  function open(){ 
20601    document.body.classList.add('mm2-open'); 
20602    showTop(); 
20603    // Add entrance animation class
20604    setTimeout(() => panel.classList.add('mm2-animated'), 10);
20605  }
20606  function close(){ 
20607    document.body.classList.remove('mm2-open');
20608    panel.classList.remove('mm2-animated');
20609  }
20610  function showTop(){ 
20611    topView.classList.add('is-active'); 
20612    detailView.classList.remove('is-active'); 
20613  }
20614  
20615  // Loading skeleton
20616  function showLoading() {
20617    const skeletons = Array.from({length: 12}, (_, i) => `
20618      <div class="mm2-card mm2-skeleton" style="animation-delay: ${i * 0.02}s">
20619        <span class="mm2-ico skeleton-box"></span>
20620        <span style="flex: 1;">
20621          <div class="skeleton-line" style="width: 60%; height: 16px; margin-bottom: 6px;"></div>
20622          <div class="skeleton-line" style="width: 40%; height: 12px;"></div>
20623        </span>
20624      </div>
20625    `).join('');
20626    topView.innerHTML = `<div class="mm2-top"><div class="mm2-grid">${skeletons}</div></div>`;
20627  }
20628  
20629  // Count total products in a category tree
20630  function countProducts(node) {
20631    let count = node.product_count || 0;
20632    if (Array.isArray(node.children)) {
20633      node.children.forEach(child => {
20634        count += countProducts(child);
20635      });
20636    }
20637    return count;
20638  }
20639  
20640  // Cache for fetched thumbnails to avoid duplicate requests
20641  const thumbnailCache = new Map();
20642  
20643  // Fetch product thumbnails for a category
20644  function fetchCategoryThumbnails(categoryPath) {
20645    // Check cache first
20646    if (thumbnailCache.has(categoryPath)) {
20647      injectThumbnails(categoryPath, thumbnailCache.get(categoryPath));
20648      return;
20649    }
20650    
20651    const url = `/api/category_products_preview.php?path=${encodeURIComponent(categoryPath)}&limit=2`;
20652    
20653    fetch(url)
20654      .then(r => {
20655        if (!r.ok) throw new Error('Network error');
20656        return r.json();
20657      })
20658      .then(data => {
20659        if (data.success && data.products && data.products.length > 0) {
20660          thumbnailCache.set(categoryPath, data.products);
20661          injectThumbnails(categoryPath, data.products);
20662        } else {
20663          // No products, remove skeleton
20664          removeThumbSkeletons(categoryPath);
20665        }
20666      })
20667      .catch(err => {
20668        console.warn('Failed to load thumbnails for', categoryPath, err);
20669        removeThumbSkeletons(categoryPath);
20670      });
20671  }
20672  
20673  // Inject thumbnails into the DOM
20674  function injectThumbnails(categoryPath, products) {
20675    const links = detailView.querySelectorAll(`[data-cat-path="${categoryPath}"]`);
20676    links.forEach(link => {
20677      const thumbsContainer = link.querySelector('.mm2-thumbs');
20678      if (!thumbsContainer) return;
20679      
20680      const thumbsHtml = products.slice(0, 2).map(p => 
20681        `<img src="${esc(p.img)}" alt="${esc(p.name)}" class="mm2-thumb" title="${esc(p.name)}" loading="lazy">`
20682      ).join('');
20683      
20684      thumbsContainer.innerHTML = thumbsHtml;
20685      thumbsContainer.removeAttribute('data-loading');
20686    });
20687  }
20688  
20689  // Remove skeleton loaders when no products
20690  function removeThumbSkeletons(categoryPath) {
20691    const links = detailView.querySelectorAll(`[data-cat-path="${categoryPath}"]`);
20692    links.forEach(link => {
20693      const thumbsContainer = link.querySelector('.mm2-thumbs');
20694      if (thumbsContainer) {
20695        thumbsContainer.remove();
20696      }
20697    });
20698  }
20699
20700  function showDetail(node){
20701    const title = node.label.split('/')[0];
20702    const children = Array.isArray(node.children) ? node.children : [];
20703    const hasChildren = children.length > 0;
20704
20705    const subnav = hasChildren ? `
20706      <nav class="mm2-subnav" role="tablist">
20707        ${children.map((c2,i)=>{
20708          const t = c2.label.split('/').slice(-1)[0];
20709          return `<button class="mm2-subitem${i===0?' active':''}" role="tab" data-idx="${i}" aria-selected="${i===0?'true':'false'}">${esc(t)}</button>`;
20710        }).join('')}
20711      </nav>` : '';
20712
20713    const subcontent = `<div class="mm2-subcontent" data-mm2="subcontent"></div>`;
20714
20715    detailView.innerHTML = `
20716      <div class="mm2-detail">
20717        <div class="mm2-detail-header">
20718          <button class="mm2-back" type="button" data-mm2="back" aria-label="Kembali">
20719            <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>
20720          </button>
20721          <div class="mm2-detail-title">${esc(title)}</div>
20722        </div>
20723
20724        ${hasChildren ? `
20725          <div class="mm2-detail-layout">
20726            ${subnav}
20727            ${subcontent}
20728          </div>` : `
20729          <div class="mm2-links">
20730            <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)}">
20731              <strong>Lihat semua ${esc(title)}</strong>
20732            </a>
20733          </div>
20734        `}
20735      </div>
20736    `;
20737
20738    topView.classList.remove('is-active');
20739    detailView.classList.add('is-active');
20740
20741    if (hasChildren) {
20742      const subEl  = detailView.querySelector('.mm2-subnav');
20743      const contEl = detailView.querySelector('[data-mm2="subcontent"]');
20744      let activeIdx = 0;
20745
20746      const renderSubgrid = (idx) => {
20747        const c2 = children[idx]; if (!c2) { contEl.innerHTML=''; return; }
20748        const title2 = c2.label.split('/').slice(-1)[0];
20749        const l3 = Array.isArray(c2.children) ? c2.children : [];
20750        const l3Html = l3.map(c3 => `
20751          <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)}">
20752            <span class="mm2-link-text">${esc(c3.label.split('/').slice(-1)[0])}</span>
20753            <span class="mm2-thumbs" data-loading="true">
20754              <span class="mm2-thumb-skeleton"></span>
20755              <span class="mm2-thumb-skeleton"></span>
20756            </span>
20757          </a>`).join('');
20758
20759        contEl.innerHTML = `
20760          <div class="mm2-links">
20761            <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)}">
20762              <strong>Lihat semua ${esc(title2)}</strong>
20763            </a>
20764            ${l3Html}
20765          </div>
20766        `;
20767        
20768        // Fetch product thumbnails for each level-3 category (desktop only)
20769        if (isDesktop()) {
20770          setTimeout(() => {
20771            l3.forEach(c3 => {
20772              fetchCategoryThumbnails(c3.path);
20773            });
20774          }, 100);
20775        }
20776      };
20777
20778      renderSubgrid(activeIdx);
20779
20780      // Desktop: hover ganti konten; Mobile & Desktop: click pilih
20781      subEl.addEventListener('mousemove', (e)=>{
20782        if (!isDesktop()) return;
20783        const it = e.target.closest('.mm2-subitem'); if (!it) return;
20784        const idx = +it.dataset.idx; if (!Number.isInteger(idx) || idx===activeIdx) return;
20785        subEl.querySelectorAll('.mm2-subitem').forEach(el=>el.classList.remove('active'));
20786        it.classList.add('active');
20787        subEl.querySelectorAll('.mm2-subitem').forEach(el=>el.setAttribute('aria-selected', el===it ? 'true':'false'));
20788        activeIdx = idx;
20789        renderSubgrid(activeIdx);
20790      });
20791
20792      subEl.addEventListener('click', (e)=>{
20793        const it = e.target.closest('.mm2-subitem'); if (!it) return;
20794        const idx = +it.dataset.idx; if (!Number.isInteger(idx)) return;
20795        subEl.querySelectorAll('.mm2-subitem').forEach(el=>el.classList.remove('active'));
20796        it.classList.add('active');
20797        subEl.querySelectorAll('.mm2-subitem').forEach(el=>el.setAttribute('aria-selected', el===it ? 'true':'false'));
20798        activeIdx = idx;
20799        renderSubgrid(activeIdx);
20800      });
20801
20802      // Delegate link clicks to add recent
20803      detailView.addEventListener('click', (e)=>{
20804        const nav = e.target.closest('[data-mm2="nav"]');
20805        if (nav) { saveRecent(nav.dataset.path, nav.dataset.label); }
20806      });
20807    }
20808  }
20809
20810  function buildTop(){
20811    const html = data.map((n, idx) => {
20812      const title = n.label.split('/')[0];
20813      const childCount = (n.children||[]).length;
20814      const productCount = countProducts(n);
20815      const gradient = categoryGradients[idx % categoryGradients.length];
20816      
20817      // Dynamic subtitle based on content - shorter for mobile
20818      let sub = '';
20819      if (productCount > 0) {
20820        sub = `${productCount} produk`;
20821        if (childCount > 0) sub += ` · ${childCount} kat`;
20822      } else if (childCount > 0) {
20823        sub = `${childCount} kategori`;
20824      } else {
20825        sub = 'Lihat semua';
20826      }
20827      
20828      const iconStyle = `background: linear-gradient(135deg, ${gradient.from}, ${gradient.to}); border-color: ${gradient.border}; color: ${gradient.color};`;
20829      
20830      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)}">
20831        <span class="mm2-ico" style="${iconStyle}">${icon()}</span>
20832        <span style="flex: 1;">
20833          <div class="mm2-title">${esc(title)}</div>
20834          <div class="mm2-sub">${esc(sub)}</div>
20835        </span>
20836      </a>`;
20837    }).join('');
20838    const closeBtnHtml = !isDesktop() ? mobileCloseBtn() : '';
20839    topView.innerHTML = `<div class="mm2-top">${closeBtnHtml}<div class="mm2-grid">${html}</div></div>`;
20840  }
20841
20842  // Open/close with safe-area
20843  trigger.addEventListener('click', (e)=>{ e.preventDefault(); document.body.classList.contains('mm2-open') ? close() : open(); });
20844  trigger.addEventListener('pointerenter', ()=>{ if (isDesktop()) { clearTimeout(closeTimer); hoverTimer=setTimeout(open, 80); }});
20845  trigger.addEventListener('pointerleave', (e)=>{ if (isDesktop() && !isToSafeArea(e)) { clearTimeout(hoverTimer); closeTimer=setTimeout(close, 200); }});
20846  panel.addEventListener('pointerenter', ()=>{ if (isDesktop()) clearTimeout(closeTimer); });
20847  panel.addEventListener('pointerleave', (e)=>{ if (isDesktop() && !isToSafeArea(e)) closeTimer=setTimeout(close, 200); });
20848  backdrop?.addEventListener('pointerenter', ()=>{ clearTimeout(closeTimer); });
20849  backdrop?.addEventListener('click', close);
20850  document.addEventListener('keydown', (e)=>{ if (e.key==='Escape') close(); });
20851
20852  // Card click → open detail (unless cmd/ctrl to open link)
20853  panel.addEventListener('click', (e)=>{
20854    const closeBtn = e.target.closest('[data-mm2="close"]'); if (closeBtn) { e.preventDefault(); close(); return; }
20855    const back = e.target.closest('[data-mm2="back"]'); if (back) { e.preventDefault(); showTop(); return; }
20856    const card = e.target.closest('[data-mm2="card"]');
20857    if (card && !e.metaKey && !e.ctrlKey) {
20858      e.preventDefault();
20859      const n = data.find(d => d.path === card.dataset.path);
20860      if (n) showDetail(n);
20861    }
20862    const nav = e.target.closest('[data-mm2="nav"]');
20863    if (nav) { saveRecent(nav.dataset.path, nav.dataset.label); }
20864  });
20865
20866  // Swipe-down gesture for mobile (bottom sheet close)
20867  if (!isDesktop()) {
20868    let startY = 0, currentY = 0, isDragging = false, startTime = 0;
20869    const SWIPE_THRESHOLD = 80;
20870    const VELOCITY_THRESHOLD = 0.3;
20871
20872    panel.addEventListener('touchstart', (e) => {
20873      if (!document.body.classList.contains('mm2-open')) return;
20874      const touch = e.touches[0];
20875      const scrollableContent = e.target.closest('.mm2-top, .mm2-detail');
20876      
20877      // Prevent drag if content is scrolled
20878      if (scrollableContent && scrollableContent.scrollTop > 5) return;
20879      
20880      // Only start drag from top area or handle
20881      const rect = panel.getBoundingClientRect();
20882      if (touch.clientY - rect.top > 60) return;
20883      
20884      startY = touch.clientY;
20885      currentY = startY;
20886      startTime = Date.now();
20887      isDragging = true;
20888    }, { passive: true });
20889
20890    panel.addEventListener('touchmove', (e) => {
20891      if (!isDragging) return;
20892      const touch = e.touches[0];
20893      currentY = touch.clientY;
20894      const deltaY = currentY - startY;
20895      
20896      // Only allow downward swipe
20897      if (deltaY > 0) {
20898        const opacity = Math.max(0.4, 1 - (deltaY / 300));
20899        panel.style.transform = `translateX(0) translateY(${deltaY}px)`;
20900        panel.style.opacity = opacity;
20901      }
20902    }, { passive: true });
20903
20904    const endDrag = () => {
20905      if (!isDragging) return;
20906      const deltaY = currentY - startY;
20907      const deltaTime = Date.now() - startTime;
20908      const velocity = deltaY / deltaTime;
20909      
20910      // Close if swiped down enough or fast swipe
20911      if (deltaY > SWIPE_THRESHOLD || velocity > VELOCITY_THRESHOLD) {
20912        close();
20913      }
20914      
20915      // Reset position
20916      panel.style.transform = '';
20917      panel.style.opacity = '';
20918      isDragging = false;
20919    };
20920
20921    panel.addEventListener('touchend', endDrag, { passive: true });
20922    panel.addEventListener('touchcancel', endDrag, { passive: true });
20923  }
20924
20925  // Fetch data with loading state
20926  if (!isLoading) {
20927    isLoading = true;
20928    showLoading();
20929    
20930    fetch(apiUrl, { headers: { 'Accept': 'application/json' }})
20931      .then(r => {
20932        if (!r.ok) throw new Error('Network response was not ok');
20933        return r.json();
20934      })
20935      .then(j => { 
20936        data = Array.isArray(j.data) ? j.data : []; 
20937        if (!data.length) {
20938          topView.innerHTML = `<div class="mm2-top"><div style="text-align:center;padding:40px;color:var(--mm2-fg-muted);">
20939            <p>Tidak ada kategori tersedia</p>
20940          </div></div>`;
20941          return;
20942        }
20943        buildTop();
20944        isLoading = false;
20945      })
20946      .catch(err => {
20947        console.error('Failed to load categories:', err);
20948        topView.innerHTML = `<div class="mm2-top"><div style="text-align:center;padding:40px;color:var(--mm2-fg-muted);">
20949          <p>Gagal memuat kategori. Silakan coba lagi.</p>
20950        </div></div>`;
20951        isLoading = false;
20952      });
20953  }
20954})();
20955
20956// Add skeleton animation styles
20957const style = document.createElement('style');
20958style.textContent = `
20959.mm2-skeleton {
20960  pointer-events: none;
20961  animation: pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite;
20962}
20963.skeleton-box, .skeleton-line {
20964  background: linear-gradient(90deg, #f1f5f9 25%, #e2e8f0 50%, #f1f5f9 75%);
20965  background-size: 200% 100%;
20966  animation: shimmer 1.5s infinite;
20967  border-radius: 8px;
20968}
20969@keyframes pulse {
20970  0%, 100% { opacity: 1; }
20971  50% { opacity: 0.6; }
20972}
20973@keyframes shimmer {
20974  0% { background-position: 200% 0; }
20975  100% { background-position: -200% 0; }
20976}
20977`;
20978document.head.appendChild(style);
20979</script>
20979
20980    
20980<script data-inlined="benchmark.js">
20981/**
20982 * Benchmark Data Loader
20983 * Handles AJAX fetch and display of benchmark data
20984 */
20985
20986(function() {
20987    'use strict';
20988    
20989    // Wait for DOM ready
20990    if (document.readyState === 'loading') {
20991        document.addEventListener('DOMContentLoaded', initBenchmark);
20992    } else {
20993        initBenchmark();
20994    }
20995    
20996    function initBenchmark() {
20997        const section = document.querySelector('.pd-benchmark-section');
20998        if (!section) return;
20999        
21000        const button = document.getElementById('btn-load-benchmark');
21001        const content = document.getElementById('benchmark-content');
21002        const productId = section.dataset.productId;
21003        
21004        if (!button || !content || !productId) return;
21005        
21006        let isLoading = false;
21007        let dataLoaded = false;
21008        
21009        button.addEventListener('click', function() {
21010            if (isLoading || dataLoaded) return;
21011            
21012            loadBenchmarkData(productId, button, content);
21013        });
21014    }
21015    
21016    function loadBenchmarkData(productId, button, container) {
21017        // Update button state
21018        button.disabled = true;
21019        button.classList.add('loading');
21020        const originalText = button.querySelector('.btn-text').textContent;
21021        button.querySelector('.btn-text').textContent = 'Memuat data...';
21022        
21023        // Show loading state
21024        container.style.display = 'block';
21025        container.innerHTML = `
21026            <div class="benchmark-loading">
21027                <div class="benchmark-loading-spinner"></div>
21028                <p>Mengambil data benchmark...</p>
21029            </div>
21030        `;
21031        
21032        // Fetch data
21033        fetch('/api/benchmark/fetch.php?product_id=' + productId)
21034            .then(function(response) {
21035                if (!response.ok) {
21036                    throw new Error('HTTP error ' + response.status);
21037                }
21038                return response.json();
21039            })
21040            .then(function(result) {
21041                if (result.success && result.data) {
21042                    renderBenchmarkData(result.data, container);
21043                    button.style.display = 'none'; // Hide button after successful load
21044                } else {
21045                    throw new Error(result.error || 'Data tidak tersedia');
21046                }
21047            })
21048            .catch(function(error) {
21049                console.error('Benchmark fetch error:', error);
21050                container.innerHTML = `
21051                    <div class="benchmark-error">
21052                        <p><strong>⚠️ Data Benchmark Tidak Tersedia</strong></p>
21053                        <p style="font-size: 14px; margin-top: 8px;">
21054                            ${error.message || 'Terjadi kesalahan saat memuat data'}
21055                        </p>
21056                        <button type="button" onclick="location.reload()" 
21057                                style="margin-top: 16px; padding: 8px 20px; background: #667eea; color: #fff; border: none; border-radius: 6px; cursor: pointer;">
21058                            Coba Lagi
21059                        </button>
21060                    </div>
21061                `;
21062                
21063                // Reset button
21064                button.disabled = false;
21065                button.classList.remove('loading');
21066                button.querySelector('.btn-text').textContent = originalText;
21067            });
21068    }
21069    
21070    function renderBenchmarkData(data, container) {
21071        let html = '<div class="benchmark-data-wrapper">';
21072        
21073        // Chipset info if available
21074        if (data.chipset) {
21075            html += `
21076                <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 10px 16px; border-radius: 8px; margin-bottom: 12px;">
21077                    <div style="font-size: 11px; opacity: 0.85; margin-bottom: 2px;">Chipset</div>
21078                    <div style="font-size: 15px; font-weight: 600;">${escapeHtml(data.chipset)}</div>
21079                </div>
21080            `;
21081        }
21082        
21083        // Main benchmark scores
21084        if (data.antutu || data.geekbench_multi || data.overall_score) {
21085            html += '<div class="benchmark-data-grid">';
21086            
21087            if (data.antutu) {
21088                html += `
21089                    <div class="benchmark-card">
21090                        <div class="benchmark-card-label">ANTUTU</div>
21091                        <div class="benchmark-card-value large">${formatNumber(data.antutu)}</div>
21092                        ${getScoreBadge(data.antutu, 'antutu')}
21093                    </div>
21094                `;
21095            }
21096            
21097            if (data.geekbench_multi) {
21098                html += `
21099                    <div class="benchmark-card">
21100                        <div class="benchmark-card-label">GEEKBENCH MULTI</div>
21101                        <div class="benchmark-card-value">${formatNumber(data.geekbench_multi)}</div>
21102                        ${data.geekbench_single ? '<div class="benchmark-card-subtitle">Single: ' + formatNumber(data.geekbench_single) + '</div>' : ''}
21103                    </div>
21104                `;
21105            }
21106            
21107            if (data.gaming_score) {
21108                html += `
21109                    <div class="benchmark-card">
21110                        <div class="benchmark-card-label">GAMING</div>
21111                        <div class="benchmark-card-value">${data.gaming_score}<span style="font-size: 14px; opacity: 0.6;">/100</span></div>
21112                    </div>
21113                `;
21114            }
21115            
21116            if (data.battery_score) {
21117                html += `
21118                    <div class="benchmark-card">
21119                        <div class="benchmark-card-label">BATTERY</div>
21120                        <div class="benchmark-card-value">${data.battery_score}<span style="font-size: 14px; opacity: 0.6;">/100</span></div>
21121                    </div>
21122                `;
21123            }
21124            
21125            if (data.overall_score) {
21126                html += `
21127                    <div class="benchmark-card">
21128                        <div class="benchmark-card-label">OVERALL</div>
21129                        <div class="benchmark-card-value">${data.overall_score}<span style="font-size: 14px; opacity: 0.6;">/100</span></div>
21130                    </div>
21131                `;
21132            }
21133            
21134            html += '</div>';
21135        }
21136        
21137        // AI Performance Description
21138        if (data.notes) {
21139            html += `
21140                <div style="background: #f8f9fa; border-left: 3px solid #667eea; padding: 10px 14px; border-radius: 6px; margin-top: 12px;">
21141                    <div style="font-size: 11px; font-weight: 600; color: #667eea; margin-bottom: 6px;">💡 Analisis Performa</div>
21142                    <div style="font-size: 13px; color: #4a5568; line-height: 1.5;">${escapeHtml(data.notes)}</div>
21143                </div>
21144            `;
21145        }
21146        
21147        // Attribution
21148        html += `
21149            <div class="benchmark-attribution">
21150                <p style="font-size: 11px; margin: 8px 0 0 0; color: #888;">Data benchmark dianalisis menggunakan AI dari spesifikasi produk</p>
21151                ${data.fetched_at ? '<p style="font-size: 10px; margin: 4px 0 0 0; color: #aaa;">Terakhir diperbarui: ' + formatDate(data.fetched_at) + '</p>' : ''}
21152            </div>
21153        `;
21154        
21155        html += '</div>';
21156        
21157        container.innerHTML = html;
21158    }
21159    
21160    function formatNumber(num) {
21161        if (!num) return '-';
21162        return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.');
21163    }
21164    
21165    function escapeHtml(text) {
21166        if (!text) return '';
21167        const div = document.createElement('div');
21168        div.textContent = text;
21169        return div.innerHTML;
21170    }
21171    
21172    function formatDate(dateStr) {
21173        try {
21174            const date = new Date(dateStr);
21175            const options = { year: 'numeric', month: 'long', day: 'numeric' };
21176            return date.toLocaleDateString('id-ID', options);
21177        } catch (e) {
21178            return dateStr;
21179        }
21180    }
21181    
21182    function getScoreBadge(score, type) {
21183        let category = '';
21184        let label = '';
21185        
21186        if (type === 'antutu') {
21187            if (score >= 500000) {
21188                category = 'excellent';
21189                label = 'Flagship';
21190            } else if (score >= 350000) {
21191                category = 'good';
21192                label = 'High-End';
21193            } else if (score >= 200000) {
21194                category = 'average';
21195                label = 'Mid-Range';
21196            } else {
21197                category = 'average';
21198                label = 'Entry-Level';
21199            }
21200        }
21201        
21202        if (label) {
21203            return '<div class="benchmark-score-badge ' + category + '">' + label + '</div>';
21204        }
21205        
21206        return '';
21207    }
21208})();
21209
21210</script>
21210
21211  
21211<script data-inlined="product-compare.js">
21212(function() {
21213  'use strict';
21214  
21215  // State management
21216  let compareList = [];
21217  let isAnalyzing = false;
21218  let currentAnalysis = null;
21219  
21220  // DOM elements
21221  const compareBtn = document.getElementById('compareBtn');
21222  const compareSidebar = document.getElementById('compareSidebar');
21223  const compareOverlay = document.getElementById('compareOverlay');
21224  const compareClose = document.getElementById('compareClose');
21225  const compareModal = document.getElementById('aiCompareModal');
21226  const compareModalClose = document.getElementById('aiCompareModalClose');
21227  
21228  // Initialize
21229  document.addEventListener('DOMContentLoaded', async function() {
21230    await initializeCompare();
21231    setupEventListeners();
21232  });
21233  
21234  /**
21235   * Initialize compare functionality
21236   */
21237  async function initializeCompare() {
21238    // Load compare list first before setting up buttons
21239    await loadCompareList();
21240    
21241    // Setup product detail compare button if exists
21242    const productCompareBtn = document.getElementById('pd-compare-btn');
21243    
21244    if (productCompareBtn) {
21245      setupProductCompareButton(productCompareBtn);
21246    }
21247    
21248    // Check for existing compare list and update UI
21249    if (compareList.length > 0) {
21250      // Only update badge and buttons, no sticky bar
21251    }
21252  }
21253  
21254  /**
21255   * Setup event listeners
21256   */
21257  function setupEventListeners() {
21258    // Navigation compare button
21259    if (compareBtn) {
21260      compareBtn.addEventListener('click', toggleCompareSidebar);
21261    }
21262    
21263    // Sidebar controls
21264    if (compareClose) {
21265      compareClose.addEventListener('click', closeCompareSidebar);
21266    }
21267    
21268    if (compareOverlay) {
21269      compareOverlay.addEventListener('click', closeCompareSidebar);
21270    }
21271    
21272    // Sidebar actions
21273    const clearAllBtn = document.getElementById('compareClearAll');
21274    if (clearAllBtn) {
21275      clearAllBtn.addEventListener('click', clearAllProducts);
21276    }
21277    
21278    const analyzeBtn = document.getElementById('compareAnalyze');
21279    if (analyzeBtn) {
21280      analyzeBtn.addEventListener('click', startAIAnalysis);
21281    }
21282    
21283    // Modal controls
21284    if (compareModalClose) {
21285      compareModalClose.addEventListener('click', closeModal);
21286    }
21287    
21288    const modalBackdrop = compareModal?.querySelector('.modal-backdrop');
21289    if (modalBackdrop) {
21290      modalBackdrop.addEventListener('click', closeModal);
21291    }
21292    
21293    // Modal actions
21294    const retryBtn = document.getElementById('aiRetry');
21295    if (retryBtn) {
21296      retryBtn.addEventListener('click', startAIAnalysis);
21297    }
21298    
21299    // Removed share and add to cart buttons - replaced with AI disclaimer
21300  }
21301  
21302  /**
21303   * Setup product detail compare button
21304   */
21305  function setupProductCompareButton(button) {
21306    const productId = parseInt(button.dataset.productId);
21307    if (!productId) return;
21308    
21309    // Check if product is already in compare list
21310    updateProductCompareButtonState(button, productId);
21311    
21312    button.addEventListener('click', async function() {
21313      // Get current variant ID (if any)
21314      const variantIdElement = document.getElementById('pd-variant-id');
21315      const variantId = variantIdElement ? parseInt(variantIdElement.value) || 0 : 0;
21316      
21317      // Update button's variant data for current selection
21318      button.dataset.variantId = variantId.toString();
21319      
21320      await toggleProductInCompare(productId, button, variantId);
21321    });
21322  }
21323  
21324  /**
21325   * Toggle product in compare list
21326   */
21327  async function toggleProductInCompare(productId, button, variantId = 0) {
21328    // Check by product ID (variants of same product treated as same item)
21329    const isInCompare = compareList.some(p => parseInt(p.id) === parseInt(productId));
21330    
21331    try {
21332      if (isInCompare) {
21333        await removeFromCompare(productId);
21334        showToast('Produk dihapus dari perbandingan', 'success');
21335      } else {
21336        await addToCompare(productId, variantId);
21337        showToast('Produk ditambahkan ke perbandingan', 'success');
21338        
21339        // Trigger fly-to-compare animation
21340        triggerFlyToCompareAnimation(button);
21341      }
21342      
21343      updateProductCompareButtonState(button, productId);
21344      updateCompareBadge();
21345      updateAllCompareButtons();
21346      
21347    } catch (error) {
21348      showToast(error.message || 'Terjadi kesalahan', 'error');
21349    }
21350  }
21351  
21352  /**
21353   * Update product compare button state
21354   */
21355  function updateProductCompareButtonState(button, productId) {
21356    // Ensure both IDs are compared as integers
21357    const isInCompare = compareList.some(p => parseInt(p.id) === parseInt(productId));
21358    button.classList.toggle('added', isInCompare);
21359    
21360    // Handle visibility of text elements
21361    const compareText = button.querySelector('.compare-text');
21362    const compareRemove = button.querySelector('.compare-remove');
21363    const compareNote = document.getElementById('compare-note');
21364    
21365    if (compareText) {
21366      compareText.style.display = isInCompare ? 'none' : 'inline';
21367    }
21368    
21369    if (compareRemove) {
21370      if (isInCompare) {
21371        compareRemove.removeAttribute('hidden');
21372        compareRemove.style.display = 'flex';
21373      } else {
21374        compareRemove.setAttribute('hidden', '');
21375        compareRemove.style.display = 'none';
21376      }
21377    }
21378    
21379    // Update info note
21380    if (compareNote) {
21381      if (isInCompare) {
21382        const currentCount = compareList.length;
21383        compareNote.textContent = `Produk sudah ada di daftar perbandingan (${currentCount}/5). Klik tombol untuk menghapus.`;
21384      } else {
21385        compareNote.textContent = 'Bandingkan hingga 5 produk dengan bantuan AI';
21386      }
21387    }
21388    
21389    // Inline compare preview removed - keeping it simple
21390  }
21391  
21392  /**
21393   * Add product to compare list
21394   */
21395  async function addToCompare(productId, variantId = 0) {
21396    const requestData = { product_id: productId };
21397    if (variantId > 0) {
21398      requestData.variant_id = variantId;
21399    }
21400    
21401    const response = await fetch('/api/compare/add.php', {
21402      method: 'POST',
21403      headers: {
21404        'Content-Type': 'application/json',
21405      },
21406      body: JSON.stringify(requestData),
21407      credentials: 'same-origin'
21408    });
21409    
21410    const result = await response.json();
21411    
21412    if (!result.success) {
21413      throw new Error(result.error || 'Gagal menambahkan produk');
21414    }
21415    
21416    // Send Google Analytics event for add to compare
21417    if (typeof gtag === 'function') {
21418      gtag('event', 'add_to_compare', {
21419        event_category: 'Product Compare',
21420        event_label: 'Product Added to Compare',
21421        product_id: productId,
21422        variant_id: variantId || 0,
21423        custom_map: {
21424          custom_parameter_1: 'compare_add_product'
21425        }
21426      });
21427    }
21428    
21429    await loadCompareList();
21430    return result;
21431  }
21432  
21433  /**
21434   * Remove product from compare list
21435   */
21436  async function removeFromCompare(productId) {
21437    const response = await fetch('/api/compare/remove.php', {
21438      method: 'POST',
21439      headers: {
21440        'Content-Type': 'application/json',
21441      },
21442      body: JSON.stringify({ product_id: productId }),
21443      credentials: 'same-origin'
21444    });
21445    
21446    const result = await response.json();
21447    
21448    if (!result.success) {
21449      throw new Error(result.error || 'Gagal menghapus produk');
21450    }
21451    
21452    // Send Google Analytics event for remove from compare
21453    if (typeof gtag === 'function') {
21454      gtag('event', 'remove_from_compare', {
21455        event_category: 'Product Compare',
21456        event_label: 'Product Removed from Compare',
21457        product_id: productId,
21458        custom_map: {
21459          custom_parameter_1: 'compare_remove_product'
21460        }
21461      });
21462    }
21463    
21464    await loadCompareList();
21465    return result;
21466  }
21467  
21468  /**
21469   * Clear all products from compare list
21470   */
21471  async function clearAllProducts() {
21472    if (compareList.length === 0) return;
21473    
21474    // Show confirmation modal
21475    const confirmed = await showConfirmModal({
21476      title: 'Hapus Semua Produk',
21477      message: 'Apakah Anda yakin ingin menghapus semua produk dari daftar perbandingan?',
21478      confirmText: 'Hapus Semua',
21479      cancelText: 'Batal',
21480      type: 'danger'
21481    });
21482    
21483    if (!confirmed) return;
21484    
21485    try {
21486      const response = await fetch('/api/compare/clear.php', {
21487        method: 'POST',
21488        credentials: 'same-origin'
21489      });
21490      
21491      const result = await response.json();
21492      
21493      if (!result.success) {
21494        throw new Error(result.error || 'Gagal menghapus produk');
21495      }
21496      
21497      // Send Google Analytics event for clear all products
21498      if (typeof gtag === 'function') {
21499        gtag('event', 'compare_clear_all', {
21500          event_category: 'Product Compare',
21501          event_label: 'Clear All Products',
21502          products_count: compareList.length,
21503          custom_map: {
21504            custom_parameter_1: 'compare_clear_action'
21505          }
21506        });
21507      }
21508      
21509      await loadCompareList();
21510      showToast('Semua produk dihapus dari perbandingan', 'success');
21511      
21512    } catch (error) {
21513      showToast(error.message || 'Terjadi kesalahan', 'error');
21514    }
21515  }
21516  
21517  /**
21518   * Load compare list from server
21519   */
21520  async function loadCompareList() {
21521    try {
21522      const response = await fetch('/api/compare/get.php', {
21523        credentials: 'same-origin'
21524      });
21525      
21526      const result = await response.json();
21527      
21528      if (result.success) {
21529        compareList = result.products || [];
21530        updateCompareSidebar();
21531        updateCompareBadge();
21532        updateAllCompareButtons();
21533      }
21534      
21535    } catch (error) {
21536      // Silently handle error - compare functionality will work with empty list
21537    }
21538  }
21539  
21540  /**
21541   * Update compare sidebar content
21542   */
21543  function updateCompareSidebar() {
21544    const emptyState = document.getElementById('compareEmpty');
21545    const listContainer = document.getElementById('compareList');
21546    const actionsContainer = document.getElementById('compareActions');
21547    const countElement = document.getElementById('compareCountSidebar');
21548    
21549    if (!emptyState || !listContainer || !actionsContainer || !countElement) return;
21550    
21551    // Update count
21552    countElement.textContent = `(${compareList.length})`;
21553    
21554    if (compareList.length === 0) {
21555      emptyState.style.display = 'block';
21556      listContainer.style.display = 'none';
21557      actionsContainer.style.display = 'none';
21558    } else {
21559      emptyState.style.display = 'none';
21560      listContainer.style.display = 'block';
21561      actionsContainer.style.display = 'flex';
21562      
21563      // Render product list
21564      listContainer.innerHTML = compareList.map(product => {
21565        // Display variant name if available
21566        let displayName = product.name;
21567        if (product.combination_name) {
21568          displayName += ` (${product.combination_name})`;
21569        }
21570        
21571        // Use variant price if available
21572        let displayPrice = product.price;
21573        if (product.variant_price !== undefined && product.variant_price !== null) {
21574          displayPrice = product.variant_price;
21575        }
21576        
21577        return `
21578          <div class="compare-item" data-product-id="${product.id}">
21579            <div class="compare-item-image">
21580              <img src="${product.image_url}" alt="${escapeHtml(displayName)}" loading="lazy">
21581            </div>
21582            <div class="compare-item-info">
21583              <div class="compare-item-name">${escapeHtml(displayName)}</div>
21584              <div class="compare-item-price">${formatPrice(displayPrice)}</div>
21585            </div>
21586            <button class="compare-item-remove" data-product-id="${product.id}" aria-label="Hapus">
21587              <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
21588                <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"/>
21589              </svg>
21590            </button>
21591          </div>
21592        `;
21593      }).join('');
21594      
21595      // Setup event listeners for remove buttons
21596      setupRemoveButtons();
21597      
21598      // Update analyze button state
21599      const analyzeBtn = document.getElementById('compareAnalyze');
21600      if (analyzeBtn) {
21601        analyzeBtn.disabled = compareList.length < 2;
21602      }
21603    }
21604  }
21605  
21606  /**
21607   * Setup remove buttons event listeners
21608   */
21609  function setupRemoveButtons() {
21610    const removeButtons = document.querySelectorAll('.compare-item-remove');
21611    removeButtons.forEach(button => {
21612      button.addEventListener('click', async (e) => {
21613        e.preventDefault();
21614        const productId = parseInt(button.dataset.productId);
21615        if (productId) {
21616          await removeProduct(productId);
21617        }
21618      });
21619    });
21620  }
21621
21622  /**
21623   * Update all compare buttons on the page
21624   */
21625  function updateAllCompareButtons() {
21626    // Update product detail compare button
21627    const productBtn = document.getElementById('pd-compare-btn');
21628    if (productBtn) {
21629      const productId = parseInt(productBtn.dataset.productId);
21630      if (productId) {
21631        updateProductCompareButtonState(productBtn, productId);
21632      }
21633    }
21634    
21635    // Update any other compare buttons on the page
21636    const allCompareButtons = document.querySelectorAll('[data-product-id][class*="compare"]');
21637    allCompareButtons.forEach(button => {
21638      const productId = parseInt(button.dataset.productId);
21639      if (productId && button.id !== 'pd-compare-btn') {
21640        updateProductCompareButtonState(button, productId);
21641      }
21642    });
21643  }
21644
21645  /**
21646   * Update compare badge
21647   */
21648  function updateCompareBadge() {
21649    const badge = document.getElementById('compareCount');
21650    if (!badge) return;
21651    
21652    const oldCount = parseInt(badge.textContent) || 0;
21653    const newCount = compareList.length;
21654    
21655    badge.textContent = newCount;
21656    badge.classList.toggle('active', newCount > 0);
21657    
21658    // Animate badge if count increased
21659    if (newCount > oldCount) {
21660      animateCompareBadge();
21661      highlightCompareButton();
21662    }
21663    
21664    // Sticky bar removed - keeping only fly-to-navbar animation
21665  }
21666  
21667  /**
21668   * Animate compare badge when new product added
21669   */
21670  function animateCompareBadge() {
21671    const badge = document.getElementById('compareCount');
21672    if (!badge) return;
21673    
21674    badge.style.transform = 'scale(1.3)';
21675    badge.style.transition = 'transform 0.3s ease';
21676    
21677    setTimeout(() => {
21678      badge.style.transform = 'scale(1)';
21679    }, 300);
21680  }
21681  
21682  /**
21683   * Highlight compare button temporarily
21684   */
21685  function highlightCompareButton() {
21686    const compareBtn = document.getElementById('compareBtn');
21687    if (!compareBtn) return;
21688    
21689    compareBtn.classList.add('pulse-highlight');
21690    
21691    setTimeout(() => {
21692      compareBtn.classList.remove('pulse-highlight');
21693    }, 3000);
21694  }
21695  
21696  /**
21697   * Trigger fly-to-compare animation
21698   */
21699  function triggerFlyToCompareAnimation(sourceButton) {
21700    if (!sourceButton) {
21701      return;
21702    }
21703    
21704    // Check if mobile view (bottom nav) or desktop (top nav)
21705    const isMobile = window.innerWidth <= 768;
21706    
21707    // Get source position
21708    const sourceRect = sourceButton.getBoundingClientRect();
21709    
21710    // Calculate target position
21711    let targetTop, targetLeft;
21712    
21713    if (isMobile) {
21714      // Mobile: fly to fixed bottom position (bottom nav area)
21715      // Use fixed coordinates regardless of bottom nav visibility
21716      targetTop = window.innerHeight - 80; // Just above bottom nav area
21717      targetLeft = window.innerWidth / 2 - 30; // Center horizontally
21718    } else {
21719      // Desktop: fly to top navbar compare button
21720      const targetElement = document.getElementById('compareBtn');
21721      if (!targetElement) {
21722        return;
21723      }
21724      const targetRect = targetElement.getBoundingClientRect();
21725      targetTop = targetRect.top + 10;
21726      targetLeft = targetRect.left + 10;
21727    }
21728    
21729    // Get product image for animation - try multiple selectors
21730    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"]');
21731    
21732    // Create animated element
21733    const flyingElement = document.createElement('div');
21734    flyingElement.className = 'flying-product';
21735    
21736    if (productImage) {
21737      flyingElement.innerHTML = `<img src="${productImage.src}" alt="Flying product">`;
21738      flyingElement.querySelector('img').style.cssText = `
21739        width: 100%;
21740        height: 100%;
21741        object-fit: cover;
21742        border-radius: 8px;
21743        box-shadow: 0 4px 12px rgba(0,0,0,0.3);
21744      `;
21745    } else {
21746      // Fallback: colored div with icon
21747      flyingElement.innerHTML = `<div class="flying-fallback">📦</div>`;
21748      flyingElement.querySelector('.flying-fallback').style.cssText = `
21749        width: 100%;
21750        height: 100%;
21751        background: #3b82f6;
21752        border-radius: 8px;
21753        display: flex;
21754        align-items: center;
21755        justify-content: center;
21756        font-size: 24px;
21757        box-shadow: 0 4px 12px rgba(0,0,0,0.3);
21758      `;
21759    }
21760    
21761    flyingElement.style.cssText = `
21762      position: fixed;
21763      top: ${sourceRect.top}px;
21764      left: ${sourceRect.left}px;
21765      width: 60px;
21766      height: 60px;
21767      z-index: 9999;
21768      pointer-events: none;
21769      opacity: 1;
21770      transform: scale(1);
21771    `;
21772    
21773    document.body.appendChild(flyingElement);
21774    
21775    // Use Web Animations API for more reliable animation
21776    flyingElement.animate([
21777      // Keyframe 1: Initial state
21778      {
21779        top: `${sourceRect.top}px`,
21780        left: `${sourceRect.left}px`,
21781        transform: 'scale(1)',
21782        opacity: 1
21783      },
21784      // Keyframe 2: Final state
21785      {
21786        top: `${targetTop}px`,
21787        left: `${targetLeft}px`,
21788        transform: 'scale(0.3)',
21789        opacity: 0
21790      }
21791    ], {
21792      duration: 800,
21793      easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
21794      fill: 'forwards'
21795    });
21796    
21797    // Clean up after animation completes
21798    setTimeout(() => {
21799      if (flyingElement.parentNode) {
21800        flyingElement.parentNode.removeChild(flyingElement);
21801      }
21802    }, 850);
21803  }
21804  
21805  // Sticky compare bar functions removed - using only fly-to-navbar animation
21806  
21807  /**
21808   * Toggle compare sidebar
21809   */
21810  function toggleCompareSidebar() {
21811    if (!compareSidebar) return;
21812    
21813    const isOpen = compareSidebar.classList.contains('open');
21814    
21815    if (isOpen) {
21816      closeCompareSidebar();
21817    } else {
21818      openCompareSidebar();
21819    }
21820  }
21821  
21822  /**
21823   * Open compare sidebar
21824   */
21825  function openCompareSidebar() {
21826    if (!compareSidebar || !compareOverlay) return;
21827    
21828    compareSidebar.classList.add('open');
21829    compareOverlay.classList.add('active');
21830    document.body.style.overflow = 'hidden';
21831    
21832    // Send Google Analytics event for compare sidebar open
21833    if (typeof gtag === 'function') {
21834      gtag('event', 'compare_sidebar_open', {
21835        event_category: 'Product Compare',
21836        event_label: 'Compare Sidebar Opened',
21837        products_count: compareList.length,
21838        custom_map: {
21839          custom_parameter_1: 'compare_sidebar_view'
21840        }
21841      });
21842    }
21843  }
21844  
21845  /**
21846   * Close compare sidebar
21847   */
21848  function closeCompareSidebar() {
21849    if (!compareSidebar || !compareOverlay) return;
21850    
21851    compareSidebar.classList.remove('open');
21852    compareOverlay.classList.remove('active');
21853    document.body.style.overflow = '';
21854  }
21855  
21856  /**
21857   * Start AI analysis
21858   */
21859  async function startAIAnalysis() {
21860    if (compareList.length < 2) {
21861      showToast('Minimal 2 produk diperlukan untuk analisis AI', 'warning');
21862      return;
21863    }
21864    
21865    if (isAnalyzing) return;
21866    
21867    // Send Google Analytics event for AI Analysis start
21868    if (typeof gtag === 'function') {
21869      gtag('event', 'ai_analysis_start', {
21870        event_category: 'Product Compare',
21871        event_label: 'AI Analysis Button Click',
21872        products_count: compareList.length,
21873        product_ids: compareList.map(p => p.id).join(','),
21874        custom_map: {
21875          custom_parameter_1: 'compare_ai_analysis'
21876        }
21877      });
21878    }
21879    
21880    // Check if products are in similar categories
21881    const mismatchInfo = await checkProductCategoryMismatch();
21882    if (mismatchInfo.shouldWarn) {
21883      const categoryList = Array.from(mismatchInfo.categories).join(', ');
21884      const confirmed = await showConfirmModal({
21885        title: 'Produk Berbeda Kategori',
21886        message: `Produk yang Anda pilih berasal dari kategori yang berbeda: ${categoryList}. AI tetap bisa menganalisis, tapi perbandingan mungkin kurang relevan. Lanjutkan analisis?`,
21887        confirmText: 'Ya, Analisis Tetap',
21888        cancelText: 'Batal',
21889        type: 'warning'
21890      });
21891      
21892      if (!confirmed) return;
21893    }
21894    
21895    openModal();
21896    showLoadingState();
21897    isAnalyzing = true;
21898    
21899    try {
21900      const response = await fetch('/api/compare/ai-analysis.php', {
21901        method: 'POST',
21902        credentials: 'same-origin'
21903      });
21904      
21905      const result = await response.json();
21906      
21907      if (!result.success) {
21908        throw new Error(result.error || 'Analisis AI gagal');
21909      }
21910      
21911      currentAnalysis = result;
21912      showAnalysisResults(result);
21913      
21914      // Send Google Analytics event for successful AI Analysis
21915      if (typeof gtag === 'function') {
21916        gtag('event', 'ai_analysis_success', {
21917          event_category: 'Product Compare',
21918          event_label: 'AI Analysis Completed',
21919          products_count: compareList.length,
21920          analysis_type: 'product_comparison',
21921          custom_map: {
21922            custom_parameter_1: 'compare_ai_success'
21923          }
21924        });
21925      }
21926      
21927    } catch (error) {
21928      showErrorState(error.message);
21929      
21930      // Send Google Analytics event for failed AI Analysis
21931      if (typeof gtag === 'function') {
21932        gtag('event', 'ai_analysis_error', {
21933          event_category: 'Product Compare',
21934          event_label: 'AI Analysis Failed',
21935          products_count: compareList.length,
21936          error_message: error.message,
21937          custom_map: {
21938            custom_parameter_1: 'compare_ai_error'
21939          }
21940        });
21941      }
21942    } finally {
21943      isAnalyzing = false;
21944    }
21945  }
21946  
21947  /**
21948   * Show modal
21949   */
21950  function openModal() {
21951    if (!compareModal) return;
21952    compareModal.classList.add('open');
21953    document.body.style.overflow = 'hidden';
21954  }
21955  
21956  /**
21957   * Close modal
21958   */
21959  function closeModal() {
21960    if (!compareModal) return;
21961    compareModal.classList.remove('open');
21962    document.body.style.overflow = '';
21963  }
21964  
21965  /**
21966   * Show loading state
21967   */
21968  function showLoadingState() {
21969    const loading = document.getElementById('aiLoading');
21970    const error = document.getElementById('aiError');
21971    const results = document.getElementById('aiResults');
21972    
21973    if (loading) loading.style.display = 'block';
21974    if (error) error.hidden = true;
21975    if (results) results.hidden = true;
21976  }
21977  
21978  /**
21979   * Show error state
21980   */
21981  function showErrorState(message) {
21982    const loading = document.getElementById('aiLoading');
21983    const error = document.getElementById('aiError');
21984    const results = document.getElementById('aiResults');
21985    const errorMessage = document.getElementById('aiErrorMessage');
21986    
21987    if (loading) loading.style.display = 'none';
21988    if (error) error.hidden = false;
21989    if (results) results.hidden = true;
21990    if (errorMessage) errorMessage.textContent = message;
21991  }
21992  
21993  /**
21994   * Show analysis results
21995   */
21996  function showAnalysisResults(analysis) {
21997    const loading = document.getElementById('aiLoading');
21998    const error = document.getElementById('aiError');
21999    const results = document.getElementById('aiResults');
22000    
22001    if (loading) loading.style.display = 'none';
22002    if (error) error.hidden = true;
22003    if (results) results.hidden = false;
22004    
22005    const data = analysis.data;
22006    
22007    // Update summary
22008    const summaryElement = document.getElementById('aiSummary');
22009    if (summaryElement) summaryElement.textContent = data.summary || '';
22010    
22011    const winnerProduct = document.getElementById('winnerProduct');
22012    const winnerReason = document.getElementById('winnerReason');
22013    if (winnerProduct && winnerReason && data.winner_overall) {
22014      const winner = compareList.find(p => parseInt(p.id) === parseInt(data.winner_overall));
22015      winnerProduct.textContent = winner ? winner.name : 'Tidak diketahui';
22016      winnerReason.textContent = data.winner_reason || '';
22017    }
22018    
22019    // Update comparison table
22020    updateComparisonTable(data.comparison_table);
22021    
22022    // Update detailed analysis
22023    updateDetailedAnalysis(data.detailed_analysis);
22024    
22025    // Update recommendations
22026    updateRecommendations(data.recommendations);
22027    
22028    // Update buying tips
22029    updateBuyingTips(data.buying_tips);
22030  }
22031  
22032  /**
22033   * Update comparison table
22034   */
22035  function updateComparisonTable(comparisonData) {
22036    const container = document.getElementById('comparisonTable');
22037    if (!container || !comparisonData?.categories) return;
22038    
22039    // First, create product header row with names
22040    const productHeaderHtml = `
22041      <div class="comparison-category">
22042        <div class="category-header">Produk</div>
22043        <div class="category-products">
22044          ${compareList.map(product => `
22045            <div class="product-score">
22046              <div class="product-name-header">
22047                <strong>${escapeHtml(product.name)}</strong>
22048              </div>
22049              <div class="product-price">
22050                ${formatPrice(product.price)}
22051              </div>
22052            </div>
22053          `).join('')}
22054        </div>
22055      </div>
22056    `;
22057    
22058    // Then create comparison categories
22059    const categoriesHtml = comparisonData.categories.map(category => `
22060      <div class="comparison-category">
22061        <div class="category-header">${escapeHtml(category.name)}</div>
22062        <div class="category-products">
22063          ${compareList.map(product => {
22064            const productData = category.products?.[product.id];
22065            return `
22066              <div class="product-score">
22067                <div class="score-value">
22068                  <span class="score-stars">${'★'.repeat(productData?.score || 0)}${'☆'.repeat(5 - (productData?.score || 0))}</span>
22069                  <span>${productData?.score || 0}/5</span>
22070                </div>
22071                <div class="score-note">${escapeHtml(productData?.note || '')}</div>
22072              </div>
22073            `;
22074          }).join('')}
22075        </div>
22076      </div>
22077    `).join('');
22078    
22079    container.innerHTML = productHeaderHtml + categoriesHtml;
22080  }
22081  
22082  /**
22083   * Update detailed analysis
22084   */
22085  function updateDetailedAnalysis(analysisData) {
22086    const container = document.getElementById('detailedAnalysis');
22087    if (!container || !analysisData) return;
22088    
22089    container.innerHTML = compareList.map(product => {
22090      const productAnalysis = analysisData[product.id];
22091      if (!productAnalysis) return '';
22092      
22093      // Use product name from API response if available, fallback to local name
22094      const productName = productAnalysis.product_name || product.name;
22095      
22096      return `
22097        <div class="product-analysis">
22098          <h5>${escapeHtml(productName)}</h5>
22099          
22100          ${productAnalysis.strengths ? `
22101            <div class="analysis-section">
22102              <h6 class="strengths">✅ Kelebihan</h6>
22103              <ul>
22104                ${productAnalysis.strengths.map(strength => 
22105                  `<li>${escapeHtml(strength)}</li>`
22106                ).join('')}
22107              </ul>
22108            </div>
22109          ` : ''}
22110          
22111          ${productAnalysis.weaknesses ? `
22112            <div class="analysis-section">
22113              <h6 class="weaknesses">❌ Kekurangan</h6>
22114              <ul>
22115                ${productAnalysis.weaknesses.map(weakness => 
22116                  `<li>${escapeHtml(weakness)}</li>`
22117                ).join('')}
22118              </ul>
22119            </div>
22120          ` : ''}
22121          
22122          ${productAnalysis.best_for ? `
22123            <div class="analysis-section">
22124              <h6 class="best-for">🎯 Cocok Untuk</h6>
22125              <p class="best-for-text">${escapeHtml(productAnalysis.best_for)}</p>
22126            </div>
22127          ` : ''}
22128        </div>
22129      `;
22130    }
22130).filter(Boolean).join('');
22131  }
22132  
22133  /**
22134   * Update recommendations
22135   */
22136  function updateRecommendations(recommendations) {
22137    if (!recommendations) return;
22138    
22139    const budgetEl = document.getElementById('budgetRecommendation');
22140    const performanceEl = document.getElementById('performanceRecommendation');
22141    const balancedEl = document.getElementById('balancedRecommendation');
22142    
22143    if (budgetEl && recommendations.budget_conscious) {
22144      const product = compareList.find(p => parseInt(p.id) === parseInt(recommendations.budget_conscious));
22145      budgetEl.textContent = product ? product.name : 'Tidak tersedia';
22146    }
22147    
22148    if (performanceEl && recommendations.performance_focused) {
22149      const product = compareList.find(p => parseInt(p.id) === parseInt(recommendations.performance_focused));
22150      performanceEl.textContent = product ? product.name : 'Tidak tersedia';
22151    }
22152    
22153    if (balancedEl && recommendations.balanced) {
22154      const product = compareList.find(p => parseInt(p.id) === parseInt(recommendations.balanced));
22155      balancedEl.textContent = product ? product.name : 'Tidak tersedia';
22156    }
22157  }
22158  
22159  /**
22160   * Check if products are from different categories
22161   */
22162  async function checkProductCategoryMismatch() {
22163    if (compareList.length < 2) {
22164      return { shouldWarn: false, categories: new Set() };
22165    }
22166    
22167    // Get the root categories from each product
22168    const rootCategories = new Set();
22169    const productCategories = [];
22170    
22171    for (const product of compareList) {
22172      // Extract root category from the product's category_path if available
22173      const rootCategory = await getProductRootCategory(product);
22174      if (rootCategory) {
22175        rootCategories.add(rootCategory);
22176        productCategories.push({
22177          product: product.name,
22178          category: rootCategory
22179        });
22180      }
22181    }
22182    
22183    // If we have more than 1 different root categories, show warning
22184    const shouldWarn = rootCategories.size > 1 && compareList.length >= 2;
22185    
22186    return {
22187      shouldWarn: shouldWarn,
22188      categories: rootCategories,
22189      productCategories: productCategories
22190    };
22191  }
22192  
22193  /**
22194   * Get product root category from category_path or fallback to name detection
22195   */
22196  async function getProductRootCategory(product) {
22197    // First try to get from category_path if available
22198    if (product.category_path) {
22199      const rootCategory = product.category_path.split('/')[0];
22200      return rootCategory;
22201    }
22202    
22203    // If no category_path, try to fetch from product detail or use name detection
22204    const detectedCategory = detectProductCategory(product.name);
22205    return detectedCategory;
22206  }
22207
22208  /**
22209   * Detect product category from name (fallback method)
22210   */
22211  function detectProductCategory(productName) {
22212    const name = productName.toLowerCase();
22213    
22214    // Map keywords to actual root categories from the API structure
22215    const categoryMappings = {
22216      // Komputer & Laptop
22217      'Komputer & Laptop': ['laptop', 'notebook', 'macbook', 'thinkpad', 'gaming laptop', 'desktop', 'pc', 'mini pc', 'all-in-one', 'server'],
22218      
22219      // Printer & Scanner  
22220      'Printer & Scanner': ['printer', 'epson printer', 'canon printer', 'hp printer', 'brother printer', 'scanner', 'laser printer', 'inkjet', 'dot matrix'],
22221      
22222      // Gadget
22223      'Gadget': ['smartphone', 'iphone', 'samsung phone', 'xiaomi', 'oppo', 'vivo', 'realme', 'tablet', 'ipad', 'android tablet'],
22224      
22225      // Periferal
22226      'Periferal': ['monitor', 'lcd monitor', 'led monitor', 'gaming monitor', 'keyboard', 'mouse', 'gaming keyboard', 'mechanical keyboard', 'webcam'],
22227      
22228      // Audio
22229      'Audio': ['headset', 'headphone', 'earphone', 'gaming headset', 'speaker', 'mikrofon', 'sound card'],
22230      
22231      // Jaringan
22232      'Jaringan': ['router', 'wifi', 'access point', 'switch', 'modem', 'network'],
22233      
22234      // Penyimpanan
22235      'Penyimpanan': ['ssd', 'hdd', 'nvme', 'solid state', 'hard disk', 'flash drive', 'usb drive', 'memory card'],
22236      
22237      // Komponen PC
22238      'Komponen PC': ['ram', 'memory', 'ddr4', 'ddr5', 'processor', 'cpu', 'intel', 'amd', 'motherboard', 'graphics card', 'gpu', 'psu', 'power supply', 'casing'],
22239      
22240      // UPS & Power
22241      'UPS & Power': ['ups', 'uninterruptible', 'stabilizer', 'power supply'],
22242      
22243      // Proyektor
22244      'Proyektor': ['proyektor', 'projector', 'layar proyektor'],
22245      
22246      // Security System
22247      'Security System': ['cctv', 'security camera', 'surveillance'],
22248      
22249      // Aksesoris
22250      'Aksesoris': ['charger laptop', 'adaptor', 'converter', 'dock', 'hub', 'kabel', 'cable', 'power bank', 'tas laptop'],
22251      
22252      // Software
22253      'Software': ['software', 'lisensi', 'license', 'antivirus'],
22254      
22255      // Perlengkapan Kantor
22256      'Perlengkapan Kantor': ['fingerprint', 'finger print', 'atk', 'alat tulis']
22257    };
22258    
22259    // Check which root category matches
22260    for (const [rootCategory, keywords] of Object.entries(categoryMappings)) {
22261      for (const keyword of keywords) {
22262        if (name.includes(keyword)) {
22263          return rootCategory;
22264        }
22265      }
22266    }
22267    
22268    return null;
22269  }
22270
22271  /**
22272   * Update buying tips
22273   */
22274  function updateBuyingTips(tips) {
22275    const container = document.getElementById('buyingTips');
22276    if (!container || !Array.isArray(tips)) return;
22277    
22278    container.innerHTML = tips.map(tip => 
22279      `<li>${escapeHtml(tip)}</li>`
22280    ).join('');
22281  }
22282  
22283  // Removed share and cart functions - replaced with AI disclaimer in UI
22284  
22285  /**
22286   * Remove product from compare (public method)
22287   */
22288  async function removeProduct(productId) {
22289    try {
22290      await removeFromCompare(productId);
22291      showToast('Produk dihapus dari perbandingan', 'success');
22292      
22293      // Update all compare buttons
22294      updateAllCompareButtons();
22295      
22296    } catch (error) {
22297      showToast(error.message || 'Terjadi kesalahan', 'error');
22298    }
22299  }
22300  
22301  /**
22302   * Utility functions
22303   */
22304  function escapeHtml(text) {
22305    const div = document.createElement('div');
22306    div.textContent = text;
22307    return div.innerHTML;
22308  }
22309  
22310  function formatPrice(price) {
22311    return 'Rp ' + parseInt(price).toLocaleString('id-ID');
22312  }
22313  
22314  /**
22315   * Show confirmation modal using the existing modal.css design
22316   */
22317  function showConfirmModal(options = {}) {
22318    return new Promise((resolve) => {
22319      const {
22320        title = 'Konfirmasi',
22321        message = 'Apakah Anda yakin?',
22322        confirmText = 'Ya',
22323        cancelText = 'Tidak',
22324        type = 'warning'
22325      } = options;
22326      
22327      // Create modal HTML
22328      const modalHTML = `
22329        <div class="modal-overlay" id="confirmModal">
22330          <div class="modal-dialog">
22331            <div class="modal-header">
22332              <h3 class="modal-title">
22333                <div class="modal-icon ${type}">
22334                  ${getModalIcon(type)}
22335                </div>
22336                ${escapeHtml(title)}
22337              </h3>
22338            </div>
22339            <div class="modal-body">
22340              <p class="modal-message">${escapeHtml(message)}</p>
22341              <div class="modal-actions">
22342                <button type="button" class="modal-btn modal-btn-secondary" id="confirmCancel">
22343                  ${escapeHtml(cancelText)}
22344                </button>
22345                <button type="button" class="modal-btn modal-btn-${type === 'danger' ? 'danger' : 'primary'}" id="confirmOk">
22346                  ${escapeHtml(confirmText)}
22347                </button>
22348              </div>
22349            </div>
22350          </div>
22351        </div>
22352      `;
22353      
22354      // Add to DOM
22355      document.body.insertAdjacentHTML('beforeend', modalHTML);
22356      const modal = document.getElementById('confirmModal');
22357      const cancelBtn = document.getElementById('confirmCancel');
22358      const okBtn = document.getElementById('confirmOk');
22359      
22360      // Show modal
22361      requestAnimationFrame(() => {
22362        modal.classList.add('show');
22363      });
22364      
22365      // Handle events
22366      function cleanup() {
22367        modal.remove();
22368      }
22369      
22370      function handleCancel() {
22371        modal.classList.remove('show');
22372        setTimeout(() => {
22373          cleanup();
22374          resolve(false);
22375        }, 200);
22376      }
22377      
22378      function handleConfirm() {
22379        modal.classList.remove('show');
22380        setTimeout(() => {
22381          cleanup();
22382          resolve(true);
22383        }, 200);
22384      }
22385      
22386      cancelBtn.addEventListener('click', handleCancel);
22387      okBtn.addEventListener('click', handleConfirm);
22388      
22389      // Close on overlay click
22390      modal.addEventListener('click', (e) => {
22391        if (e.target === modal) {
22392          handleCancel();
22393        }
22394      });
22395      
22396      // Close on escape
22397      document.addEventListener('keydown', function escapeHandler(e) {
22398        if (e.key === 'Escape') {
22399          document.removeEventListener('keydown', escapeHandler);
22400          handleCancel();
22401        }
22402      });
22403      
22404      // Focus on confirm button
22405      setTimeout(() => okBtn.focus(), 100);
22406    });
22407  }
22408  
22409  /**
22410   * Get icon for modal type
22411   */
22412  function getModalIcon(type) {
22413      const icons = {
22414        danger: '🚨 ',
22415        warning: '⚠️',
22416        success: '✅',
22417        info: 'ℹ️'
22418      };
22419      return icons[type] || icons.info;
22420    }
22421
22422
22423  function showToast(message, type = 'info') {
22424    // Integration with existing toast system or create simple one
22425    
22426    // Simple alert fallback - replace with proper toast system
22427    if (type === 'error') {
22428      alert('Error: ' + message);
22429    }
22430  }
22431  
22432  // Inline compare preview functions removed - keeping it simple and clean
22433  
22434  /**
22435   * Get current product ID from page
22436   */
22437  function getCurrentProductId() {
22438    const button = document.getElementById('pd-compare-btn');
22439    return button ? parseInt(button.dataset.productId) : null;
22440  }
22441  
22442  // hideStickyCompareBar function removed - no longer using sticky bar
22443
22444  // Public API
22445  window.productCompare = {
22446    removeProduct: removeProduct,
22447    addToCompare: addToCompare,
22448    loadCompareList: loadCompareList,
22449    getCompareList: function() { return compareList; },
22450    openSidebar: openCompareSidebar,
22451    startAnalysis: startAIAnalysis,
22452    // Helper function to add from external sources with variant support
22453    addProductWithVariant: async function(productId, variantId = 0) {
22454      return await addToCompare(productId, variantId);
22455    }
22456  };
22457
22458})();
22459
22460
22461
22462</script>
vendor: 1 bytes, line 22462
22462
22463<script data-inlined="product-card-actions.js">
22464/**
22465 * Product Card Quick Actions
22466 * Bottom sheet menu for quick add to cart & compare
22467 * Integrates with existing product-compare.js and cart.js
22468 */
22469
22470(function() {
22471  'use strict';
22472  
22473  let activeSheet = null;
22474  let activeProductData = null;
22475  
22476  // DOM elements (will be created dynamically)
22477  let overlay = null;
22478  let bottomSheet = null;
22479  
22480  /**
22481   * Initialize on DOM ready
22482   */
22483  document.addEventListener('DOMContentLoaded', function() {
22484    createBottomSheet();
22485    attachEventListeners();
22486  });
22487  
22488  /**
22489   * Create bottom sheet HTML structure
22490   */
22491  function createBottomSheet() {
22492    // Create overlay
22493    overlay = document.createElement('div');
22494    overlay.className = 'pca-overlay';
22495    overlay.addEventListener('click', closeBottomSheet);
22496    
22497    // Create bottom sheet
22498    bottomSheet = document.createElement('div');
22499    bottomSheet.className = 'pca-bottom-sheet';
22500    bottomSheet.innerHTML = `
22501      <div class="pca-sheet-handle"></div>
22502      <div class="pca-sheet-product" id="pca-product-preview">
22503        <!-- Product info will be inserted here -->
22504      </div>
22505      <div class="pca-sheet-actions">
22506        <button type="button" class="pca-action-item cart" id="pca-add-cart">
22507          <svg class="pca-action-icon" viewBox="0 0 24 24" fill="currentColor">
22508            <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"/>
22509          </svg>
22510          <div class="pca-action-content">
22511            <div class="pca-action-label">Tambah ke Keranjang</div>
22512            <div class="pca-action-desc">Lanjut belanja produk lain</div>
22513          </div>
22514        </button>
22515        
22516        <button type="button" class="pca-action-item compare" id="pca-add-compare">
22517          <svg class="pca-action-icon" viewBox="0 0 24 24" fill="currentColor">
22518            <path d="M21,9L17,5V8H10V10H17V13M7,11L3,15L7,19V16H14V14H7V11Z"/>
22519          </svg>
22520          <div class="pca-action-content">
22521            <div class="pca-action-label">Bandingkan Produk</div>
22522            <div class="pca-action-desc">Analisis dengan AI</div>
22523          </div>
22524          <span class="pca-action-badge compare" id="pca-compare-count" style="display:none;">0</span>
22525        </button>
22526      </div>
22527    `;
22528    
22529    // Append to body
22530    document.body.appendChild(overlay);
22531    document.body.appendChild(bottomSheet);
22532    
22533    // Setup action buttons
22534    document.getElementById('pca-add-cart').addEventListener('click', handleAddToCart);
22535    document.getElementById('pca-add-compare').addEventListener('click', handleAddToCompare);
22536    
22537    // Prevent clicks inside bottom sheet from closing it
22538    bottomSheet.addEventListener('click', function(e) {
22539      e.stopPropagation();
22540    });
22541  }
22542  
22543  /**
22544   * Attach event listeners to all 3-dot buttons
22545   */
22546  function attachEventListeners() {
22547    // Use event delegation for dynamically loaded product cards
22548    document.addEventListener('click', function(e) {
22549      const btn = e.target.closest('.prod-card-actions-btn');
22550      if (btn) {
22551        e.preventDefault();
22552        e.stopPropagation();
22553        handleActionsButtonClick(btn);
22554      }
22555    });
22556    
22557    // Handle ESC key
22558    document.addEventListener('keydown', function(e) {
22559      if (e.key === 'Escape' && activeSheet) {
22560        closeBottomSheet();
22561      }
22562    });
22563  }
22564  
22565  /**
22566   * Handle 3-dot button click
22567   */
22568  function handleActionsButtonClick(button) {
22569    // Get product ID directly from button
22570    const productId = parseInt(button.dataset.productId) || 0;
22571    if (!productId) {
22572      console.error('Product ID not found on button:', button);
22573      showToast('Data produk tidak ditemukan', 'error');
22574      return;
22575    }
22576    
22577    // Find parent card for additional data
22578    const card = button.closest('.prod-card, .pa-card');
22579    if (!card) {
22580      console.error('Product card not found');
22581      showToast('Data produk tidak ditemukan', 'error');
22582      return;
22583    }
22584    
22585    // Extract product data from card
22586    const productData = extractProductData(card, productId);
22587    if (!productData) {
22588      showToast('Data produk tidak ditemukan', 'error');
22589      return;
22590    }
22591    
22592    activeProductData = productData;
22593    openBottomSheet(productData);
22594  }
22595  
22596  /**
22597   * Extract product data from card element
22598   */
22599  function extractProductData(card, productId) {
22600    try {
22601      // Product ID already passed from button
22602      if (!productId) {
22603        console.error('Product ID not provided');
22604        return null;
22605      }
22606      // Product ID already passed from button
22607      if (!productId) {
22608        console.error('Product ID not provided');
22609        return null;
22610      }
22611      
22612      const id = productId;
22613      
22614      // Get product name
22615      const nameEl = card.querySelector('.prod-name, .title');
22616      const name = nameEl ? nameEl.textContent.trim() : 'Produk';
22617      
22618      // Get product image
22619      const imgEl = card.querySelector('.thumb img, img');
22620      const image = imgEl ? imgEl.src : '/assets/img/placeholder.png';
22621      
22622      // Get product price and detect variants
22623      const priceEl = card.querySelector('.prod-price-final, .final, .prod-price');
22624      let price = 0;
22625      let hasVariants = false;
22626      let priceDisplay = '';
22627      
22628      if (priceEl) {
22629        // Get text content and clean it
22630        let priceText = priceEl.textContent.trim();
22631        priceDisplay = priceText;
22632        
22633        // Check if it's a range (indicates product has variants)
22634        if (priceText.includes('-')) {
22635          hasVariants = true;
22636          // Take the first price for calculation
22637          priceText = priceText.split('-')[0].trim();
22638        }
22639        
22640        // Remove all non-numeric characters
22641        priceText = priceText.replace(/[^0-9]/g, '');
22642        price = parseInt(priceText) || 0;
22643      }
22644      
22645      // Get stock status
22646      const stockDot = card.querySelector('.stock-dot');
22647      let stockStatus = 'unknown';
22648      let stockLabel = 'Cek stok';
22649      
22650      if (stockDot) {
22651        if (stockDot.classList.contains('ready')) {
22652          stockStatus = 'in';
22653          stockLabel = 'Stok tersedia';
22654        } else if (stockDot.classList.contains('low')) {
22655          stockStatus = 'low';
22656          stockLabel = 'Stok terbatas';
22657        } else if (stockDot.classList.contains('out')) {
22658          stockStatus = 'out';
22659          stockLabel = 'Stok habis';
22660        }
22661      }
22662      
22663      // Get category path (if available)
22664      const categoryPath = card.dataset.category || '';
22665      
22666      // Get URL
22667      let url = '#';
22668      if (card.tagName === 'A') {
22669        url = card.href;
22670      } else {
22671        const link = card.querySelector('a[href*="/product/"]');
22672        if (link) url = link.href;
22673      }
22674      
22675      return {
22676        id: id,
22677        name: name,
22678        image: image,
22679        price: price,
22680        priceDisplay: priceDisplay,
22681        hasVariants: hasVariants,
22682        stockStatus: stockStatus,
22683        stockLabel: stockLabel,
22684        categoryPath: categoryPath,
22685        url: url
22686      };
22687    } catch (error) {
22688      console.error('Error extracting product data:', error);
22689      return null;
22690    }
22691  }
22692  
22693  /**
22694   * Open bottom sheet
22695   */
22696  function openBottomSheet(productData) {
22697    if (!overlay || !bottomSheet) return;
22698    
22699    // Update product preview
22700    updateProductPreview(productData);
22701    
22702    // Update cart button based on variant status
22703    updateCartButtonForVariant(productData);
22704    
22705    // Update compare button state
22706    updateCompareButtonState(productData.id);
22707    
22708    // Show overlay and sheet
22709    overlay.classList.add('active');
22710    bottomSheet.classList.add('active');
22711    document.body.style.overflow = 'hidden';
22712    
22713    activeSheet = true;
22714    
22715    // Google Analytics
22716    if (typeof gtag === 'function') {
22717      gtag('event', 'quick_actions_open', {
22718        event_category: 'Product Card',
22719        event_label: 'Quick Actions Menu Opened',
22720        product_id: productData.id,
22721        product_name: productData.name
22722      });
22723    }
22724  }
22725  
22726  /**
22727   * Close bottom sheet
22728   */
22729  function closeBottomSheet() {
22730    if (!overlay || !bottomSheet) return;
22731    
22732    overlay.classList.remove('active');
22733    bottomSheet.classList.remove('active');
22734    document.body.style.overflow = '';
22735    
22736    activeSheet = false;
22737    activeProductData = null;
22738  }
22739  
22740  /**
22741   * Update product preview in bottom sheet
22742   */
22743  function updateProductPreview(productData) {
22744    const preview = document.getElementById('pca-product-preview');
22745    if (!preview) return;
22746    
22747    // Use full price display (shows range for variants)
22748    const priceDisplay = productData.priceDisplay || formatPrice(productData.price);
22749    
22750    preview.innerHTML = `
22751      <img src="${escapeHtml(productData.image)}" 
22752           alt="${escapeHtml(productData.name)}" 
22753           class="pca-product-image"
22754           loading="lazy">
22755      <div class="pca-product-info">
22756        <h4 class="pca-product-name">${escapeHtml(productData.name)}</h4>
22757        <p class="pca-product-price">${escapeHtml(priceDisplay)}</p>
22758        <p class="pca-product-stock ${productData.stockStatus}">${productData.stockLabel}</p>
22759      </div>
22760    `;
22761  }
22762  
22763  /**
22764   * Update cart button for variant products
22765   */
22766  function updateCartButtonForVariant(productData) {
22767    const cartBtn = document.getElementById('pca-add-cart');
22768    if (!cartBtn) return;
22769    
22770    if (productData.hasVariants) {
22771      // Product has variants - change to "View Detail"
22772      cartBtn.dataset.hasVariants = 'true';
22773      cartBtn.dataset.productUrl = productData.url;
22774      
22775      // Update icon to eye/external
22776      const iconSvg = cartBtn.querySelector('svg');
22777      if (iconSvg) {
22778        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"/>';
22779      }
22780      
22781      // Update text
22782      const label = cartBtn.querySelector('.pca-action-label');
22783      const desc = cartBtn.querySelector('.pca-action-desc');
22784      if (label) label.textContent = 'Lihat Detail Produk';
22785      if (desc) desc.textContent = 'Pilih varian terlebih dahulu';
22786      
22787    } else {
22788      // Single product - normal cart button
22789      cartBtn.dataset.hasVariants = 'false';
22790      
22791      // Restore cart icon
22792      const iconSvg = cartBtn.querySelector('svg');
22793      if (iconSvg) {
22794        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"/>';
22795      }
22796      
22797      // Restore text
22798      const label = cartBtn.querySelector('.pca-action-label');
22799      const desc = cartBtn.querySelector('.pca-action-desc');
22800      if (label) label.textContent = 'Tambah ke Keranjang';
22801      if (desc) desc.textContent = 'Lanjut belanja produk lain';
22802    }
22803  }
22804  
22805  /**
22806   * Update compare button state
22807   */
22808  function updateCompareButtonState(productId) {
22809    const compareBtn = document.getElementById('pca-add-compare');
22810    const compareBadge = document.getElementById('pca-compare-count');
22811    
22812    if (!compareBtn) return;
22813    
22814    // Check if product is in compare list (using global compareList if available)
22815    let isInCompare = false;
22816    let compareCount = 0;
22817    
22818    // Try to get compare list from product-compare.js via window.productCompare
22819    if (typeof window.productCompare !== 'undefined' && typeof window.productCompare.getCompareList === 'function') {
22820      const compareList = window.productCompare.getCompareList();
22821      compareCount = compareList.length;
22822      isInCompare = compareList.some(p => parseInt(p.id) === parseInt(productId));
22823    } else {
22824      // Fallback: check badge count
22825      const mainBadge = document.getElementById('compareCount');
22826      if (mainBadge) {
22827        compareCount = parseInt(mainBadge.textContent) || 0;
22828      }
22829    }
22830    
22831    // Update button state
22832    if (isInCompare) {
22833      compareBtn.classList.add('active');
22834      compareBtn.querySelector('.pca-action-label').textContent = 'Hapus dari Perbandingan';
22835      compareBtn.querySelector('.pca-action-desc').textContent = 'Sudah ada di daftar';
22836    } else {
22837      compareBtn.classList.remove('active');
22838      compareBtn.querySelector('.pca-action-label').textContent = 'Bandingkan Produk';
22839      compareBtn.querySelector('.pca-action-desc').textContent = 'Analisis dengan AI';
22840    }
22841    
22842    // Update badge
22843    if (compareBadge && compareCount > 0) {
22844      compareBadge.textContent = compareCount;
22845      compareBadge.style.display = 'inline-flex';
22846    } else if (compareBadge) {
22847      compareBadge.style.display = 'none';
22848    }
22849  }
22850  
22851  /**
22852   * Handle add to cart
22853   */
22854  async function handleAddToCart() {
22855    if (!activeProductData) return;
22856    
22857    const button = document.getElementById('pca-add-cart');
22858    if (!button) return;
22859    
22860    // If product has variants, redirect to detail page
22861    if (activeProductData.hasVariants) {
22862      window.location.href = activeProductData.url;
22863      return;
22864    }
22865    
22866    // Check stock
22867    if (activeProductData.stockStatus === 'out') {
22868      showToast('Produk sedang habis', 'warning');
22869      return;
22870    }
22871    
22872    // Show loading state
22873    button.classList.add('loading');
22874    button.disabled = true;
22875    
22876    // CSRF Token Helper
22877    function getCsrfToken() {
22878      return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
22879    }
22880    
22881    try {
22882      // Call cart API with proper format
22883      const response = await fetch('/api/cart/add.php', {
22884        method: 'POST',
22885        headers: {
22886          'Content-Type': 'application/json',
22887          'X-CSRF-Token': getCsrfToken()
22888        },
22889        body: JSON.stringify({
22890          csrf_token: getCsrfToken(),
22891          product_id: activeProductData.id,
22892          qty: 1, // Use 'qty' not 'quantity'
22893          variant_id: 0, // Default variant
22894          meta: {
22895            name: activeProductData.name,
22896            image_url: activeProductData.image
22897          }
22898        }),
22899        credentials: 'same-origin'
22900      });
22901      
22902      const result = await response.json();
22903      
22904      // Check both response status and result.ok
22905      if (!response.ok || !result.ok) {
22906        throw new Error(result.error || result.message || 'Gagal menambahkan ke keranjang');
22907      }
22908      
22909      // Success animation
22910      button.classList.remove('loading');
22911      button.classList.add('success');
22912      
22913      // Update cart badge
22914      updateCartBadge();
22915
22916      // PlazaAnalytics tracking
22917      if (window.PlazaAnalytics) {
22918        const _slug = (activeProductData.url || '').replace(/.*\/product\//, '');
22919        window.PlazaAnalytics.track('add_to_cart', { slug: _slug });
22920      }
22921
22922      // Show success toast
22923      showToast('Produk ditambahkan ke keranjang', 'success');
22924      
22925      // Google Analytics
22926      if (typeof gtag === 'function') {
22927        gtag('event', 'add_to_cart', {
22928          event_category: 'Ecommerce',
22929          event_label: 'Quick Add to Cart',
22930          items: [{
22931            item_id: activeProductData.id,
22932            item_name: activeProductData.name,
22933            price: activeProductData.price,
22934            quantity: 1
22935          }]
22936        });
22937      }
22938      
22939      // Close sheet after short delay
22940      setTimeout(() => {
22941        closeBottomSheet();
22942        button.classList.remove('success');
22943        button.disabled = false;
22944      }, 1000);
22945      
22946    } catch (error) {
22947      console.error('Add to cart error:', error);
22948      button.classList.remove('loading');
22949      button.disabled = false;
22950      showToast(error.message || 'Terjadi kesalahan', 'error');
22951    }
22952  }
22953  
22954  /**
22955   * Handle add to compare
22956   */
22957  async function handleAddToCompare() {
22958    if (!activeProductData) return;
22959    
22960    const button = document.getElementById('pca-add-compare');
22961    if (!button) return;
22962    
22963    // Check if already in compare
22964    const isActive = button.classList.contains('active');
22965    
22966    // Check 5-product limit before adding
22967    if (!isActive) {
22968      const mainBadge = document.getElementById('compareCount');
22969      const currentCount = mainBadge ? parseInt(mainBadge.textContent) || 0 : 0;
22970      
22971      if (currentCount >= 5) {
22972        showToast('Maksimal 5 produk untuk perbandingan', 'warning');
22973        return;
22974      }
22975    }
22976    
22977    // Show loading state
22978    button.classList.add('loading');
22979    button.disabled = true;
22980    
22981    try {
22982      if (isActive) {
22983        // Remove from compare
22984        await removeFromCompare(activeProductData.id);
22985        showToast('Produk dihapus dari perbandingan', 'success');
22986      } else {
22987        // Add to compare
22988        await addToCompare(activeProductData.id);
22989        showToast('Produk ditambahkan ke perbandingan', 'success');
22990        
22991        // Trigger fly animation
22992        triggerFlyToCompareAnimation();
22993      }
22994      
22995      // Success animation
22996      button.classList.remove('loading');
22997      button.classList.add('success');
22998      
22999      // Update compare button state
23000      updateCompareButtonState(activeProductData.id);
23001      
23002      // Close sheet after short delay
23003      setTimeout(() => {
23004        closeBottomSheet();
23005        button.classList.remove('success');
23006        button.disabled = false;
23007      }, 800);
23008      
23009    } catch (error) {
23010      console.error('Compare error:', error);
23011      button.classList.remove('loading');
23012      button.disabled = false;
23013      showToast(error.message || 'Terjadi kesalahan', 'error');
23014    }
23015  }
23016  
23017  /**
23018   * Add to compare (calls existing API)
23019   */
23020  async function addToCompare(productId) {
23021    const response = await fetch('/api/compare/add.php', {
23022      method: 'POST',
23023      headers: {
23024        'Content-Type': 'application/json',
23025      },
23026      body: JSON.stringify({
23027        product_id: productId,
23028        variant_id: 0
23029      }),
23030      credentials: 'same-origin'
23031    });
23032    
23033    const result = await response.json();
23034    
23035    if (!result.success) {
23036      throw new Error(result.error || 'Gagal menambahkan produk');
23037    }
23038    
23039    // Trigger compare list reload if function exists
23040    if (typeof window.productCompare !== 'undefined' && typeof window.productCompare.loadCompareList === 'function') {
23041      await window.productCompare.loadCompareList();
23042    }
23043    
23044    return result;
23045  }
23046  
23047  /**
23048   * Remove from compare (calls existing API)
23049   */
23050  async function removeFromCompare(productId) {
23051    const response = await fetch('/api/compare/remove.php', {
23052      method: 'POST',
23053      headers: {
23054        'Content-Type': 'application/json',
23055      },
23056      body: JSON.stringify({
23057        product_id: productId
23058      }),
23059      credentials: 'same-origin'
23060    });
23061    
23062    const result = await response.json();
23063    
23064    if (!result.success) {
23065      throw new Error(result.error || 'Gagal menghapus produk');
23066    }
23067    
23068    // Trigger compare list reload if function exists
23069    if (typeof window.productCompare !== 'undefined' && typeof window.productCompare.loadCompareList === 'function') {
23070      await window.productCompare.loadCompareList();
23071    }
23072    
23073    return result;
23074  }
23075  
23076  /**
23077   * Update cart badge
23078   */
23079  function updateCartBadge() {
23080    // Try to call global cart update function if exists
23081    if (typeof window.updateCartBadge === 'function') {
23082      window.updateCartBadge();
23083    } else {
23084      // Fallback: increment badge manually
23085      const badge = document.getElementById('cartCount');
23086      if (badge) {
23087        const currentCount = parseInt(badge.textContent) || 0;
23088        badge.textContent = currentCount + 1;
23089        badge.classList.add('active');
23090      }
23091    }
23092  }
23093  
23094  /**
23095   * Trigger fly to compare animation
23096   */
23097  function triggerFlyToCompareAnimation() {
23098    const compareBtn = document.getElementById('compareBtn');
23099    if (!compareBtn || !activeProductData) return;
23100    
23101    // Create flying element
23102    const flyingElement = document.createElement('div');
23103    flyingElement.className = 'flying-product';
23104    flyingElement.style.cssText = `
23105      position: fixed;
23106      width: 60px;
23107      height: 60px;
23108      z-index: 99999;
23109      pointer-events: none;
23110    `;
23111    
23112    flyingElement.innerHTML = `
23113      <img src="${activeProductData.image}" 
23114           style="width:100%;height:100%;object-fit:cover;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.3);">
23115    `;
23116    
23117    // Position at bottom sheet
23118    const sheetRect = bottomSheet.getBoundingClientRect();
23119    const targetRect = compareBtn.getBoundingClientRect();
23120    
23121    flyingElement.style.top = `${sheetRect.top + 20}px`;
23122    flyingElement.style.left = `${sheetRect.left + 20}px`;
23123    
23124    document.body.appendChild(flyingElement);
23125    
23126    // Animate to target
23127    requestAnimationFrame(() => {
23128      flyingElement.style.transition = 'all 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
23129      flyingElement.style.top = `${targetRect.top + 10}px`;
23130      flyingElement.style.left = `${targetRect.left + 10}px`;
23131      flyingElement.style.transform = 'scale(0.3)';
23132      flyingElement.style.opacity = '0';
23133    });
23134    
23135    // Clean up
23136    setTimeout(() => {
23137      if (flyingElement.parentNode) {
23138        flyingElement.parentNode.removeChild(flyingElement);
23139      }
23140    }, 800);
23141  }
23142  
23143  /**
23144   * Format price as Rupiah
23145   */
23146  function formatPrice(price) {
23147    if (!price || price === 0) return 'Rp 0';
23148    return 'Rp ' + parseInt(price).toLocaleString('id-ID');
23149  }
23150  
23151  /**
23152   * Escape HTML
23153   */
23154  function escapeHtml(text) {
23155    const div = document.createElement('div');
23156    div.textContent = text;
23157    return div.innerHTML;
23158  }
23159  
23160  /**
23161   * Show toast notification (uses global showToast if available)
23162   */
23163  function showToast(message, type = 'info') {
23164    if (typeof window.showToast === 'function') {
23165      window.showToast({
23166        title: type === 'success' ? 'Berhasil' : type === 'error' ? 'Gagal' : 'Info',
23167        message: message,
23168        type: type,
23169        duration: 3000
23170      });
23171    } else {
23172      // Fallback: console log
23173      console.log(`[${type.toUpperCase()}] ${message}`);
23174    }
23175  }
23176  
23177  /**
23178   * Expose functions for external access
23179   */
23180  window.ProductCardActions = {
23181    openSheet: openBottomSheet,
23182    closeSheet: closeBottomSheet,
23183    updateCompareState: updateCompareButtonState
23184  };
23185  
23186})();
23187
23188</script>
23188
23189    
23189<script data-inlined="product-reviews.js">
23190/**
23191 * Product Reviews - Frontend JavaScript
23192 * Handles review display, submission, and helpful voting
23193 */
23194
23195const ProductReviews = (function() {
23196  'use strict';
23197
23198  let currentPage = 1;
23199  let currentSort = 'helpful';
23200  let productId = null;
23201  let isLoading = false;
23202
23203  /**
23204   * Public API: Initialize reviews for a product
23205   */
23206  function init(pid) {
23207    productId = pid;
23208    
23209    if (!productId) {
23210      console.warn('Product ID not provided to ProductReviews.init()');
23211      return;
23212    }
23213
23214    initializeReviewTab();
23215    loadReviews();
23216  }
23217
23218  /**
23219   * Initialize review tab functionality
23220   */
23221  function initializeReviewTab() {
23222    // Tab switching
23223    const tabButtons = document.querySelectorAll('.tab-btn');
23224    tabButtons.forEach(btn => {
23225      btn.addEventListener('click', function() {
23226        const tabName = this.dataset.tab;
23227        switchTab(tabName);
23228      });
23229    });
23230
23231    // Sort dropdown
23232    const sortSelect = document.getElementById('review-sort');
23233    if (sortSelect) {
23234      sortSelect.addEventListener('change', function() {
23235        currentSort = this.value;
23236        currentPage = 1;
23237        loadReviews();
23238      });
23239    }
23240
23241    // Load more button
23242    const loadMoreBtn = document.getElementById('load-more-reviews');
23243    if (loadMoreBtn) {
23244      loadMoreBtn.addEventListener('click', function() {
23245        currentPage++;
23246        loadReviews(true); // Append mode
23247      });
23248    }
23249
23250    // Write review button
23251    const writeReviewBtn = document.getElementById('write-review-btn');
23252    if (writeReviewBtn) {
23253      writeReviewBtn.addEventListener('click', showReviewModal);
23254    }
23255  }
23256
23257  /**
23258   * Switch between product tabs
23259   */
23260  function switchTab(tabName) {
23261    // Update button states
23262    document.querySelectorAll('.tab-btn').forEach(btn => {
23263      btn.classList.toggle('active', btn.dataset.tab === tabName);
23264    });
23265
23266    // Update content visibility
23267    document.querySelectorAll('.tab-content').forEach(content => {
23268      content.style.display = content.id === `${tabName}-tab` ? 'block' : 'none';
23269    });
23270  }
23271
23272  /**
23273   * Load reviews from API
23274   */
23275  async function loadReviews(append = false) {
23276    if (isLoading) return;
23277    isLoading = true;
23278
23279    const container = document.getElementById('reviews-list');
23280    const loadMoreBtn = document.getElementById('load-more-reviews');
23281
23282    // Check if review section exists (product might not have reviews section)
23283    if (!container) {
23284      console.log('Review section not found - product may not have reviews yet');
23285      isLoading = false;
23286      return;
23287    }
23288
23289    try {
23290      if (!append) {
23291        container.innerHTML = '<div class="review-loading">Memuat ulasan...</div>';
23292      } else {
23293        loadMoreBtn.disabled = true;
23294        loadMoreBtn.textContent = 'Memuat...';
23295      }
23296
23297      const response = await fetch(`/api/reviews/list.php?product_id=${productId}&sort=${currentSort}&page=${currentPage}&limit=10`);
23298      const data = await response.json();
23299
23300      if (!data.success) {
23301        throw new Error(data.message || 'Gagal memuat ulasan');
23302      }
23303
23304      // Update statistics (only on initial load)
23305      if (!append && data.stats) {
23306        updateReviewStats(data.stats);
23307      }
23308
23309      // Render reviews
23310      if (!append) {
23311        container.innerHTML = '';
23312      }
23313
23314      const reviews = data.reviews || [];
23315      
23316      if (reviews.length === 0 && !append) {
23317        container.innerHTML = '<div class="no-reviews">Belum ada ulasan untuk produk ini. Jadilah yang pertama!</div>';
23318      } else {
23319        reviews.forEach(review => {
23320          container.appendChild(createReviewElement(review));
23321        });
23322      }
23323
23324      // Update load more button
23325      if (loadMoreBtn) {
23326        if (data.pagination.has_more) {
23327          loadMoreBtn.style.display = 'block';
23328          loadMoreBtn.disabled = false;
23329          loadMoreBtn.textContent = 'Muat Lebih Banyak';
23330        } else {
23331          loadMoreBtn.style.display = 'none';
23332        }
23333      }
23334
23335    } catch (error) {
23336      console.error('Load reviews error:', error);
23337      container.innerHTML = '<div class="review-error">Gagal memuat ulasan. Silakan coba lagi.</div>';
23338    } finally {
23339      isLoading = false;
23340    }
23341  }
23342
23343  /**
23344   * Update review statistics display
23345   */
23346  function updateReviewStats(stats) {
23347    const avgRatingEl = document.getElementById('avg-rating');
23348    const totalReviewsEl = document.getElementById('total-reviews');
23349    const ratingBarsContainer = document.getElementById('rating-bars');
23350
23351    if (avgRatingEl) {
23352      avgRatingEl.textContent = stats.average_rating.toFixed(1);
23353    }
23354
23355    if (totalReviewsEl) {
23356      totalReviewsEl.textContent = `${stats.total_reviews} ulasan`;
23357    }
23358
23359    // Update rating breakdown bars
23360    if (ratingBarsContainer) {
23361      ratingBarsContainer.innerHTML = '';
23362      stats.rating_breakdown.forEach(item => {
23363        const bar = document.createElement('div');
23364        bar.className = 'rating-bar';
23365        bar.innerHTML = `
23366          <span class="rating-label">${item.rating}★</span>
23367          <div class="bar-container">
23368            <div class="bar-fill" style="width: ${item.percentage}%"></div>
23369          </div>
23370          <span class="rating-count">${item.count}</span>
23371        `;
23372        ratingBarsContainer.appendChild(bar);
23373      });
23374    }
23375
23376    // Update tab count
23377    const reviewTab = document.querySelector('[data-tab="reviews"]');
23378    if (reviewTab && stats.total_reviews > 0) {
23379      reviewTab.textContent = `Ulasan (${stats.total_reviews})`;
23380    }
23381  }
23382
23383  /**
23384   * Create review element
23385   */
23386  function createReviewElement(review) {
23387    const div = document.createElement('div');
23388    div.className = 'review-item';
23389    div.dataset.reviewId = review.ID;
23390
23391    // Build stars
23392    const stars = '★'.repeat(review.RATING) + '☆'.repeat(5 - review.RATING);
23393
23394    // Build images if available
23395    let imagesHtml = '';
23396    if (review.images && review.images.length > 0) {
23397      imagesHtml = '<div class="review-images">';
23398      review.images.forEach(img => {
23399        imagesHtml += `<img src="${escapeHtml(img)}" alt="Review image" class="review-img" loading="lazy">`;
23400      });
23401      imagesHtml += '</div>';
23402    }
23403
23404    // Verified purchase badge
23405    const verifiedBadge = review.IS_VERIFIED_PURCHASE === 'Y' 
23406      ? '<span class="verified-badge">✓ Pembelian Terverifikasi</span>' 
23407      : '';
23408    
23409    // Use DISPLAY_NAME if available (for anonymous reviews), otherwise use FULL_NAME
23410    const displayName = review.DISPLAY_NAME || review.FULL_NAME || 'Anonymous';
23411    const anonymousIndicator = review.IS_ANONYMOUS === 'Y' 
23412      ? '<span class="anonymous-badge" title="Nama pengguna disamark
23412an untuk privasi">🔒</span>' 
23413      : '';
23414
23415    div.innerHTML = `
23416      <div class="review-header">
23417        <div class="review-author">
23418          <strong>${escapeHtml(displayName)}</strong>
23419          ${anonymousIndicator}
23420          ${verifiedBadge}
23421        </div>
23422        <div class="review-rating">
23423          <span class="stars">${stars}</span>
23424        </div>
23425      </div>
23426      <div class="review-date">${review.created_ago}</div>
23427      <div class="review-text">${escapeHtml(review.REVIEW_TEXT).replace(/\n/g, '<br>')}</div>
23428      ${imagesHtml}
23429      <div class="review-actions">
23430        <button class="btn-helpful" data-review-id="${review.ID}">
23431          <span class="helpful-icon">👍</span>
23432          <span class="helpful-text">Membantu (${review.HELPFUL_COUNT || 0})</span>
23433        </button>
23434      </div>
23435    `;
23436
23437    // Add helpful button listener
23438    const helpfulBtn = div.querySelector('.btn-helpful');
23439    helpfulBtn.addEventListener('click', () => markAsHelpful(review.ID));
23440
23441    return div;
23442  }
23443
23444  /**
23445   * Get CSRF token from meta tag
23446   */
23447  function getCsrfToken() {
23448    const metaTag = document.querySelector('meta[name="csrf-token"]');
23449    return metaTag ? metaTag.getAttribute('content') : '';
23450  }
23451
23452  /**
23453   * Mark review as helpful
23454   */
23455  async function markAsHelpful(reviewId) {
23456    // Check if logged in
23457    const isLoggedIn = document.body.classList.contains('logged-in') || 
23458                      document.querySelector('meta[name="user-logged-in"]')?.content === 'true';
23459
23460    if (!isLoggedIn) {
23461      showModal({
23462        title: 'Login Diperlukan',
23463        message: 'Silakan login terlebih dahulu untuk memberikan vote pada ulasan ini.',
23464        icon: 'info',
23465        buttons: [
23466          { text: 'Batal', className: 'modal-btn-secondary' },
23467          { text: 'Login', className: 'modal-btn-primary', callback: () => window.location.href = '/login' }
23468        ]
23469      });
23470      return;
23471    }
23472
23473    // Get current button element
23474    const reviewItem = document.querySelector(`[data-review-id="${reviewId}"]`);
23475    const helpfulBtn = reviewItem?.querySelector('.btn-helpful');
23476    
23477    if (!helpfulBtn) return;
23478    
23479    // Disable button during request
23480    helpfulBtn.disabled = true;
23481    const originalText = helpfulBtn.querySelector('.helpful-text').textContent;
23482
23483    try {
23484      const response = await fetch('/api/reviews/helpful.php', {
23485        method: 'POST',
23486        headers: { 
23487          'Content-Type': 'application/json',
23488          'X-CSRF-Token': getCsrfToken()
23489        },
23490        credentials: 'same-origin',
23491        body: JSON.stringify({ 
23492          csrf_token: getCsrfToken(),
23493          review_id: reviewId 
23494        })
23495      });
23496
23497      const data = await response.json();
23498
23499      if (data.success) {
23500        // Update button count immediately based on action
23501        const currentCount = parseInt(originalText.match(/\d+/)?.[0] || 0);
23502        const newCount = data.action === 'added' ? currentCount + 1 : currentCount - 1;
23503        helpfulBtn.querySelector('.helpful-text').textContent = `Membantu (${newCount})`;
23504        
23505        // Show success message
23506        showToast(data.message, 'success');
23507        
23508        // Re-enable button
23509        helpfulBtn.disabled = false;
23510      } else {
23511        showToast(data.message, 'error');
23512        helpfulBtn.disabled = false;
23513      }
23514
23515    } catch (error) {
23516      console.error('Mark helpful error:', error);
23517      showToast('Gagal memberikan vote. Silakan coba lagi.', 'error');
23518      helpfulBtn.disabled = false;
23519    }
23520  }
23521
23522  /**
23523   * Show review submission modal (placeholder)
23524   */
23525  function showReviewModal() {
23526    // TODO: Implement review modal
23527    // For now, redirect to account page
23528    window.location.href = '/account/orders';
23529  }
23530
23531  /**
23532   * Show toast notification
23533   */
23534  function showToast(message, type = 'info') {
23535    const toast = document.createElement('div');
23536    toast.className = `review-toast toast-${type}`;
23537    toast.textContent = message;
23538    
23539    document.body.appendChild(toast);
23540    
23541    setTimeout(() => {
23542      toast.classList.add('show');
23543    }, 100);
23544    
23545    setTimeout(() => {
23546      toast.classList.remove('show');
23547      setTimeout(() => toast.remove(), 300);
23548    }, 3000);
23549  }
23550
23551  /**
23552   * Show modal dialog
23553   * @param {Object} options - Modal options
23554   * @param {string} options.title - Modal title
23555   * @param {string} options.message - Modal message
23556   * @param {string} options.icon - Icon type (info, success, warning, danger)
23557   * @param {Array} options.buttons - Array of button objects {text, className, callback}
23558   */
23559  function showModal(options) {
23560    const {
23561      title = 'Konfirmasi',
23562      message = '',
23563      icon = 'info',
23564      buttons = [{ text: 'OK', className: 'modal-btn-primary' }]
23565    } = options;
23566
23567    // Icon emoji mapping
23568    const iconEmojis = {
23569      info: 'ℹ️',
23570      success: '✓',
23571      warning: '⚠️',
23572      danger: '✕'
23573    };
23574
23575    // Create modal HTML
23576    const modalHTML = `
23577      <div class="modal-overlay" id="helpful-vote-modal" style="display: flex !important; align-items: center !important; justify-content: center !important;">
23578        <div class="modal-dialog">
23579          <div class="modal-header">
23580            <h3 class="modal-title">
23581              <span class="modal-icon ${icon}">${iconEmojis[icon] || iconEmojis.info}</span>
23582              ${escapeHtml(title)}
23583            </h3>
23584          </div>
23585          <div class="modal-body">
23586            <p class="modal-message">${escapeHtml(message)}</p>
23587            <div class="modal-actions">
23588              ${buttons.map((btn, index) => 
23589                `<button class="modal-btn ${btn.className || 'modal-btn-secondary'}" data-index="${index}">${escapeHtml(btn.text)}</button>`
23590              ).join('')}
23591            </div>
23592          </div>
23593        </div>
23594      </div>
23595    `;
23596
23597    // Append to body
23598    const tempDiv = document.createElement('div');
23599    tempDiv.innerHTML = modalHTML;
23600    const modal = tempDiv.firstElementChild;
23601    document.body.appendChild(modal);
23602
23603    // Add event listeners to buttons
23604    buttons.forEach((btn, index) => {
23605      const btnElement = modal.querySelector(`[data-index="${index}"]`);
23606      btnElement.addEventListener('click', () => {
23607        closeModal(modal);
23608        if (btn.callback && typeof btn.callback === 'function') {
23609          btn.callback();
23610        }
23611      });
23612    });
23613
23614    // Close on overlay click
23615    modal.addEventListener('click', (e) => {
23616      if (e.target === modal) {
23617        closeModal(modal);
23618      }
23619    });
23620
23621    // Show modal with animation
23622    setTimeout(() => modal.classList.add('show'), 10);
23623  }
23624
23625  /**
23626   * Close modal dialog
23627   */
23628  function closeModal(modal) {
23629    modal.classList.remove('show');
23630    setTimeout(() => modal.remove(), 200);
23631  }
23632
23633  /**
23634   * Escape HTML for XSS protection
23635   */
23636  function escapeHtml(text) {
23637    const div = document.createElement('div');
23638    div.textContent = text;
23639    return div.innerHTML;
23640  }
23641
23642  /**
23643   * Get product ID from URL
23644   */
23645  function getProductIdFromUrl() {
23646    const match = window.location.pathname.match(/\/product\/(\d+)/);
23647    return match ? match[1] : null;
23648  }
23649
23650  // Public API
23651  return {
23652    init: init
23653  };
23654
23655})();
23656
23657</script>
23657
23658    
23658<script data-inlined="review-submission.js">
23659/**
23660 * Review Submission Modal & Handler
23661 * Handles review form display, validation, and submission
23662 */
23663
23664(function() {
23665    'use strict';
23666
23667    let currentProductId = null;
23668    let currentOrderId = null;
23669    let currentSoRefId = null;
23670    let selectedRating = 0;
23671    let uploadedImages = [];
23672
23673    // Get CSRF token from meta tag
23674    function getCsrfToken() {
23675        const metaTag = document.querySelector('meta[name="csrf-token"]');
23676        return metaTag ? metaTag.getAttribute('content') : '';
23677    }
23678
23679    // Initialize
23680    function init() {
23681        createReviewModal();
23682        attachEventListeners();
23683        loadReviewableOrders();
23684    }
23685
23686    // Create review modal HTML
23687    function createReviewModal() {
23688        const modalHTML = `
23689            <div class="modal-overlay" id="review-modal-overlay" style="display:none;">
23690                <div class="modal-content review-modal">
23691                    <div class="modal-header">
23692                        <h3 class="modal-title">Tulis Ulasan</h3>
23693                        <button type="button" class="modal-close" id="review-modal-close" aria-label="Tutup">
23694                            <svg width="20" height="20" viewBox="0 0 24 24">
23695                                <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"/>
23696                            </svg>
23697                        </button>
23698                    </div>
23699                    
23700                    <div class="modal-body">
23701                        <form id="review-form" class="review-form">
23702                            <!-- Product Info -->
23703                            <div class="review-product-info" id="review-product-info">
23704                                <img id="review-product-image" src="" alt="" class="review-product-thumb">
23705                                <div class="review-product-details">
23706                                    <h4 id="review-product-name"></h4>
23707                                    <p id="review-product-sku"></p>
23708                                </div>
23709                            </div>
23710
23711                            <!-- Rating -->
23712                            <div class="form-group">
23713                                <label class="form-label">Rating <span style="color:#ef4444;">*</span></label>
23714                                <div class="star-rating-input" id="star-rating-input">
23715                                    <button type="button" class="star-btn" data-rating="1">★</button>
23716                                    <button type="button" class="star-btn" data-rating="2">★</button>
23717                                    <button type="button" class="star-btn" data-rating="3">★</button>
23718                                    <button type="button" class="star-btn" data-rating="4">★</button>
23719                                    <button type="button" class="star-btn" data-rating="5">★</button>
23720                                </div>
23721                                <p class="rating-label" id="rating-label">Pilih rating Anda</p>
23722                            </div>
23723
23724                            <!-- Review Text -->
23725                            <div class="form-group">
23726                                <label class="form-label" for="review-text">Ulasan Anda <span style="color:#ef4444;">*</span></label>
23727                                <textarea 
23728                                    id="review-text" 
23729                                    name="review_text"
23730                                    class="form-control" 
23731                                    rows="6" 
23732                                    placeholder="Ceritakan pengalaman Anda dengan produk ini..."
23733                                    minlength="20"
23734                                    maxlength="5000"
23735                                    required></textarea>
23736                                <p class="form-hint">
23737                                    <span id="review-char-count">0</span>/5000 karakter
23738                                    <span style="color:#059669;font-weight:500;">
23739                                        💰 +1,000 point untuk ulasan ≥ 100 karakter
23740                                    </span>
23741                                </p>
23742                            </div>
23743
23744                            <!-- Image Upload -->
23745                            <div class="form-group">
23746                                <label class="form-label">Upload Foto (Opsional)</label>
23747                                <div class="image-upload-container">
23748                                    <input type="file" 
23749                                           id="review-images" 
23750                                           name="images[]" 
23751                                           accept="image/jpeg,image/jpg,image/png,image/webp"
23752                                           multiple
23753                                           style="display:none;">
23754                                    <button type="button" class="btn-upload-trigger" id="upload-trigger">
23755                                        <svg width="24" height="24" viewBox="0 0 24 24">
23756                                            <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"/>
23757                                        </svg>
23758                                        <span>Pilih Foto</span>
23759                                    </button>
23760                                    <p class="form-hint">
23761                                        Max 5 foto, format JPG/PNG/WebP, max 2MB per foto
23762                                        <span style="color:#059669;font-weight:500;">
23763                                            💰 +2,000 point untuk ulasan dengan foto
23764                                        </span>
23765                                    </p>
23766                                </div>
23767                                <div class="image-preview-grid" id="image-preview-grid"></div>
23768                            </div>
23769
23770                            <!-- Point Reward Info -->
23771                            <div class="point-reward-info">
23772                                <div class="pri-header">
23773                                    <span>🎁</span>
23774                                    <span>Dapatkan Point Reward</span>
23775                                </div>
23776                                <ul class="pri-list">
23777                                    <li>✅ Ulasan dasar: <strong>5,000 point</strong></li>
23778                                    <li id="pri-long-text" style="opacity:0.5;">📝 Ulasan panjang (≥100 kata): <strong>+1,000 point</strong></li>
23779                                    <li id="pri-images" style="opacity:0.5;">📸 Dengan foto: <strong>+2,000 point</strong></li>
23780                                </ul>
23781                                <div class="pri-total">
23782                                    Total Reward: <strong id="pri-total-points">5,000 point</strong>
23783                                </div>
23784                            </div>
23785
23786                            <!-- Privacy Option -->
23787                            <div class="form-group" style="margin-top:20px;padding:15px;background:#f8fafc;border-radius:8px;">
23788                                <label class="checkbox-label" style="display:flex;align-items:center;cursor:pointer;margin:0;">
23789                                    <input type="checkbox" 
23790                                           id="review-anonymous" 
23791                                           name="is_anonymous"
23792                                           style="margin-right:8px;width:18px;height:18px;cursor:pointer;">
23793                                    <span style="font-size:14px;">
23794                                        <strong>🔒 Sembunyikan nama saya</strong>
23795                                        <span style="color:#64748b;display:block;font-size:13px;margin-top:4px;" id="review-anonymous-preview">
23796                                            Nama Anda akan ditampilkan dengan disamarkan untuk menjaga privasi
23797                                        </span>
23798                                    </span>
23799                                </label>
23800                            </div>
23801
23802                            <!-- Error Alert -->
23803                            <div class="form-error" id="review-form-error" style="display:none;"></div>
23804
23805                            <!-- Submit Buttons -->
23806                            <div class="modal-footer">
23807                                <button type="button" class="btn btn-secondary" id="review-cancel">Batal</button>
23808                                <button type="submit" class="btn btn-primary" id="review-submit">
23809                                    Kirim Ulasan
23810                                </button>
23811                            </div>
23812                        </form>
23813                    </div>
23814                </div>
23815            </div>
23816        `;
23817
23818        document.body.insertAdjacentHTML('beforeend', modalHTML);
23819    }
23820
23821    // Attach event listeners
23822    function attachEventListeners() {
23823        // Modal triggers - event delegation
23824        document.addEventListener('click', function(e) {
23825            const btn = e.target.closest('.btn-write-review');
23826            if (btn) {
23827                e.preventDefault();
23828                openReviewModal(
23829                    btn.dataset.productId,
23830                    btn.dataset.orderId,
23831                    btn.dataset.productName,
23832                    btn.dataset.productImage,
23833                    btn.dataset.productSku,
23834                    btn.dataset.soRefId || null
23835                );
23836            }
23837        });
23838
23839        // Close modal
23840        setTimeout(() => {
23841            const closeBtn = document.getElementById('review-modal-close');
23842            const cancelBtn = document.getElementById('review-cancel');
23843            const overlay = document.getElementById('review-modal-overlay');
23844
23845            if (closeBtn) {
23846                closeBtn.addEventListener('click', closeReviewModal);
23847            }
23848            if (cancelBtn) {
23849                cancelBtn.addEventListener('click', closeReviewModal);
23850            }
23851            if (overlay) {
23852                overlay.addEventListener('click', function(e) {
23853                    if (e.target === overlay) closeReviewModal();
23854                });
23855            }
23856        }, 100);
23857
23858        // Star rating
23859        setTimeout(() => {
23860            const starButtons = document.querySelectorAll('.star-btn');
23861            starButtons.forEach(btn => {
23862                btn.addEventListener('click', function(e) {
23863                    e.preventDefault();
23864                    const rating = parseInt(this.dataset.rating);
23865                    setRating(rating);
23866                });
23867            });
23868        }, 100);
23869
23870        // Character count
23871        setTimeout(() => {
23872            const textarea = document.getElementById('review-text');
23873            if (textarea) {
23874                textarea.addEventListener('input', function() {
23875                    updateCharCount();
23876                    calculatePotentialPoints();
23877                });
23878            }
23879        }, 100);
23880
23881        // Image upload
23882        setTimeout(() => {
23883            const uploadTrigger = document.getElementById('upload-trigger');
23884            const fileInput = document.getElementById('review-images');
23885            
23886            if (uploadTrigger && fileInput) {
23887                uploadTrigger.addEventListener('click', () => fileInput.click());
23888                fileInput.addEventListener('change', handleImageSelection);
23889            }
23890        }, 100);
23891
23892        // Form submit
23893        setTimeout(() => {
23894            const form = document.getElementById('review-form');
23895            if (form) {
23896                form.addEventListener('submit', handleSubmit);
23897            }
23898        }, 100);
23899    }
23900
23901    // Open review modal
23902    function openReviewModal(productId, orderId, productName, productImage, productSku, soRefId = null) {
23903        const overlay = document.getElementById('review-modal-overlay');
23904        if (!overlay) {
23905            console.error('Modal overlay not found!');
23906            return;
23907        }
23908        
23909        currentProductId = productId;
23910        currentOrderId = orderId;
23911        currentSoRefId = soRefId;
23912        selectedRating = 0;
23913        uploadedImages = [];
23914
23915        // Set product info
23916        const nameEl = document.getElementById('review-product-name');
23917        const imageEl = document.getElementById('review-product-image');
23918        const skuEl = document.getElementById('review-product-sku');
23919        
23920        if (nameEl) nameEl.textContent = productName;
23921        if (imageEl) imageEl.src = productImage || '/assets/img/no-image.png';
23922        if (skuEl) skuEl.textContent = 'SKU: ' + productSku;
23923
23924        // Update privacy preview with actual user name
23925        updatePrivacyPreview();
23926
23927        // Reset form
23928        const form = document.getElementById('review-form');
23929        const previewGrid = document.getElementById('image-preview-grid');
23930        const errorEl = document.getElementById('review-form-error');
23931        
23932        if (form) form.reset();
23933        if (previewGrid) previewGrid.innerHTML = '';
23934        if (errorEl) errorEl.style.display = 'none';
23935        
23936        setRating(0);
23937        updateCharCount();
23938        calculatePotentialPoints();
23939
23940        // Show modal
23941        overlay.style.display = 'flex';
23942        overlay.classList.add('show');
23943        document.body.style.overflow = 'hidden';
23944    }
23945
23946    // Close review modal
23947    function closeReviewModal() {
23948        const overlay = document.getElementById('review-modal-overlay');
23949        if (overlay) {
23950            overlay.classList.remove('show');
23951            setTimeout(() => {
23952                overlay.style.display = 'none';
23953            }, 200); // Wait for fade animation
23954        }
23955        document.body.style.overflow = '';
23956    }
23957
23958    // Set star rating
23959    function setRating(rating) {
23960        selectedRating = rating;
23961        const stars = document.querySelectorAll('.star-btn');
23962        const labels = ['', 'Sangat Buruk', 'Buruk', 'Cukup', 'Bagus', 'Sangat Bagus'];
23963        
23964        stars.forEach((star, index) => {
23965            if (index < rating) {
23966                star.classList.add('active');
23967            } else {
23968                star.classList.remove('active');
23969            }
23970        });
23971
23972        document.getElementById('rating-label').textContent = labels[rating] || 'Pilih rating Anda';
23973    }
23974
23975    // Update character count
23976    function updateCharCount() {
23977        const textarea = document.getElementById('review-text');
23978        const count = textarea.value.length;
23979        document.getElementById('review-char-count').textContent = count;
23980    }
23981
23982    // Calculate potential points
23983    function calculatePotentialPoints() {
23984        const textarea = document.getElementById('review-text');
23985        if (!textarea) return;
23986        
23987        const charCount = textarea.value.trim().length;
23988        const hasImages = uploadedImages.length > 0;
23989
23990        let totalPoints = 5000;
23991        let longTextActive = charCount >= 100;
23992        let imagesActive = hasImages;
23993
23994        if (longTextActive) totalPoints += 1000;
23995        if (imagesActive) totalPoints += 2000;
23996
23997        // Update UI
23998        const longTextEl = document.getElementById('pri-long-text');
23999        const imagesEl = document.getElementById('pri-images');
24000        const totalEl = document.getElementById('pri-total-points');
24001        
24002        if (longTextEl) longTextEl.style.opacity = longTextActive ? '1' : '0.5';
24003        if (imagesEl) imagesEl.style.opacity = imagesActive ? '1' : '0.5';
24004        if (totalEl) totalEl.textContent = totalPoints.toLocaleString('id-ID') + ' point';
24005    }
24006
24007    // Handle image selection
24008    function handleImageSelection(e) {
24009        const files = Array.from(e.target.files);
24010        
24011        if (files.length > 5) {
24012            showError('Maksimal 5 foto yang dapat diupload');
24013            e.target.value = ''; // Reset input
24014            return;
24015        }
24016
24017        uploadedImages = [];
24018        const previewGrid = document.getElementById('image-preview-grid');
24019        previewGrid.innerHTML = '';
24020
24021        files.forEach((file, index) => {
24022            // Validate file type
24023            if (!file.type.match(/image\/(jpeg|jpg|png|webp)/)) {
24024                showError('Format file harus JPG, PNG, atau WebP');
24025                return;
24026            }
24027
24028            // Validate file size (2MB)
24029            if (file.size > 2 * 1024 * 1024) {
24030                showError('Ukuran file maksimal 2MB');
24031                return;
24032            }
24033
24034            uploadedImages.push(file);
24035
24036            // Create preview
24037            const reader = new FileReader();
24038            reader.onload = function(e) {
24039                const previewHTML = `
24040                    <div class="image-preview-item">
24041                        <img src="${e.target.result}" alt="Preview ${index + 1}">
24042                        <button type="button" class="remove-image" data-index="${index}" aria-label="Hapus">
24043                            <svg width="16" height="16" viewBox="0 0 24 24">
24044                                <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"/>
24045                            </svg>
24046                        </button>
24047                    </div>
24048                `;
24049                previewGrid.insertAdjacentHTML('beforeend', previewHTML);
24050            };
24051            reader.readAsDataURL(file);
24052        });
24053
24054        calculatePotentialPoints();
24055
24056        // Attach remove handlers
24057        setTimeout(() => {
24058            document.querySelectorAll('.remove-image').forEach(btn => {
24059                btn.addEventListener('click', function() {
24060                    const index = parseInt(this.dataset.index);
24061                    removeImage(index);
24062                });
24063            });
24064        }, 100);
24065    }
24066
24067    // Remove image
24068    function removeImage(index) {
24069        uploadedImages.splice(index, 1);
24070        
24071        const previewGrid = document.getElementById('image-preview-grid');
24072        previewGrid.innerHTML = '';
24073
24074        uploadedImages.forEach((file, idx) => {
24075            const reader = new FileReader();
24076            reader.onload = function(e) {
24077                const previewHTML = `
24078                    <div class="image-preview-item">
24079                        <img src="${e.target.result}" alt="Preview ${idx + 1}">
24080                        <button type="button" class="remove-image" data-index="${idx}" aria-label="Hapus">
24081                            <svg width="16" height="16" viewBox="0 0 24 24">
24082                                <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"/>
24083                            </svg>
24084                        </button>
24085                    </div>
24086                `;
24087                previewGrid.insertAdjacentHTML('beforeend', previewHTML);
24088            };
24089            reader.readAsDataURL(file);
24090        });
24091
24092        calculatePotentialPoints();
24093
24094        setTimeout(() => {
24095            document.querySelectorAll('.remove-image').forEach(btn => {
24096                btn.addEventListener('click', function() {
24097                    const idx = parseInt(this.dataset.index);
24098                    removeImage(idx);
24099                });
24100            });
24101        }, 100);
24102    }
24103
24104    // Handle form submission
24105    async function handleSubmit(e) {
24106        e.preventDefault();
24107
24108        // Validate
24109        if (selectedRating === 0) {
24110            showError('Silakan pilih rating terlebih dahulu');
24111            return;
24112        }
24113
24114        const reviewText = document.getElementById('review-text').value.trim();
24115        if (reviewText.length < 20) {
24116            showError('Ulasan minimal 20 karakter');
24117            return;
24118        }
24119
24120        const submitBtn = document.getElementById('review-submit');
24121        submitBtn.disabled = true;
24122        
24123        try {
24124            // Step 1: Upload images if any
24125            let imageUrls = [];
24126            if (uploadedImages.length > 0) {
24127                submitBtn.textContent = 'Mengupload foto...';
24128                imageUrls = await uploadImages();
24129                if (!imageUrls) {
24130                    // Upload failed
24131                    submitBtn.disabled = false;
24132                    submitBtn.textContent = 'Kirim Ulasan';
24133                    return;
24134                }
24135            }
24136
24137            // Step 2: Submit review with image URLs
24138            submitBtn.textContent = 'Mengirim ulasan...';
24139            
24140            const payload = {
24141                csrf_token: getCsrfToken(),
24142                product_id: currentProductId,
24143                order_id: currentOrderId || 0,
24144                so_ref_id: currentSoRefId || undefined,
24145                rating: selectedRating,
24146                review_text: reviewText,
24147                is_anonymous: document.getElementById('review-anonymous').checked,
24148                images: imageUrls
24149            };
24150
24151            const response = await fetch('/api/reviews/submit.php', {
24152                method: 'POST',
24153                headers: {
24154                    'Content-Type': 'application/json',
24155                    'X-CSRF-Token': getCsrfToken()
24156                },
24157                body: JSON.stringify(payload)
24158            });
24159
24160            const result = await response.json();
24161
24162            if (result.success) {
24163                showSuccess(result.message || 'Ulasan berhasil dikirim!');
24164                closeReviewModal();
24165                
24166                // Refresh reviewable orders if on account page
24167                if (typeof loadReviewableOrders === 'function') {
24168                    setTimeout(loadReviewableOrders, 500);
24169                }
24170            } else {
24171                showError(result.message || 'Gagal mengirim ulasan');
24172            }
24173        } catch (error) {
24174            console.error('Submit error:', error);
24175            showError('Terjadi kesalahan saat mengirim ulasan');
24176        } finally {
24177            submitBtn.disabled = false;
24178            submitBtn.textContent = 'Kirim Ulasan';
24179        }
24180    }
24181
24182    /**
24183     * Upload images to server
24184     * @returns {Promise<Array|null>} Array of image URLs or null if failed
24185     */
24186    async function uploadImages() {
24187        if (uploadedImages.length === 0) {
24188            return [];
24189        }
24190
24191        const formData = new FormData();
24192        uploadedImages.forEach(file => {
24193            formData.append('images[]', file);
24194        });
24195
24196        try {
24197            const response = await fetch('/api/reviews/upload_images.php', {
24198                method: 'POST',
24199                headers: {
24200                    'X-CSRF-Token': getCsrfToken()
24201                },
24202                body: formData
24203            });
24204
24205            // Check if response is JSON
24206            const contentType = response.headers.get('content-type');
24207            if (!contentType || !contentType.includes('application/json')) {
24208                const text = await response.text();
24209                console.error('Non-JSON response:', text);
24210                showError('Server error: Response bukan JSON. Cek browser console untuk detail.');
24211                return null;
24212            }
24213
24214            const result = await response.json();
24215
24216            if (result.success && result.images) {
24217                // Extract URLs from uploaded images
24218                return result.images.map(img => img.url);
24219            } else {
24220                showError(result.message || 'Gagal mengupload foto');
24221                return null;
24222            }
24223        } catch (error) {
24224            console.error('Upload images error:', error);
24225            showError('Terjadi kesalahan saat mengupload foto. Cek console untuk detail.');
24226            return null;
24227        }
24228    }
24229
24230    // Load reviewable orders (for account page)
24231    async function loadReviewableOrders() {
24232        // Only run if on account/profile page
24233        const container = document.getElementById('reviewable-orders-list');
24234        const loading = document.getElementById('reviewable-orders-loading');
24235        const empty = document.getElementById('reviewable-orders-empty');
24236        
24237        if (!container) {
24238            return;
24239        }
24240
24241        // Show loading
24242        if (loading) loading.style.display = 'block';
24243        if (empty) empty.style.display = 'none';
24244        container.innerHTML = '';
24245
24246        try {
24247            const response = await fetch('/api/reviews/reviewable.php');
24248            const result = await response.json();
24249
24250            if (loading) loading.style.display = 'none';
24251
24252            if (result.success && result.data.length > 0) {
24253                renderReviewableOrders(result.data);
24254                if (empty) empty.style.display = 'none';
24255            } else {
24256                if (empty) empty.style.display = 'block';
24257            }
24258        } catch (error) {
24259            console.error('Load reviewable orders error:', error);
24260            if (loading) loading.style.display = 'none';
24261            if (empty) {
24262                empty.innerHTML = '<p>Gagal memuat data. Silakan refresh halaman.</p>';
24263                empty.style.display = 'block';
24264            }
24265        }
24266    }
24267
24268    // Render reviewable orders
24269    function renderReviewableOrders(orders) {
24270        const container = document.getElementById('reviewable-orders-list');
24271        if (!container) return;
24272
24273        let html = '';
24274        
24275        orders.forEach(order => {
24276            html += `
24277                <div class="reviewable-order-item">
24278                    <img src="${escapeHtml(order.product_image || '/assets/img/no-image.png')}" alt="${escapeHtml(order.product_name)}" class="roi-image">
24279                    <div class="roi-info">
24280                        <h4>${escapeHtml(order.product_name)}</h4>
24281                        <p class="roi-meta">Order: ${escapeHtml(order.order_number)}</p>
24282                        <p class="roi-meta">Dibeli: ${escapeHtml(order.order_date)}</p>
24283                    </div>
24284                    <button type="button" 
24285                            class="btn btn-primary btn-write-review"
24286                            data-product-id="${order.product_id}"
24287                            data-order-id="${order.order_id || ''}"
24288                            data-so-ref-id="${escapeHtml(order.so_ref_id || '')}"
24289                            data-product-name="${escapeHtml(order.product_name)}"
24290                            data-product-image="${escapeHtml(order.product_image || '')}"
24291                            data-product-sku="${escapeHtml(order.product_sku || '')}">
24292                        ✍️ Tulis Ulasan
24293                    </button>
24294                </div>
24295            `;
24296        });
24297        
24298        container.innerHTML = html;
24299    }
24300
24301    // Escape HTML for XSS protection
24302    function escapeHtml(text) {
24303        const div = document.createElement('div');
24304        div.textContent = text;
24305        return div.innerHTML;
24306    }
24307
24308    // Show error
24309    function showError(message) {
24310        const errorEl = document.getElementById('review-form-error');
24311        errorEl.textContent = message;
24312        errorEl.style.display = 'block';
24313        
24314        setTimeout(() => {
24315            errorEl.style.display = 'none';
24316        }, 5000);
24317    }
24318
24319    // Show success
24320    function showSuccess(message) {
24321        // Create toast notification
24322        const toast = document.createElement('div');
24323        toast.className = 'toast-notification success';
24324        toast.textContent = message;
24325        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;';
24326        
24327        document.body.appendChild(toast);
24328        
24329        setTimeout(() => {
24330            toast.style.opacity = '0';
24331            toast.style.transition = 'opacity 0.3s';
24332            setTimeout(() => toast.remove(), 300);
24333        }, 3000);
24334    }
24335
24336    // Mask user name for privacy preview (like PHP maskReviewerName)
24337    function maskUserName(fullName) {
24338        if (!fullName || fullName.trim() === '') {
24339            return 'Anonymous';
24340        }
24341        
24342        const words = fullName.trim().split(/\s+/);
24343        const maskedWords = words.map(word => {
24344            const len = word.length;
24345            if (len <= 1) return word;
24346            return word.charAt(0) + '*'.repeat(len - 1);
24347        });
24348        
24349        return maskedWords.join(' ');
24350    }
24351
24352    // Update privacy preview text with user's actual masked name
24353    function updatePrivacyPreview() {
24354        const previewEl = document.getElementById('review-anonymous-preview');
24355        if (!previewEl) return;
24356        
24357        const userName = window.userFullName || '';
24358        if (userName) {
24359            const maskedName = maskUserName(userName);
24360            previewEl.textContent = `Nama Anda akan ditampilkan sebagai "${maskedName}" untuk menjaga privasi`;
24361        } else {
24362            previewEl.textContent = 'Nama Anda akan ditampilkan de
24362ngan disamarkan untuk menjaga privasi';
24363        }
24364    }
24365
24366    // Initialize when DOM ready
24367    if (document.readyState === 'loading') {
24368        document.addEventListener('DOMContentLoaded', init);
24369    } else {
24370        init();
24371    }
24372
24373    // Export openReviewModal to global scope for use in other scripts
24374    window.openReviewModal = openReviewModal;
24375
24376})();
24377
24378</script>
24378
24379    <style>
24380/* =========================================================
24381   ⚡ FIXED FOOTER – RED ELEGANT MODE
24382========================================================= */
24383
24384.sbo-top-fixed-footer{
24385      position: fixed;
24386    left: 0;
24387    right: 0;
24388    bottom: 0;
24389    z-index: 999;
24390    display: flex;
24391    align-items: center;
24392    justify-content: space-around;
24393    gap: 8px;
24394    padding: 11px 12px calc(11px + env(safe-area-inset-bottom));
24395    overflow: hidden;
24396    background: radial-gradient(circle at 50% 0%, #000000, rgb(0 0 0 / 92%) 58%, rgb(0 0 0 / 98%)), linear-gradient(180deg, rgba(25, 0, 0, 0.92), rgb(0 0 0 / 98%));
24397    backdrop-filter: blur(12px);
24398    border-top: 1px solid rgb(143 1 1);
24399    border-radius: 18px 18px 0 0;
24400    box-shadow: 0 -12px 30px #410000, inset 0 1px 0 rgba(255, 255, 255, .05);
24401    font-family: "Orbitron", "Poppins", system-ui, sans-serif;
24402}
24403
24404/* scanline */
24405.sbo-top-fixed-footer::before{
24406  content:"";
24407  position:absolute;
24408  inset:0;
24409  pointer-events:none;
24410  background:
24411    repeating-linear-gradient(
24412      0deg,
24413      rgba(255,255,255,.015),
24414      rgba(255,255,255,.015) 1px,
24415      transparent 1px,
24416      transparent 6px
24417    );
24418  opacity:.85;
24419  mix-blend-mode:overlay;
24420}
24421
24422/* shine sweep */
24423.sbo-top-fixed-footer::after{
24424  content:"";
24425  position:absolute;
24426  top:0;
24427  left:-160%;
24428  width:60%;
24429  height:100%;
24430  background: linear-gradient(120deg, transparent, rgba(255, 90, 90, 0.25), transparent);
24431  transform: skewX(-25deg);
24432  animation: footerFinalShine 3.8s infinite;
24433  mix-blend-mode: screen;
24434  pointer-events:none;
24435}
24436
24437@keyframes footerFinalShine{
24438  0%{left:-160%;}
24439  68%{left:160%;}
24440  100%{left:160%;}
24441}
24442
24443/* =========================================================
24444   ITEM
24445========================================================= */
24446
24447.sbo-top-fixed-footer a{
24448  flex:1;
24449  max-width:110px;
24450
24451  display:flex;
24452  flex-direction:column;
24453  align-items:center;
24454  justify-content:center;
24455  gap:6px;
24456
24457  padding: 8px 6px;
24458
24459  text-decoration:none;
24460  text-align:center;
24461
24462  color: rgba(171 0 0);
24463  font-size: 12px;
24464  font-weight: 800;
24465  letter-spacing: .25px;
24466
24467  border-radius: 14px;
24468  position:relative;
24469  z-index:1;
24470
24471  transition: all .18s ease;
24472}
24473
24474.sbo-top-fixed-footer a:hover{
24475  color:#ffffff;
24476  background: rgba(255, 80, 80, 0.08);
24477  filter: drop-shadow(0 0 12px rgba(255, 80, 80, 0.15));
24478  transform: translateY(-1px);
24479}
24480
24481.sbo-top-fixed-footer a:active{
24482  transform: translateY(0) scale(.99);
24483}
24484
24485/* =========================================================
24486   ICON
24487========================================================= */
24488
24489.sbo-top-fixed-footer img{
24490  width:26px;
24491  height:26px;
24492  transition: .18s ease;
24493  filter: drop-shadow(0 0 8px rgba(255 242 80 / 30%5));
24494}
24495
24496.sbo-top-fixed-footer a:hover img{
24497  transform: scale(1.08);
24498  filter: drop-shadow(0 0 16px rgba(255, 80, 80, 0.6));
24499}
24500
24501/* =========================================================
24502   HUD CORNERS
24503========================================================= */
24504
24505.sbo-top-fixed-footer .hud-corner{
24506  position:absolute;
24507  width:18px;height:18px;
24508  pointer-events:none;
24509  border: 1px solid rgba(255, 90, 90, 0.5);
24510  filter: drop-shadow(0 0 10px rgba(255, 80, 80, 0.2));
24511  opacity:.85;
24512}
24513
24514.sbo-top-fixed-footer .hud-tl{top:8px;left:10px;border-right:none;border-bottom:none;border-radius:10px 0 0 0;}
24515.sbo-top-fixed-footer .hud-tr{top:8px;right:10px;border-left:none;border-bottom:none;border-radius:0 10px 0 0;}
24516.sbo-top-fixed-footer .hud-bl{bottom:8px;left:10px;border-right:none;border-top:none;border-radius:0 0 0 10px;}
24517.sbo-top-fixed-footer .hud-br{bottom:8px;right:10px;border-left:none;border-top:none;border-radius:0 0 10px 0;}
24518
24519/* =========================================================
24520   CENTER CTA
24521========================================================= */
24522
24523.sbo-top-fixed-footer .tada{
24524      color: #ffffff;
24525    background: radial-gradient(circle at 50% 0%, #f52b2b, rgba(0, 0, 0, 0) 70%), rgba(255, 60, 60, 0.08);
24526    border: 1px solid rgba(255, 90, 90, 0.35);
24527    box-shadow: 0 0 18px #f52b2b, inset 0 0 16px rgba(255, 60, 60, 0.15);
24528}
24529
24530.sbo-top-fixed-footer .tada img{
24531  width:30px;
24532  height:30px;
24533  filter: drop-shadow(0 0 16px rgba(255, 80, 80, 0.65));
24534}
24535
24536/* pulse */
24537.sbo-top-fixed-footer .tada::before{
24538  content:"";
24539  position:absolute;
24540  inset:-12px;
24541  border-radius:999px;
24542  background:
24543    radial-gradient(circle, #000000, transparent 70%),
24544    radial-gradient(circle, rgba(255, 120, 120, 0.08), transparent 72%);
24545  filter: blur(12px);
24546  opacity:.55;
24547  animation: corePulse 2.9s ease-in-out infinite;
24548  z-index:-1;
24549}
24550
24551@keyframes corePulse{
24552  0%,100%{opacity:.35; transform:scale(.99);}
24553  50%{opacity:.75; transform:scale(1.02);}
24554}
24555
24556/* ACTIVE */
24557.sbo-top-fixed-footer a.is-active{
24558  color:#ffffff;
24559  background: rgba(255, 80, 80, 0.1);
24560  box-shadow: inset 0 0 0 1px rgba(255, 90, 90, 0.25);
24561}
24562
24563.sbo-top-fixed-footer a.is-active img{
24564  filter: drop-shadow(0 0 18px rgba(255, 80, 80, 0.8));
24565}
24566
24567/* RESPONSIVE */
24568@media (max-width:500px){
24569  .sbo-top-fixed-footer{
24570    padding: 10px 10px calc(10px + env(safe-area-inset-bottom));
24571    border-radius: 16px 16px 0 0;
24572    gap:6px;
24573  }
24574  .sbo-top-fixed-footer a{
24575    font-size:11px;
24576    padding:7px 5px;
24577    gap:5px;
24578    max-width:104px;
24579  }
24580  .sbo-top-fixed-footer img{width:22px;height:22px;}
24581  .sbo-top-fixed-footer .tada img{width:26px;height:26px;}
24582}
24583</style>
24584<div class="sbo-top-fixed-footer">
24585  <a href="https://bim.miami/aboutus/" target="_blank" rel="nofollow noopener">
24586    <img src="https://imgmahasuhu.io/images/2026/05/13/iconbonus.png" alt="BONUS BERLINTOTO">
24587    Promosi
24588  </a>
24589  <a href="https://bim.miami/aboutus/" target="_blank" rel="nofollow noopener">
24590    <img src="https://imgmahasuhu.io/images/2026/05/13/iconlogin.png" alt="LOGIN BERLINTOTO">
24591    Login
24592  </a>
24593  <a href="https://bim.miami/aboutus/" target="_blank" rel="nofollow noopener" class="tada">
24594    <img src="https://imgmahasuhu.io/images/2026/05/13/icondaftar.webp" alt="DAFTAR BERLINTOTO">
24595    Daftar
24596  </a>
24597  <a href="https://bim.miami/aboutus/" target="_blank" rel="nofollow noopener">
24598    <img src="https://imgmahasuhu.io/images/2026/05/13/iconlinkalternatif.png" alt="LINK ALTERNATIF BERLINTOTO">
24599    Alternatif
24600  </a>
24601  <a href="https://bim.miami/aboutus/" target="_blank" rel="nofollow noopener">
24602    <img src="https://imgmahasuhu.io/images/2026/05/13/iconlivechat.png" alt="LIVE CHAT BERLINTOTO">
24603    Live Chat
24604  </a>
24605</div>
24606<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v4513226cdae34746b4dedf0b4dfa099e1781791509496" integrity="sha512-ZE9pZaUXND66v380QUtch/5sE9tPFh2zg45pR2PB0CVkCtOREv2AJKkSidISWkysEuQ0EH8faUU5du78bx87UQ==" data-cf-beacon='{"version":"2024.11.0","token":"51df3b17a746455f940c0b561e8c0c0b","r":1,"server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 24606
24606
24607<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v4513226cdae34746b4dedf0b4dfa099e1781791509496" integrity="sha512-ZE9pZaUXND66v380QUtch/5sE9tPFh2zg45pR2PB0CVkCtOREv2AJKkSidISWkysEuQ0EH8faUU5du78bx87UQ==" data-cf-beacon='{"version":"2024.11.0","token":"51df3b17a746455f940c0b561e8c0c0b","r":1}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 24607
24607
24608<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v4513226cdae34746b4dedf0b4dfa099e1781791509496" integrity="sha512-ZE9pZaUXND66v380QUtch/5sE9tPFh2zg45pR2PB0CVkCtOREv2AJKkSidISWkysEuQ0EH8faUU5du78bx87UQ==" data-cf-beacon='{"version":"2024.11.0","token":"51df3b17a746455f940c0b561e8c0c0b","r":1}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 24608
24608
24609<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v4513226cdae34746b4dedf0b4dfa099e1781791509496" integrity="sha512-ZE9pZaUXND66v380QUtch/5sE9tPFh2zg45pR2PB0CVkCtOREv2AJKkSidISWkysEuQ0EH8faUU5du78bx87UQ==" data-cf-beacon='{"version":"2024.11.0","token":"51df3b17a746455f940c0b561e8c0c0b","r":1}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 24609
24609
24610<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v3d52b47920f24c319d37e2661827c42b1787588026925" integrity="sha512-d9sL6GJLXn6fInD1+TVXhTcQOsmxeHfmHAvwGDIxp5TO+uo1fiWW7mHomMj4MLRlCsJDTqXzWLHJFFlPCEIj/A==" data-cf-beacon='{"version":"2024.11.0","token":"6138f29286ee4657aedef37717a3a41a","r":1}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 24610
24610
24611<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v3d52b47920f24c319d37e2661827c42b1787588026925" integrity="sha512-d9sL6GJLXn6fInD1+TVXhTcQOsmxeHfmHAvwGDIxp5TO+uo1fiWW7mHomMj4MLRlCsJDTqXzWLHJFFlPCEIj/A==" data-cf-beacon='{"version":"2024.11.0","token":"584e215915fc4a94bc26e2b00099ce04","r":1}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 24611
24611
24612<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v3d52b47920f24c319d37e2661827c42b1787588026925" integrity="sha512-d9sL6GJLXn6fInD1+TVXhTcQOsmxeHfmHAvwGDIxp5TO+uo1fiWW7mHomMj4MLRlCsJDTqXzWLHJFFlPCEIj/A==" data-cf-beacon='{"version":"2024.11.0","token":"73e47ca119414820a5354c1e7e4c82fe","r":1}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 24612
24612
24613<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v3d52b47920f24c319d37e2661827c42b1787588026925" integrity="sha512-d9sL6GJLXn6fInD1+TVXhTcQOsmxeHfmHAvwGDIxp5TO+uo1fiWW7mHomMj4MLRlCsJDTqXzWLHJFFlPCEIj/A==" data-cf-beacon='{"version":"2024.11.0","token":"d4e1c111d5d6419e9d912cb595e74e73","r":1}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 24613
24613
24614<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v3d52b47920f24c319d37e2661827c42b1787588026925" integrity="sha512-d9sL6GJLXn6fInD1+TVXhTcQOsmxeHfmHAvwGDIxp5TO+uo1fiWW7mHomMj4MLRlCsJDTqXzWLHJFFlPCEIj/A==" data-cf-beacon='{"version":"2024.11.0","token":"a97528b5df52422ca8ea8c0d234766d0","r":1}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 24614
24614
24615<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v3d52b47920f24c319d37e2661827c42b1787588026925" integrity="sha512-d9sL6GJLXn6fInD1+TVXhTcQOsmxeHfmHAvwGDIxp5TO+uo1fiWW7mHomMj4MLRlCsJDTqXzWLHJFFlPCEIj/A==" data-cf-beacon='{"version":"2024.11.0","token":"8c95032917ae4f3da8bb19ff3e848b2c","r":1}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 24615
24615
24616<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v3d52b47920f24c319d37e2661827c42b1787588026925" integrity="sha512-d9sL6GJLXn6fInD1+TVXhTcQOsmxeHfmHAvwGDIxp5TO+uo1fiWW7mHomMj4MLRlCsJDTqXzWLHJFFlPCEIj/A==" data-cf-beacon='{"version":"2024.11.0","token":"cee4242206b04e6d8fc1a0fa16207d5e","r":1}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 24616
24616
24617<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v3d52b47920f24c319d37e2661827c42b1787588026925" integrity="sha512-d9sL6GJLXn6fInD1+TVXhTcQOsmxeHfmHAvwGDIxp5TO+uo1fiWW7mHomMj4MLRlCsJDTqXzWLHJFFlPCEIj/A==" data-cf-beacon='{"version":"2024.11.0","token":"1c3a4a5b897742b8b80dc093d821946c","r":1}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 24617
24617
24618<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v3d52b47920f24c319d37e2661827c42b1787588026925" integrity="sha512-d9sL6GJLXn6fInD1+TVXhTcQOsmxeHfmHAvwGDIxp5TO+uo1fiWW7mHomMj4MLRlCsJDTqXzWLHJFFlPCEIj/A==" data-cf-beacon='{"version":"2024.11.0","token":"577a11b268174c14a1684d49b7e85c4f","r":1}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 24618
24618
24619<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":"577a11b268174c14a1684d49b7e85c4f","r":1,"spa":2}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 24619
24619
24620<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":"467ad21de1b447a8a311c1acf9547e04","r":1,"spa":2}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 24620
24620
24621<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":"ed7af6afbc08412fa75c251a1332ecfc","r":1,"spa":2}' crossorigin="anonymous"></script>
vendor: 1 bytes, line 24621
24621
24622<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":"cdb9c2a2d72c44fc91f04b655b34b25a","r":1,"spa":2}' crossorigin="anonymous"></script>
24622
24623</body>
24624</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.