PageSourceSearch

https://calcourtsfitness.com/

html calcourtsfitness.com collected 2026-10-08 04:10:06 UTC 839,198 bytes, 24,606 lines download raw bytes

1<!DOCTYPE html>
2<html lang="id">
3<head>
4  <meta charset="UTF-8">
5  <title>Menghadirkan PIONTOGEL - Situs Alami Mendapatkan Cuan Dengan Permainan Resmi</title>
6  <meta name="description" content="PIONTOGEL merepresentasikan wahana digital dengan artikulasi modern yang mengakomodasi beragam permainan resmi secara terintegrasi. Melalui tata kelola antarmuka yang sistematis, navigasi adaptif, dan aksesibilitas yang optimal, platform ini menghadirkan pengalaman digital yang praktis sekaligus atraktif.">
7  <meta name="robots" content="index,follow">
8  <link rel="canonical" href="https://calcourtsfitness.com/">
9  <meta name="google-site-verification" content="6fVXoKpZcx_doXYAZnJ6iZNC63wL6r5Pzw99b7is38E" />
10  <link rel="amphtml" href="https://calcourtsfitness.nichesite.org/">
11  <link rel="alternate" href="https://calcourtsfitness.nichesite.org/" hreflang="id">
12  <link rel="alternate" href="https://calcourtsfitness.nichesite.org/" hreflang="en">
13  <link rel="alternate" href="https://calcourtsfitness.nichesite.org/" hreflang="x-default">
14  <link rel="alternate" hreflang="id-ID" href="https://calcourtsfitness.nichesite.org/" />
15  <link rel="alternate" hreflang="en-US" href="https://calcourtsfitness.nichesite.org/" />
16  <link rel="alternate" hreflang="en-SG" href="https://calcourtsfitness.nichesite.org/" />
17    <link rel="preconnect" href="https://www.googletagmanager.com" crossorigin>
vendor: 67 bytes, lines 17-18
17
18  <script async src="https://www.googletagmanager.com/gtag/js?id=
18G-0LSM1YJSPZ
vendor: 48 bytes, lines 18-19
18" nonce="se3iyzEHMc9FQEB4wSyd0w=="></script>
19  
19<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>
19
20  <meta property="og:locale" content="id_ID">
21  <meta property="og:site_name" content="TOTO TOGEL">
22  <meta property="og:type" content="product">
23  <meta property="og:title" content="Menghadirkan PIONTOGEL - Situs Alami Mendapatkan Cuan Dengan Permainan Resmi">
24  <meta property="og:description" content="PIONTOGEL merepresentasikan wahana digital dengan artikulasi modern yang mengakomodasi beragam permainan resmi secara terintegrasi. Melalui tata kelola antarmuka yang sistematis, navigasi adaptif, dan aksesibilitas yang optimal, platform ini menghadirkan pengalaman digital yang praktis sekaligus atraktif.">
25  <meta property="og:url" content="https://calcourtsfitness.com/">
26  <meta property="og:image" content="https://a1.asdqwe.vip/banner/piontogel-img1">
27  <meta property="og:image:alt" content="Menghadirkan PIONTOGEL - Situs Alami Mendapatkan Cuan Dengan Permainan Resmi">
28<meta property="product:price:amount" content="5000">
29<meta property="product:price:currency" content="IDR">
30<meta property="product:availability" content="out of stock">
31<meta name="twitter:card" content="summary_large_image">
32<meta name="twitter:title" content="Menghadirkan PIONTOGEL - Situs Alami Mendapatkan Cuan Dengan Permainan Resmi">
33<meta name="twitter:description" content="PIONTOGEL merepresentasikan wahana digital dengan artikulasi modern yang mengakomodasi beragam permainan resmi secara terintegrasi. Melalui tata kelola antarmuka yang sistematis, navigasi adaptif, dan aksesibilitas yang optimal, platform ini menghadirkan pengalaman digital yang praktis sekaligus atraktif.">
34<meta name="twitter:image" content="https://a1.asdqwe.vip/banner/piontogel-img1">
35
36<script type="application/ld+json">
37      {
38        "@context": "http:\/\/schema.org",
39        "@type": "BreadcrumbList",
40        "itemListElement": [
41          {
42            "@type": "ListItem",
43            "item": {
44              "@id": "https://calcourtsfitness.com/",
45              "name": "Menghadirkan PIONTOGEL - Situs Alami Mendapatkan Cuan Dengan Permainan Resmi"
46            },
47            "position": 1
48          }
49        ]
50      }
51    </script>
51
52    
52<script type="application/ld+json">
53      {
54        "@context": "http:\/\/schema.org",
55        "@type": "Product",
56        "name": "Menghadirkan PIONTOGEL - Situs Alami Mendapatkan Cuan Dengan Permainan Resmi",
57        "description": "PIONTOGEL merepresentasikan wahana digital dengan artikulasi modern yang mengakomodasi beragam permainan resmi secara terintegrasi. Melalui tata kelola antarmuka yang sistematis, navigasi adaptif, dan aksesibilitas yang optimal, platform ini menghadirkan pengalaman digital yang praktis sekaligus atraktif.",
58        "image": "https://a1.asdqwe.vip/banner/piontogel-img1",
59        "offers": {
60          "@type": "http:\/\/schema.org\/Offer",
61          "price": 9999,
62          "url": "https://calcourtsfitness.com/",
63          "priceCurrency": "IDR",
64          "availability": "http:\/\/schema.org\/InStock"
65        }
66      }
67    </script>
67
68    
68<script type="application/ld+json">
69      {
70        "@context": "http:\/\/schema.org\/",
71        "@type": "WebPage",
72        "speakable": {
73          "@type": "SpeakableSpecification",
74          "cssSelector": [".description"],
75          "xpath": ["\/html\/head\/title"]
76        }
77      }
78    </script>
78
79    <!-- ZS -->
80    <!-- Schema JSON untuk SBO -->
81<script type="application/ld+json">
82{
83"@context": "https://schema.org/",
84"@type": "Product",
85"name": "Menghadirkan PIONTOGEL - Situs Alami Mendapatkan Cuan Dengan Permainan Resmi",
86"url": "https://calcourtsfitness.com/",
87"sku": "BANDAR TOGEL",
88"image": [
89  "https://a1.asdqwe.vip/banner/piontogel-img1"
90],
91"description": "PIONTOGEL merepresentasikan wahana digital dengan artikulasi modern yang mengakomodasi beragam permainan resmi secara terintegrasi. Melalui tata kelola antarmuka yang sistematis, navigasi adaptif, dan aksesibilitas yang optimal, platform ini menghadirkan pengalaman digital yang praktis sekaligus atraktif.",
92"brand": {
93  "@type": "Organization",
94  "name": "TOGEL ONLINE"
95},
96"offers": {
97  "@type": "Offer",
98  "name": "Menghadirkan PIONTOGEL - Situs Alami Mendapatkan Cuan Dengan Permainan Resmi",
99  "sku": "TOGEL ONLINE",
100  "price": "9999",
101  "priceCurrency": "IDR",
102  "availability": "https://schema.org/InStock"
103}
104}
105</script>
105
106
107<script type="application/ld+json">
108{
109"@context": "https://schema.org/",
110"@type": "Product",
111"name": "Menghadirkan PIONTOGEL - Situs Alami Mendapatkan Cuan Dengan Permainan Resmi",
112"image": "https://a1.asdqwe.vip/banner/piontogel-img1",
113"description": "PIONTOGEL merepresentasikan wahana digital dengan artikulasi modern yang mengakomodasi beragam permainan resmi secara terintegrasi. Melalui tata kelola antarmuka yang sistematis, navigasi adaptif, dan aksesibilitas yang optimal, platform ini menghadirkan pengalaman digital yang praktis sekaligus atraktif.",
114"sku": "TOGEL ONLINE",
115"offers": {
116  "@type": "Offer",
117  "priceCurrency": "IDR",
118  "url": "https://calcourtsfitness.com/",
119  "availability": "https://schema.org/InStock",
120  "price": "9999.00"
121}
122}
123</script>
123
124
125<script type="application/ld+json" id="bv-jsonld-bvloader-summary">
126{
127"@context": "https://schema.org/",
128"@id": "https://calcourtsfitness.com/",
129"aggregateRating": {
130  "@type": "AggregateRating",
131  "itemReviewed": {
132    "@type": "IndividualProduct",
133    "name": "BANDAR TOGEL"
134  },
135  "ratingValue": "5.0",
136  "reviewCount": 954638,
137  "bestRating": 5
138}
139}
140</script>
140
141
142<script type="application/ld+json">
143{
144"@context": "https://schema.org",
145"@type": "BreadcrumbList",
146"itemListElement": [
147  {
148    "@type": "ListItem",
149    "position": 1,
150    "item": {
151      "@id": "https://calcourtsfitness.com/",
152      "name": "BANDAR TOGEL"
153    }
154  },
155  {
156    "@type": "ListItem",
157    "position": 2,
158    "item": {
159      "@id": "https://calcourtsfitness.com/",
160      "name": "Menghadirkan PIONTOGEL - Situs Alami Mendapatkan Cuan Dengan Permainan Resmi"
161    }
162  },
163  {
164    "@type": "ListItem",
165    "position": 3,
166    "item": {
167      "@id": "https://calcourtsfitness.com/",
168      "name": "TOGEL ONLINE"
169    }
170  },
171  {
172    "@type": "ListItem",
173    "position": 4,
174    "item": {
175      "@id": "https://calcourtsfitness.com/",
176      "name": "Menghadirkan PIONTOGEL - Situs Alami Mendapatkan Cuan Dengan Permainan Resmi"
177    }
178  }
179]
180}
181</script>
181
182
183<script type="application/ld+json" id="bv-jsonld-reviews-data">
184{
185  "@context": "https://schema.org",
186  "@type": "Organization",
187  "@id": "https://calcourtsfitness.com/",
188  "name": "BANDAR TOGEL ONLINE",
189  "alternateName": "BANDAR TOGEL ONLINE",
190  "url": "https://calcourtsfitness.com/",
191  "review": [
192    {
193      "@type": "Review",
194      "dateCreated": "2026-03-31T12:28:35+00:00",
195      "datePublished": "2026-03-31T12:28:35+00:00",
196      "headline": "TOGEL ONLINE",
197      "reviewBody": "Yang bikin saya tertarik adalah pilihan permainannya cukup beragam. Jadi bisa melihat-lihat dulu sebelum menentukan pilihan.",
198      "reviewRating": {
199        "@type": "Rating",
200        "ratingValue": 5,
201        "bestRating": 5,
202        "worstRating": 1
203      },
204      "author": {
205        "@type": "Person",
206        "name": "Rian Pratama – Surabaya"
207      }
208    },
209    {
210      "@type": "Review",
211      "dateCreated": "2026-04--02T18:28:21+00:00",
212      "datePublished": "2026-04-03T18:28:21+00:00",
213      "headline": "TOGEL ONLINE",
214      "reviewBody": "Pasarannya lengkap dan riwayat keluaran tersusun rapi. Saya jadi lebih mudah membandingkan result sekaligus melihat pola angka yang sering muncul.",
215      "reviewRating": {
216        "@type": "Rating",
217        "ratingValue": 5,
218        "bestRating": 5,
219        "worstRating": 1
220      },
221      "author": {
222        "@type": "Person",
223        "name": "Vinka Aurelia – Semarang"
224      }
225    },
226    {
227      "@type": "Review",
228      "dateCreated": "2026-04-05T19:28:18+00:00",
229      "datePublished": "2026-04-05T19:28:18+00:00",
230      "headline": "TOGEL ONLINE",
231      "reviewBody": "Menurut saya konsepnya cukup beda. Informasi yang ditampilkan nggak terasa terlalu penuh dan masih nyaman buat dijelajahi.",
232      "reviewRating": {
233        "@type": "Rating",
234        "ratingValue": 5,
235        "bestRating": 5,
236        "worstRating": 1
237      },
238      "author": {
239        "@type": "Person",
240        "name": "Violet Evergarde – Makassar"
241      }
242    }
243  ]
244}
245</script>
vendor: 2 bytes, line 245
245
246<script type="application/ld+json">
247{
248  "@context": "https://schema.org",
249  "@type": "FAQPage",
250  "mainEntity": [
251    {
252  "@type": "Question",
253  "name": "Apa yang menjadi ciri khas PIONTOGEL?",
254  "acceptedAnswer": {
255    "@type": "Answer",
256    "text": "PIONTOGEL mengusung konsep ruang digital yang memadukan keberagaman permainan, penyajian informasi, dan pengalaman penggunaan dalam satu platform."
257  }
258},
259{
260  "@type": "Question",
261  "name": "Mengapa keberagaman permainan menjadi bagian penting?",
262  "acceptedAnswer": {
263    "@type": "Answer",
264    "text": "Keberagaman memberikan keleluasaan bagi pengguna untuk mengenali berbagai pilihan yang tersedia sesuai dengan ketertarikannya masing-masing."
265  }
266},
267{
268  "@type": "Question",
269  "name": "Apa yang dimaksud dengan perspektif berbeda pada PIONTOGEL?",
270  "acceptedAnswer": {
271    "@type": "Answer",
272    "text": "Perspektif tersebut merujuk pada cara platform menyusun informasi dan menghadirkan pilihan hiburan secara lebih terarah, bukan sekadar menampilkan banyak elemen sekaligus."
273  }
274},
275{
276  "@type": "Question",
277  "name": "Bisa PIONTOGEL diakses menggunakan ponsel?",
278  "acceptedAnswer": {
279    "@type": "Answer",
280    "text": "Pengguna dapat melihat kategori dan informasi yang disediakan pada platform, kemudian memilih bagian yang ingin dieksplorasi berdasarkan keterangannya."
281  }
282},
283{
284  "@type": "Question",
285  "name": "Apakah PIONTOGEL cocok untuk pengguna baru?",
286  "acceptedAnswer": {
287    "@type": "Answer",
288    "text": "Platform dengan penyajian informasi yang terstruktur dapat membantu pengguna baru mengenali berbagai bagian secara bertahap tanpa harus memahami seluruh fitur sekaligus."
289  }
290}
291  ]
292}
293</script>
293
294  <!-- User Session Data -->
295    <meta name="csrf-token" content="9ce6e4a17f7cc189893df9e47789b13e98829450d516a0cae5ca7b5f43e5dadc">
296  <meta name="user-logged-in" content="false">
297  <meta name="user-id" content="0">
298
299  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
300
301  <link rel="shortcut icon" href="https://a1.asdqwe.vip/assets/icon-piontogel.png" type="image/x-icon">
302  <style data-inlined="style.css">
303@font-face {
304    font-family: 'Segoe UI';
305    src: url('/assets/fonts/segoeui.woff2') format('woff2'),
306         url('/assets/fonts/segoeui.woff') format('woff');
307    font-weight: 400;
308    font-style: normal;
309}
310
311:root {
312  --brand-50:#FFF5E6;
313  --brand-100:#FFE7CC;
314  --brand-500:#F28C1A;
315  --brand-600:#D97809;
316  --gray-900:#1F1F23;
317  --gray-700:#3D3F45;
318  --gray-500:#6F737B;
319  --gray-300:#C5C9CF;
320  --gray-100:#F3F5F7;
321  --border:#E4E7EB;
322  --radius:8px;
323  --transition: .18s ease;
324  /* font-family: "Inter", system-ui, sans-serif; */
325  font-family: 'Segoe UI', sans-serif;
326}
327
328* { box-sizing: border-box; }
329body {
330  margin:0;
331  padding-bottom: 0; /* Reset for desktop */
332  color:var(--gray-900);
333  background:#fff;
334}
335img { max-width:100%; display:block; }
336
337.container {
338  width:100%;
339  max-width:1280px;
340  margin:0 auto;
341  padding:10px 0;
342}
343
344.topbar {
345  background:#111;
346  color:#fff;
347  font-size:13px;
348}
349.topbar-inner {
350  display:flex;
351  justify-content:space-between;
352  align-items:center;
353  min-height:36px;
354}
355.topbar a { color:#fff; text-decoration:none; margin-left:12px; }
356
357/* ========== Desktop base (Glassmorphism Modern with Warm Gradient) ========== */
358.navbar {
359  background:linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(255,245,230,0.85) 100%);
360  backdrop-filter:blur(12px);
361  -webkit-backdrop-filter:blur(12px);
362  border-bottom:1px solid rgba(228,231,235,0.6);
363  box-shadow:0 2px 16px -4px rgba(0,0,0,0.08);
364  position:sticky;
365  top:0;
366  z-index:500;
367  transition:all .3s ease;
368}
369
370/* Accent line gradient orange */
371.navbar::after {
372  content:'';
373  position:absolute;
374  bottom:0;
375  left:0;
376  right:0;
377  height:2px;
378  background:linear-gradient(90deg,transparent 0%, var(--brand-500) 20%, var(--brand-600) 50%, var(--brand-500) 80%, transparent 100%);
379  opacity:0.5;
380}
381
382.nav-inner {
383  display:flex;
384  align-items:center;
385  gap:24px;
386  min-height:70px;
387}
388.nav-inner .search .m-search-ico { display: none; }
389
390/* Secara default (desktop) ikon login disembunyikan; teks muncul */
391.login-btn .login-ico { display:none; }
392.navbar-logo {
393  display: none;
394  background:linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(255,245,230,0.85) 100%);
395  backdrop-filter:blur(12px);
396  -webkit-backdrop-filter:blur(12px);
397  border-bottom:1px solid rgba(228,231,235,0.6);
398  box-shadow:0 2px 16px -4px rgba(0,0,0,0.08);
399  position:sticky;
400  top:0;
401  z-index:500;
402}
403
404/* Hide bottom navigation on desktop - only show on mobile */
405.bottom-nav {
406  display: none;
407}
408
409/* ==============================================================
410   MOBILE (<=640px)
411   ============================================================== */
412@media (max-width:640px){
413  .container { padding:10px 16px; }
414
415  .topbar { display:none !important; }
416
417  /* Add padding bottom for sticky bottom nav */
418  body {
419    padding-bottom: 68px; /* Height of bottom nav + safe area */
420  }
421
422  /* Mobile Top Navbar - Clean & Minimal */
423  .navbar {
424    background:linear-gradient(135deg, rgba(255,255,255,0.97) 0%, rgba(255,245,230,0.9) 100%);
425    backdrop-filter:blur(16px);
426    -webkit-backdrop-filter:blur(16px);
427    box-shadow:0 2px 12px -3px rgba(0,0,0,0.08);
428    border: 0;
429  }
430
431  /* Simple grid layout - Logo, Search, Category */
432  .nav-inner {
433    display:grid;
434    grid-template-columns:1fr;
435    gap:10px;
436    padding:12px 16px 0 12px;
437    min-height:auto;
438  }
439
440  /* Logo centered at top */
441  .nav-inner .logo {
442    justify-self:center;
443    margin:0;
444    display:flex;
445  }
446  .nav-inner .logo img { height:36px; }
447
448  /* Search bar - full width */
449  .nav-inner .search {
450    position:relative;
451    display:flex;
452    align-items:center;
453    gap:0;
454    width:100%;
455    height:44px;
456    background:#fff;
457    border:1.5px solid #e5e7eb;
458    border-radius:12px;
459    padding:0 12px;
460    box-shadow:0 1px 3px rgba(0,0,0,0.04);
461    transition:all .25s;
462  }
463  .nav-inner .search:focus-within {
464    border-color:#fb923c;
465    box-shadow:0 0 0 3px rgba(251, 146, 60, 0.1);
466  }
467
468  .nav-inner .search-icon-btn {
469    color:#9ca3af;
470    padding:4px;
471    margin-right:4px;
472  }
473
474  .nav-inner .search-icon-btn svg {
475    width:18px;
476    height:18px;
477  }
478
479  .nav-inner .search input {
480    flex:1;
481    min-width:0;
482    border:none;
483    outline:none;
484    background:transparent;
485    font-size:14px;
486    padding:0 8px;
487    height:100%;
488  }
489
490  .nav-inner .ai-bot-toggle {
491    padding:6px;
492    margin-left:0;
493  }
494
495  .nav-inner .ai-bot-toggle .bot-icon {
496    width:20px;
497    height:20px;
498  }
499
500  .nav-inner .search-suggest {
501    left:0;
502    right:0;
503    width:calc(100vw - 32px);
504    margin-top:6px;
505    border-radius:12px;
506    max-height:70vh;
507  }
508
509  /* Hide desktop nav actions but keep structure for userMenu */
510  .nav-inner .nav-actions {
511    display:flex !important; /* Keep flex to allow userMenu positioning */
512    visibility:hidden; /* Hide visually but keep in DOM */
513    pointer-events:none; /* Disable interactions with hidden elements */
514    position:absolute; /* Remove from flow */
515    right:0;
516    top:0;
517  }
518
519  /* Re-enable userMenu for logged-in users */
520  .nav-inner .nav-actions #userMenu {
521    visibility:visible !important;
522    pointer-events:auto !important;
523  }
524
525  .nav-inner .logo { display: none; }
526  .navbar-logo {
527    display: block;
528    /* background:linear-gradient(135deg, rgba(255,255,255,0.97) 0%, rgba(255,245,230,0.9) 100%); */
529    backdrop-filter:blur(16px);
530    -webkit-backdrop-filter:blur(16px);
531    position: static; /* Remove sticky on mobile */
532    box-shadow: none;
533    border-bottom: 0;
534    padding-top: .6rem;
535  }
536  .nav-inner .logo-mobile { display: block; }
537
538  /* Category menu wrapper will handle visibility */
539
540  /* ==============================================================
541     BOTTOM NAVIGATION - Styles moved to bottom-nav.css
542     ============================================================== */
543  /* All bottom-nav styles now in /assets/css/bottom-nav.css */
544
545  /* Hide desktop elements that now moved to bottom nav */
546  #wishlistBtn,
547  #notifBtn,
548  #loginBtn { display:none !important; }
549
550  /* Hide desktop user menu on mobile - use bottom sheet instead */
551  .navbar #userMenu {
552    display:none !important;
553  }
554
555  .nav-actions .cart-indicator,
556  .nav-actions .compare-indicator {
557    display:none !important;
558  }
559
560  /* Hide category button from bottom nav */
561  #bottomCategoryBtn {
562    display: none !important;
563  }
564
565  /* User menu button styling for mobile - scoped to navbar only */
566  .navbar #userMenu .login-btn.logged-in {
567    width:40px;
568    height:40px;
569    padding:0;
570    border-radius:50%;
571    display:flex;
572    align-items:center;
573    justify-content:center;
574    background:rgba(255,255,255,0.95);
575    backdrop-filter:blur(12px);
576    -webkit-backdrop-filter:blur(12px);
577    border:1px solid rgba(228,231,235,0.6);
578    box-shadow:0 2px 12px -3px rgba(0,0,0,0.12);
579  }
580
581  .navbar #userMenu .login-btn.logged-in .login-text {
582    display:none;
583  }
584
585  .navbar #userMenu .login-btn.logged-in .login-ico {
586    display:inline;
587    font-size:20px;
588    line-height:1;
589  }
590
591  /* User dropdown positioning for mobile */
592  .navbar #userMenu .user-dropdown {
593    position:fixed !important;
594    top:60px !important;
595    right:16px !important;
596    left:auto !important;
597    min-width:200px;
598    background:rgba(255,255,255,0.98);
599    backdrop-filter:blur(20px);
600    -webkit-backdrop-filter:blur(20px);
601    border:1px solid rgba(228,231,235,0.6);
602    box-shadow:0 8px 24px -8px rgba(0,0,0,0.2);
603    z-index:1002 !important;
604    border-radius:12px;
605    padding:8px 0;
606  }
607
608  /* Force visibility when not hidden attribute */
609  .navbar #userMenu .user-dropdown:not([hidden]) {
610    display:block !important;
611    visibility:visible !important;
612    opacity:1 !important;
613  }
614
615  /* Container untuk kategori dan tombol kategori sejajar */
616  .navbar .container:last-child {
617    display:flex !important;
618    align-items:center !important;
619    padding:4px 16px 6px !important;
620    gap:5px;
621    overflow:visible;
622  }
623
624  /* Wrapper untuk mega-cats dengan fade gradient (mobile only) */
625  .mega-cats-wrapper {
626    position:relative;
627    flex:1;
628    overflow:hidden;
629    min-height:34px; /* Ensure content is visible */
630    display:flex; /* Ensure wrapper is flex container */
631  }
632
633  /* Fade gradient indicator di ujung kanan (mobile only) */
634  .mega-cats-fade {
635    position:absolute;
636    top:0;
637    right:0;
638    bottom:0;
639    width:80px; /* Increased from 60px for better visibility */
640    background:linear-gradient(to left,
641      rgba(255,245,230,1) 0%,
642      rgba(255,245,230,0.98) 15%,
643      rgba(255,245,230,0.85) 35%,
644      transparent 100%);
645    pointer-events:none;
646    z-index:5; /* Increased z-index */
647    transition:opacity 0.3s ease;
648  }
649
650  /* Kategori menu di mobile */
651  .mega-cats.container {
652    display:flex !important;
653    gap:12px;
654    overflow-x:auto;
655    overflow-y:visible;
656    -webkit-overflow-scrolling:touch;
657    scrollbar-width:none;
658    flex:1;
659    padding:0 !important;
660    margin:0 !important;
661    position:relative; /* For z-index stacking */
662    z-index:1; /* Below fade gradient */
663  }
664  .mega-cats.container::-webkit-scrollbar { display:none; }
665
666  /* Styling untuk menu items di mobile */
667  .mega-cats a {
668    font-size:13px;
669    padding:6px 10px;
670    white-space:nowrap;
671    flex-shrink:0;
672    position:relative; /* Ensure proper stacking */
673  }
674  .mega-cats a.active {
675    background:var(--brand-600);
676    color:#fff;
677    font-weight:600;
678    border-radius:6px;
679  }
680
681  /* Tombol kategori di mobile */
682  .mm-wp {
683    flex-shrink:0;
684  }
685  .mm2-trigger {
686    display:flex;
687    align-items:center;
688    gap:6px;
689    padding:6px 12px;
690    background:var(--gray-100);
691    color:var(--gray-700);
692    text-decoration:none;
693    border-radius:6px;
694    font-size:13px;
695    white-space:nowrap;
696  }
697
698  .mm2-trigger .label-cat {display: none; }
699  /* Tombol more kategori */
700  .mega-more-btn {
701    border:1px solid var(--border);
702    background:#fff;
703    border-radius:10px;
704    padding:6px 14px;
705    font-size:18px;
706    cursor:pointer;
707  }
708  .mega-cats-more.active > .mega-more-btn {
709    background:var(--brand-600);
710    color:#fff;
711    border-color:var(--brand-600);
712  }
713  .mega-more-dropdown {
714    top:calc(100% + 10px);
715    right:0;
716    min-width:85vw;
717    max-width:90vw;
718    border-radius:16px;
719  }
720  .mega-more-grid {
721    grid-template-columns:repeat(auto-fill,minmax(120px,1fr));
722  }
723
724  /* ==============================================================
725     MOBILE USER BOTTOM SHEET
726     ============================================================== */
727  .mobile-user-sheet {
728    position:fixed;
729    top:0;
730    left:0;
731    right:0;
732    bottom:0;
733    z-index:10000;
734    pointer-events:none;
735  }
736
737  .mobile-user-sheet.active {
738    pointer-events:auto;
739  }
740
741  .mobile-user-sheet-backdrop {
742    position:absolute;
743    top:0;
744    left:0;
745    right:0;
746    bottom:0;
747    background:rgba(0,0,0,0.5);
748    opacity:0;
749    transition:opacity .3s ease;
750  }
751
752  .mobile-user-sheet.active .mobile-user-sheet-backdrop {
753    opacity:1;
754  }
755
756  .mobile-user-sheet-content {
757    position:absolute;
758    bottom:0;
759    left:0;
760    right:0;
761    background:#fff;
762    border-radius:20px 20px 0 0;
763    box-shadow:0 -4px 24px rgba(0,0,0,0.15);
764    transform:translateY(100%);
765    transition:transform .3s cubic-bezier(0.4, 0.0, 0.2, 1);
766    padding-bottom:calc(68px + env(safe-area-inset-bottom));
767  }
768
769  .mobile-user-sheet.active .mobile-user-sheet-content {
770    transform:translateY(0);
771  }
772
773  .mobile-user-sheet-header {
774    padding:16px 20px 12px;
775    border-bottom:1px solid #e5e7eb;
776  }
777
778  .mobile-user-sheet-handle {
779    width:40px;
780    height:4px;
781    background:#d1d5db;
782    border-radius:2px;
783    margin:0 auto 16px;
784  }
785
786  .mobile-user-info {
787    display:flex;
788    align-items:center;
789    gap:12px;
790  }
791
792  .mobile-user-avatar {
793    width:48px;
794    height:48px;
795    border-radius:50%;
796    background:linear-gradient(135deg, #FFE7CC, #FFD9A8);
797    display:flex;
798    align-items:center;
799    justify-content:center;
800    font-size:24px;
801    flex-shrink:0;
802  }
803
804  .mobile-user-details {
805    flex:1;
806    min-width:0;
807  }
808
809  .mobile-user-name {
810    font-size:16px;
811    font-weight:600;
812    color:#111827;
813    margin-bottom:2px;
814    white-space:nowrap;
815    overflow:hidden;
816    text-overflow:ellipsis;
817  }
818
819  .mobile-user-email {
820    font-size:13px;
821    color:#6b7280;
822    white-space:nowrap;
823    overflow:hidden;
824    text-overflow:ellipsis;
825  }
826
827  .mobile-user-sheet-body {
828    padding:8px 0;
829  }
830
831  .mobile-user-sheet-item {
832    display:flex;
833    align-items:center;
834    gap:16px;
835    padding:14px 20px;
836    text-decoration:none;
837    color:#374151;
838    font-size:15px;
839    transition:background .2s;
840    border:none;
841    background:none;
842    width:100%;
843    text-align:left;
844    cursor:pointer;
845  }
846
847  .mobile-user-sheet-item:active {
848    background:#f3f4f6;
849  }
850
851  .mobile-user-sheet-icon {
852    font-size:20px;
853    width:24px;
854    text-align:center;
855    flex-shrink:0;
856  }
857
858  .mobile-user-sheet-label {
859    flex:1;
860    font-weight:500;
861  }
862
863  .mobile-user-sheet-divider {
864    height:1px;
865    background:#e5e7eb;
866    margin:8px 0;
867  }
868}
869
870/* END NAVBAR */
871
872.logo img { display:block; }
873.search {
874  flex:1;
875  position:relative;
876  display:flex;
877  align-items:center;
878  gap:0;
879  background:#fff;
880  border:1.5px solid #e5e7eb;
881  border-radius:12px;
882  padding:0 16px;
883  height:38px;
884  transition:all 0.3s ease;
885}
886.search:focus-within {
887  border-color:#fb923c;
888  box-shadow:0 0 0 3px rgba(251, 146, 60, 0.1);
889}
890.search input {
891  flex:1;
892  border:none;
893  outline:none;
894  background:transparent;
895  font-size:15px;
896  padding:0 12px;
897  height:100%;
898  color:#1f2937;
899}
900.search input::placeholder {
901  color:#9ca3af;
902}
903.search-icon-btn {
904  background:transparent;
905  border:none;
906  padding:0;
907  display:flex;
908  align-items:center;
909  justify-content:center;
910  cursor:pointer;
911  color:#6b7280;
912  transition:color 0.2s ease;
913  flex-shrink:0;
914}
915.search-icon-btn:hover {
916  color:#fb923c;
917}
918.search-suggest {
919  position:absolute;
920  left:0; right:0; top:100%;
921  background:#fff;
922  border:1px solid var(--border);
923  border-radius:var(--radius);
924  padding:8px 0;
925  box-shadow:0 8px 20px rgba(0,0,0,.08);
926  margin-top:4px;
927  font-size:14px;
928  max-height:260px;
929  overflow:auto;
930}
931.search-suggest div {
932  padding:6px 12px;
933  cursor:pointer;
934}
935.search-suggest div:hover { background:var(--gray-100); }
936
937.nav-actions { display:flex; gap:10px; align-items:center; }
938.icon-btn, .login-btn {
939  background:linear-gradient(135deg, rgba(255,255,255,0.95), rgba(249,250,251,0.9));
940  border:1px solid rgba(228,231,235,0.6);
941  padding:8px 12px;
942  border-radius:var(--radius);
943  cursor:pointer;
944  font-size:14px;
945  transition:all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
946  height: 38px;
947  position:relative;
948  box-shadow:
949    0 1px 3px rgba(0,0,0,0.06),
950    0 0 0 1px rgba(255,255,255,0.5) inset;
951  backdrop-filter:blur(8px);
952  -webkit-backdrop-filter:blur(8px);
953}
954.icon-btn:hover, .login-btn:hover {
955  background:linear-gradient(135deg, rgba(249,250,251,1), rgba(243,244,246,0.95));
956  border-color:rgba(209,213,219,0.8);
957  box-shadow:
958    0 2px 8px rgba(0,0,0,0.08),
959    0 0 0 1px rgba(255,255,255,0.6) inset;
960  transform:translateY(-1px);
961}
962.cart-indicator, .compare-indicator { position:relative; }
963.badge {
964  background:linear-gradient(135deg, #EF4444, #DC2626);
965  color:#fff;
966  font-size:10.5px;
967  font-weight:600;
968  padding:2px 6px;
969  border-radius:999px;
970  position:absolute;
971  top:-6px;
972  right:-6px;
973  min-width:18px;
974  height:18px;
975  display:flex;
976  align-items:center;
977  justify-content:center;
978  box-shadow:
979    0 2px 8px rgba(239,68,68,0.4),
980    0 0 0 2px #fff,
981    0 0 12px -2px rgba(239,68,68,0.6);
982  transition:all 0.3s ease;
983}
984.icon-btn:hover .badge {
985  transform:scale(1.08);
986  box-shadow:
987    0 4px 12px rgba(239,68,68,0.5),
988    0 0 0 2px #fff,
989    0 0 16px -2px rgba(239,68,68,0.7);
990}
991.badge.is-empty { display:none; }
992
993/* User Menu Dropdown */
994.user-menu {
995  position:relative;
996}
997.user-dropdown {
998  position:absolute;
999  top:calc(100% + 8px);
1000  right:0;
1001  background:#fff;
1002  border:1px solid var(--border);
1003  border-radius:12px;
1004  box-shadow:0 10px 40px rgba(0,0,0,0.12);
1005  min-width:200px;
1006  padding:8px 0;
1007  z-index:9999;
1008}
1009
1010/* Ensure dropdown is visible when not hidden */
1011.user-dropdown:not([hidden]) {
1012  display:block !important;
1013  visibility:visible !important;
1014  opacity:1 !important;
1015}
1016
1017/* Mobile: Force user dropdown to be visible above everything */
1018@media (max-width:640px) {
1019  .user-dropdown:not([hidden]) {
1020    position:fixed !important;
1021    z-index:9999 !important;
1022    top:60px !important;
1023    right:16px !important;
1024  }
1025}
1026
1027.dropdown-item {
1028  display:flex;
1029  align-items:center;
1030  gap:12px;
1031  padding:10px 16px;
1032  text-decoration:none;
1033  color:var(--gray-700);
1034  font-size:14px;
1035  transition:var(--transition);
1036  border:none;
1037  background:none;
1038  width:100%;
1039  text-align:left;
1040  cursor:pointer;
1041}
1042.dropdown-item:hover {
1043  background:var(--gray-100);
1044  color:var(--gray-900);
1045}
1046.dropdown-icon {
1047  font-size:16px;
1048  opacity:0.7;
1049}
1050.dropdown-separator {
1051  height:1px;
1052  background:var(--border);
1053  margin:8px 0;
1054}
1055.login-btn.logged-in {
1056  background:linear-gradient(135deg, rgba(255,245,230,0.8), rgba(255,231,204,0.6));
1057  border:1px solid rgba(242,140,26,0.2);
1058  color:var(--brand-700);
1059  max-width:200px; /* Prevent button from getting too wide */
1060  display:inline-flex;
1061  align-items:center;
1062  gap:10px;
1063  padding:4px 12px 4px 4px;
1064  transition:all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1065  position:relative;
1066  overflow:hidden;
1067  box-shadow:
1068    0 1px 3px rgba(242,140,26,0.08),
1069    0 0 0 1px rgba(255,255,255,0.5) inset,
1070    0 0 12px -6px rgba(242,140,26,0.1);
1071  backdrop-filter:blur(8px);
1072  -webkit-backdrop-filter:blur(8px);
1073}
1074/* Subtle background glow effect */
1075.login-btn.logged-in::before {
1076  content:'';
1077  position:absolute;
1078  top:50%;
1079  left:0;
1080  width:32px;
1081  height:32px;
1082  background:radial-gradient(circle, rgba(242,140,26,0.15), transparent 70%);
1083  transform:translateY(-50%);
1084  opacity:0;
1085  transition:opacity 0.3s ease;
1086}
1087.login-btn.logged-in:hover::before {
1088  opacity:1;
1089}
1090.login-btn.logged-in .user-avatar {
1091  width:32px;
1092  height:32px;
1093  border-radius:50%;
1094  background:linear-gradient(135deg, #FFE7CC 0%, #FFD9A8 50%, #FFCC80 100%);
1095  color:#D97809;
1096  font-size:11.5px;
1097  font-weight:700;
1098  display:flex;
1099  align-items:center;
1100  justify-content:center;
1101  flex-shrink:0;
1102  letter-spacing:0.3px;
1103  position:relative;
1104  box-shadow:
1105    0 2px 8px rgba(242,140,26,0.25),
1106    0 1px 3px rgba(0,0,0,0.12),
1107    inset 0 1px 0 rgba(255,255,255,0.5);
1108  transition:all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1109}
1110/* Ring gradient border */
1111.login-btn.logged-in .user-avatar::before {
1112  content:'';
1113  position:absolute;
1114  inset:-2px;
1115  border-radius:50%;
1116  padding:2px;
1117  background:linear-gradient(135deg, #F28C1A, #FFD9A8, #F28C1A);
1118  -webkit-mask:linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
1119  -webkit-mask-composite:xor;
1120  mask:linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
1121  mask-composite:exclude;
1122  opacity:0;
1123  transition:opacity 0.3s ease;
1124}
1125.login-btn.logged-in:hover .user-avatar::before {
1126  opacity:1;
1127}
1128.login-btn.logged-in:hover .user-avatar {
1129  transform:scale(1.08) rotate(-3deg);
1130  box-shadow:
1131    0 4px 12px rgba(242,140,26,0.35),
1132    0 2px 6px rgba(0,0,0,0.15),
1133    inset 0 1px 0 rgba(255,255,255,0.6);
1134}
1135.login-btn.logged-in .login-text {
1136  overflow:hidden;
1137  text-overflow:ellipsis;
1138  white-space:nowrap;
1139  flex:1;
1140  min-width:0;
1141  font-weight:500;
1142  font-size:14px;
1143  transition:color 0.2s ease;
1144}
1145.login-btn.logged-in:hover .login-text {
1146  color:var(--brand-600);
1147}
1148.login-btn.logged-in:hover {
1149  background:linear-gradient(135deg, rgba(255,245,230,0.95), rgba(255,231,204,0.85));
1150  border-color:rgba(242,140,26,0.3);
1151  box-shadow:
1152    0 4px 16px -4px rgba(242,140,26,0.25),
1153    0 0 0 1px rgba(242,140,26,0.1) inset,
1154    0 1px 2px rgba(0,0,0,0.05);
1155  transform:translateY(-1px);
1156}
1157
1158/* Desktop: hide wrapper elements, show menu normally */
1159@media (min-width: 761px) {
1160  .mega-cats-wrapper {
1161    display:contents; /* Remove wrapper from layout */
1162  }
1163  .mega-cats-fade {
1164    display:none !important; /* Hide fade on desktop */
1165  }
1166}
1167
1168.mega-cats {
1169  display:flex;
1170  gap:5px;
1171  /* padding:6px 16px 10px; */
1172  scrollbar-width:none;
1173  position: relative;
1174  z-index: 510;
1175  overflow-x:auto;
1176  -webkit-overflow-scrolling:touch;
1177  flex:1;
1178}
1179.mega-cats::-webkit-scrollbar {
1180  display:none;
1181}
1182.mega-cats a {
1183  text-decoration:none;
1184  font-size:14px;
1185  color:var(--gray-700);
1186  padding:6px 10px;
1187  border-radius:6px;
1188  white-space:nowrap;
1189  transition:var(--transition);
1190}
1191.mega-cats a:hover { background:var(--brand-100); color:var(--brand-600); }
1192/*.mega-cats::after {
1193  content:'';
1194  position:absolute;
1195  right:0;
1196  top:0;
1197  width:40px;
1198  height:100%;
1199  background:linear-gradient(to right,rgba(255,255,255,0),#fff);
1200  pointer-events:none;
1201}*/
1202
1203/* Tambahan untuk tombol More & dropdown.
1204   Style dasar .mega-cats dan .mega-cats a tetap pakai definisi Anda. */
1205
1206.mega-cats-more {
1207  position:relative;
1208  display:flex;
1209  align-items:center;
1210}
1211
1212.mega-cats-more.active > .mega-more-btn {
1213  background:var(--brand-600);
1214  color:#fff;
1215}
1216
1217.mega-more-btn {
1218  cursor:pointer;
1219  border:none;
1220  background:var(--gray-100,#f5f5f5);
1221  color:var(--gray-700,#333);
1222  padding:6px 12px;
1223  font-size:16px;
1224  line-height:1;
1225  border-radius:6px;
1226  display:flex;
1227  align-items:center;
1228  justify-content:center;
1229  transition:var(--transition);
1230}
1231.mega-more-btn:hover {
1232  background:var(--brand-100);
1233  color:var(--brand-600);
1234}
1235
1236.mega-more-dropdown {
1237  position:absolute;
1238  top:100%;
1239  right:0;
1240  margin-top:8px;
1241  background:#fff;
1242  border:1px solid var(--gray-200,#e3e6e9);
1243  border-radius:10px;
1244  padding:12px 14px 14px;
1245  min-width:400px;
1246  max-width:520px;
1247  max-height:420px;
1248  box-shadow:0 12px 32px -8px rgba(25,40,65,.25);
1249  z-index: 3000;
1250}
1251
1252.mega-more-grid {
1253  display:grid;
1254  grid-template-columns:repeat(auto-fill,minmax(120px,1fr));
1255  gap:8px 10px;
1256  overflow-x: hidden;
1257  max-height:380px;
1258  overscroll-behavior:contain;
1259  scrollbar-width:thin;
1260}
1261.mega-more-grid::-webkit-scrollbar {
1262  width:6px;
1263}
1264.mega-more-grid::-webkit-scrollbar-thumb {
1265  background:var(--gray-300,#c7ccd1);
1266  border-radius:3px;
1267}
1268
1269/* Link di dropdown mewarisi style .mega-cats a (warna, padding, radius).
1270   Kita tambahkan state active agar konsisten. */
1271.mega-more-grid a.active {
1272  background:var(--brand-600);
1273  color:#fff;
1274  font-weight:600;
1275}
1276.hero {
1277  background:linear-gradient(135deg,var(--brand-50), #fff);
1278}
1279.hero-inner {
1280  display:grid;
1281  grid-template-columns:repeat(auto-fit,minmax(320px,1fr));
1282  gap:48px;
1283  align-items:center;
1284}
1285.hero-text h1 {
1286  font-size:48px;
1287  line-height:1.1;
1288  margin:0;
1289  font-weight:600;
1290  font-family:"Poppins",sans-serif;
1291}
1292.hero-text .text-brand { color:var(--brand-500); }
1293.lead { font-size:18px; color:var(--gray-700); margin-top:16px; }
1294.hero-cta { margin-top:28px; display:flex; gap:16px; }
1295.hero-bullets { margin-top:28px; display:flex; gap:28px; font-size:14px; color:var(--gray-600); }
1296.hero-box {
1297  background:#fff;
1298  padding:10px;
1299  border-radius:16px;
1300  box-shadow:0 4px 18px rgba(0,0,0,.08);
1301  /* min-height:320px; */
1302  display:flex;
1303  align-items:center;
1304  justify-content:center;
1305}
1306.hero-box img {border-radius: 10px; }
1307
1308@media (max-width:680px) {
1309    .hero-inner { gap: 25px; }
1310}
1311
1312.usp-section {
1313  position:relative;
1314  background:
1315    linear-gradient(180deg,#FFFDFB 0%, #FFFFFF 60%) ;
1316  border-top:1px solid var(--border);
1317  border-bottom:1px solid var(--border);
1318  padding:10px 0 10px;
1319}
1320
1321.usp-list {
1322  list-style:none;
1323  margin:0;
1324  padding:0;
1325  display:grid;
1326  gap:26px;
1327}
1328
1329.usp-list.variant-soft {
1330  grid-template-columns:repeat(auto-fit,minmax(230px,1fr));
1331}
1332
1333.usp-item {
1334  display:flex;
1335  gap:16px;
1336  align-items:flex-start;
1337  background:#fff;
1338  padding:18px 18px 18px 16px;
1339  border:1px solid rgba(226,226,226,0.7);
1340  border-radius:18px;
1341  position:relative;
1342  overflow:hidden;
1343  isolation:isolate;
1344  box-shadow:0 2px 4px -1px rgba(0,0,0,.04), 0 6px 18px -6px rgba(0,0,0,.08);
1345  transition: .35s cubic-bezier(.16,.8,.32,1);
1346}
1347
1348.usp-item::before {
1349  content:"";
1350  position:absolute;
1351  inset:0;
1352  background:radial-gradient(circle at 18% 22%, rgba(242,140,26,.18), transparent 60%);
1353  opacity:.65;
1354  transition:.5s;
1355  z-index:-1;
1356}
1357
1358.usp-item:hover {
1359  transform:translateY(-6px);
1360  box-shadow:0 10px 26px -8px rgba(0,0,0,.15), 0 3px 8px -2px rgba(0,0,0,.08);
1361  border-color:rgba(242,140,26,.35);
1362}
1363
1364.usp-item:hover::before {
1365  background:radial-gradient(circle at 70% 40%, rgba(242,140,26,.22), transparent 65%);
1366}
1367
1368.usp-icon {
1369  flex:0 0 56px;
1370  width:56px;
1371  height:56px;
1372  border-radius:16px;
1373  display:flex;
1374  align-items:center;
1375  justify-content:center;
1376  background:
1377    linear-gradient(135deg,#FFE7CC,#FFD9A8);
1378  position:relative;
1379  box-shadow:inset 0 0 0 1px rgba(255,255,255,.6), 0 4px 10px -3px rgba(0,0,0,.15);
1380  transition:.45s;
1381}
1382
1383.usp-item:hover .usp-icon {
1384  transform:rotate(-4deg) scale(1.06);
1385  box-shadow:inset 0 0 0 1px rgba(255,255,255,.8), 0 6px 14px -4px rgba(0,0,0,.20);
1386}
1387
1388.usp-icon svg {
1389  width:28px;
1390  height:28px;
1391  display:block;
1392  filter:drop-shadow(0 2px 2px rgba(0,0,0,.18));
1393}
1394
1395.usp-text h3 {
1396  font-size:15px;
1397  font-weight:600;
1398  margin:2px 0 6px;
1399  letter-spacing:.2px;
1400}
1401
1402.usp-text p {
1403  font-size:13px;
1404  line-height:1.3;
1405  margin:0;
1406  color:var(--gray-600);
1407}
1408
1409@media (max-width:680px) {
1410  .usp-section { padding: 5px 0 5px; margin-bottom: 0.75rem;}
1411  .usp-item { padding:16px 14px; }
1412  .usp-icon { width:50px;height:50px;border-radius:14px; }
1413  .usp-text h3 { font-size:14px; margin-bottom:4px; }
1414  .usp-text p { font-size:12.5px; }
1415  .usp-list { gap: 10px; }
1416}
1417
1418.flash { padding:50px 0 30px; background:#fff; }
1419.flash-head {
1420  display:flex; align-items:center;
1421  justify-content:space-between;
1422  margin-bottom:20px;
1423}
1424.flash-head h2 { margin:0; font-size:24px; font-family:"Poppins"; }
1425.flash .countdown {
1426  font-family:monospace;
1427  background:#111;
1428  color:#fff;
1429  padding:8px 14px;
1430  border-radius:8px;
1431  font-size:16px;
1432  letter-spacing:1px;
1433}
1434.flash-scroll {
1435  display:flex;
1436  gap:16px;
1437  overflow-x:auto;
1438  padding-bottom:6px;
1439  scrollbar-width:none;
1440}
1441.flash-scroll::-webkit-scrollbar { display:none; }
1442
1443.products { padding:60px 0; background:#fff; }
1444.products-head {
1445  display:flex; justify-content:space-between; align-items:center;
1446  margin-bottom:24px;
1447  flex-wrap:wrap;
1448  gap:16px;
1449}
1450.products-head h2 { margin:0; font-size:24px; font-family:"Poppins"; }
1451.product-filters input {
1452  padding:8px 12px;
1453  border:1px solid var(--border);
1454  border-radius:8px;
1455  font-size:14px;
1456  width:220px;
1457}
1458
1459.product-grid {
1460  display:grid;
1461  gap:24px;
1462  grid-template-columns:repeat(auto-fill,minmax(180px,1fr));
1463}
1464
1465.prod-card {
1466  background:#fff;
1467  border:1px solid var(--border);
1468  padding:12px;
1469  border-radius:12px;
1470  position:relative;
1471  display:flex;
1472  flex-direction:column;
1473  transition:var(--transition);
1474}
1475.prod-card.small { min-width:160px; }
1476.prod-card:hover { box-shadow:0 8px 20px rgba(0,0,0,.12); transform:translateY(-3px); }
1477.prod-card .img-box {
1478  background:var(--gray-100);
1479  border-radius:10px;
1480  aspect-ratio:1/1;
1481  display:flex;
1482  align-items:center;
1483  justify-content:center;
1484  overflow:hidden;
1485}
1486.prod-card .img-box img { width:100%; height:100%; object-fit:contain; transition:var(--transition); }
1487.prod-card:hover .img-box img { transform:scale(1.04); }
1488.prod-card .name {
1489  font-size:13.5px;
1490  font-weight:500;
1491  margin:10px 0 6px;
1492  min-height:36px;
1493  line-height:1.3;
1494}
1495.price {
1496  display:flex;
1497  gap:6px;
1498  align-items:center;
1499  flex-wrap:wrap;
1500}
1501.price strong { color:var(--brand-600); font-size:14px; }
1502.price .old {
1503  font-size:11px;
1504  color:var(--gray-500);
1505  text-decoration:line-through;
1506}
1507.tag.discount {
1508  position:absolute;
1509  top:8px; left:8px;
1510  background:#E54848;
1511  color:#fff;
1512  font-size:11px;
1513  padding:4px 6px;
1514  border-radius:6px;
1515  font-weight:600;
1516}
1517.btn {
1518  border:none;
1519  cursor:pointer;
1520  font-weight:500;
1521  font-size:14px;
1522  padding:12px 22px;
1523  border-radius:10px;
1524  background:var(--gray-900);
1525  color:#fff;
1526  transition:var(--transition);
1527  text-decoration:none;
1528  text-align:center;
1529  display:inline-flex;
1530  align-items:center;
1531  justify-content:center;
1532  gap:6px;
1533}
1534.btn:hover { background:#000; }
1535.btn-primary { background:var(--brand-500); }
1536.btn-primary:hover { background:var(--brand-600); }
1537.btn-outline {
1538  background:#fff;
1539  color:var(--gray-900);
1540  border:1px solid var(--border);
1541}
1542.btn-outline:hover { background:var(--gray-100); }
1543.btn-sm { padding:8px 14px; font-size:13px; }
1544.btn-mini { padding:6px 10px; font-size:12px; }
1545.add-cart { margin-top:auto; background:var(--brand-500); color:#fff; }
1546.add-cart:hover { background:var(--brand-600); }
1547
1548/* ==== FOOTER COMPACT FIX PATCH (final) ==== */
1549
1550/* 1. Pastikan grid aktif */
1551
1552.site-footer.footer-compact .ft-grid {
1553  display:grid;
1554  grid-template-columns:repeat(auto-fit,minmax(240px,1fr));
1555  gap:30px 40px;
1556  align-items:start;
1557}
1558
1559/* 2. Spacing atas bawah dipadatkan */
1560.site-footer.footer-compact .ft-top {
1561  padding:42px 0 26px;
1562}
1563
1564/* 3. Judul & teks */
1565.site-footer.footer-compact .ft-title {
1566  margin:0 0 14px;
1567  font-size:14px;
1568  letter-spacing:.8px;
1569  text-transform:uppercase;
1570  font-weight:600;
1571  color:#82b1c9;
1572}
1573
1574.site-footer.footer-compact .ft-desc,
1575.site-footer.footer-compact .ft-text {
1576  margin:0 0 16px;
1577  font-size:13.5px;
1578  line-height:1.45;
1579  color:#c6d6e0;
1580  max-width:330px;
1581}
1582.site-footer.footer-compact .ft-desc.sm-gap { margin-top:18px; }
1583
1584/* 4. Reset ikon SVG agar tidak membesar */
1585.site-footer.footer-compact svg {
1586  width:20px;
1587  height:20px;
1588  flex:0 0 20px;
1589  display:inline-block;
1590}
1591.site-footer.footer-compact .ft-social svg { width:18px; height:18px; }
1592.site-footer.footer-compact .ft-wa-btn svg { width:18px; height:18px; }
1593
1594/* Jika ada aturan global svg { width:100%; } — paksa override */
1595svg { max-width:100%; }
1596.site-footer.footer-compact svg {
1597  width:20px !important;
1598  height:20px !important;
1599}
1600
1601/* 5. Link list */
1602.site-footer.footer-compact .ft-links {
1603  list-style:none;
1604  margin:0 0 12px;
1605  padding:0;
1606  display:flex;
1607  flex-direction:column;
1608  gap:4px;
1609}
1610.site-footer.footer-compact .ft-links li { margin:0; }
1611.site-footer.footer-compact .ft-links a {
1612  display:flex;
1613  align-items:center;
1614  gap:8px;
1615  font-size:13.5px;
1616  padding:5px 2px;
1617  text-decoration:none;
1618  color:#c6d6e0;
1619  border-radius:8px;
1620  transition:.25s;
1621}
1622.site-footer.footer-compact .ft-links a:hover {
1623  background:rgba(255,255,255,.06);
1624  padding-left:6px;
1625  color:#fff;
1626}
1627
1628.site-footer.footer-compact .ico-s {
1629  width:24px;
1630  height:24px;
1631  background:#103346;
1632  color:#89b8cf;
1633  border-radius:6px;
1634  display:flex;
1635  align-items:center;
1636  justify-content:center;
1637  flex:0 0 24px;
1638  font-size:12px;
1639  transition:.3s;
1640}
1641.site-footer.footer-compact .ft-links a:hover .ico-s {
1642  background:#F28C1A;
1643  color:#fff;
1644  transform:rotate(-4deg);
1645}
1646
1647/* 6. WhatsApp button */
1648.site-footer.footer-compact .ft-wa-btn {
1649  display:inline-flex;
1650  align-items:center;
1651  gap:8px;
1652  background:#fff;
1653  color:#062437;
1654  padding:8px 18px;
1655  font-size:13.5px;
1656  border-radius:22px;
1657  font-weight:500;
1658  text-decoration:none;
1659  box-shadow:0 3px 8px -3px rgba(0,0,0,.45);
1660  transition:.25s;
1661}
1662.site-footer.footer-compact .ft-wa-btn:hover {
1663  transform:translateY(-2px);
1664  box-shadow:0 6px 14px -6px rgba(0,0,0,.55);
1665}
1666
1667/* 7. Sosial */
1668.site-footer.footer-compact .ft-social {
1669  display:flex;
1670  gap:12px;
1671  margin-top:4px;
1672}
1673.site-footer.footer-compact .ft-social a {
1674  width:34px;
1675  height:34px;
1676  background:rgba(255,255,255,.08);
1677  display:flex;
1678  align-items:center;
1679  justify-content:center;
1680  border-radius:10px;
1681  color:#c6d6e0;
1682  transition:.25s;
1683}
1684.site-footer.footer-compact .ft-social a:hover {
1685  background:#F28C1A;
1686  color:#fff;
1687}
1688
1689/* 8. Alamat */
1690.site-footer.footer-compact .ft-address {
1691  font-size:12.5px;
1692  line-height:1.44;
1693  color:#8fa4b1;
1694  max-width:330px;
1695  margin-top:12px;
1696}
1697.site-footer.footer-compact .ft-address .company {
1698  margin:0 0 4px;
1699  font-weight:600;
1700  color:#e5f2f7;
1701  font-size:12.8px;
1702}
1703
1704/* 9. Payments kecil & rapi */
1705.site-footer.footer-compact .ft-payments {
1706  margin-top:14px;
1707  display:flex;
1708  flex-direction:column;
1709  gap:8px;
1710  max-width:350px;
1711}
1712.site-footer.footer-compact .ft-payments .pay-row {
1713  display:flex;
1714  gap:8px;
1715  flex-wrap:wrap;
1716}
1717.site-footer.footer-compact .ft-payments img {
1718  width:46px;
1719  height:32px;
1720  padding:3px 5px;
1721  background:#fff;
1722  border-radius:7px;
1723  object-fit:contain;
1724  box-shadow:0 2px 6px -3px rgba(0,0,0,.45);
1725  transition:.25s;
1726}
1727.site-footer.footer-compact .ft-payments img:hover {
1728  transform:translateY(-3px);
1729  box-shadow:0 5px 12px -6px rgba(0,0,0,.55);
1730}
1731
1732/* 10. Bottom bar */
1733.site-footer.footer-compact .ft-bottom {
1734  border-top:1px solid rgba(255,255,255,.08);
1735  background:#041f31;
1736  padding:12px 0 16px;
1737  margin-top:4px;
1738}
1739.site-footer.footer-compact .ft-copy {
1740  font-size:11.5px;
1741  color:#8fa7b5;
1742  display:flex;
1743  flex-wrap:wrap;
1744  gap:12px;
1745  justify-content:center;
1746  line-height:1.3;
1747  margin:0;
1748}
1749.site-footer.footer-compact .ft-copy span {
1750  position:relative;
1751  padding:0 10px;
1752}
1753.site-footer.footer-compact .ft-copy span+span:before {
1754  content:"";
1755  position:absolute;
1756  left:0;
1757  top:50%;
1758  width:1px;
1759  height:12px;
1760  background:rgba(255,255,255,.14);
1761  transform:translateY(-50%);
1762}
1763@media (max-width:580px){
1764  .site-footer.footer-compact .ft-top { padding:34px 0 18px; }
1765  .site-footer.footer-compact .ft-grid { gap:26px 20px; }
1766  .site-footer.footer-compact .ft-copy span { padding:0; }
1767  .site-footer.footer-compact .ft-copy span+span:before { display:none; }
1768}
1769.copy {
1770  margin-top:40px;
1771  border-top:1px solid #222;
1772  text-align:center;
1773  padding:18px 0;
1774  font-size:12px;
1775  color:#666;
1776}
1777
1778@media (max-width:760px) {
1779  .hero-text h1 { font-size:38px; }
1780  .hero { padding:0px 0 30px; }
1781  .topbar-inner { flex-direction:column; gap:4px; padding:6px 0; }
1782  .nav-inner { flex-wrap:wrap; }
1783  .mega-cats { gap:10px; }
1784  .product-grid { gap:16px; }
1785  .prod-card { padding:10px; }
1786}
1787
1788/* === PLAZA IT DARK ORANGE FOOTER THEME === */
1789.site-footer.footer-compact {
1790  --ft-bg-0:#051722;
1791  --ft-bg-1:#072534;
1792  --ft-bg-2:#0B2F40;
1793  --ft-bg-3:#103A4E;
1794  --ft-orange:#F28C1A;
1795  --ft-orange-dark:#D97809;
1796  --ft-text:#E9F2F6;
1797  --ft-text-sec:#B8CAD3;
1798  --ft-text-muted:#8FA4B1;
1799  --ft-divider:rgba(255,255,255,0.08);
1800  --ft-glow:rgba(242,140,26,0.18);
1801  --ft-glow-fade:rgba(242,140,26,0);
1802  color:var(--ft-text);
1803}
1804
1805/* BASE TEXT / LINKS (override sebelumnya jika perlu) */
1806.site-footer.footer-compact .ft-title { color:#9CC5D7; }
1807.site-footer.footer-compact .ft-links a { color:var(--ft-text-sec); }
1808.site-footer.footer-compact .ft-links a:hover { color:#fff; }
1809.site-footer.footer-compact .ft-address { color:var(--ft-text-muted); }
1810.site-footer.footer-compact .ft-copy { color:#9AB2BD; }
1811
1812/* ICON BACKGROUND (kubu gelap) */
1813.site-footer.footer-compact .ico-s {
1814  background:#0F3647;
1815  color:#9FC7DA;
1816}
1817.site-footer.footer-compact .ft-links a:hover .ico-s {
1818  background:var(--ft-orange);
1819  color:#fff;
1820}
1821
1822/* WhatsApp button biarkan putih agar kontras */
1823.site-footer.footer-compact .ft-wa-btn {
1824  box-shadow:0 4px 12px -5px rgba(0,0,0,.6), 0 0 0 3px rgba(255,255,255,0.05);
1825}
1826.site-footer.footer-compact .ft-wa-btn:hover {
1827  box-shadow:0 8px 20px -8px rgba(0,0,0,.65), 0 0 0 3px rgba(242,140,26,0.25);
1828}
1829
1830/* Payment cards border halus */
1831.site-footer.footer-compact .ft-payments img {
1832  box-shadow:0 2px 6px -3px rgba(0,0,0,.55), 0 0 0 1px rgba(255,255,255,0.05);
1833}
1834.site-footer.footer-compact .ft-payments img:hover {
1835  box-shadow:0 6px 14px -6px rgba(0,0,0,.65), 0 0 0 1px rgba(242,140,26,0.4);
1836}
1837
1838/* ========== VARIANT A: DIAGONAL EMBER (AKTIF) ========== */
1839.site-footer.footer-compact.variant-ember {
1840  background:
1841    radial-gradient(circle at 78% 82%, var(--ft-glow) 0%, var(--ft-glow-fade) 55%),
1842    linear-gradient(140deg,var(--ft-bg-1) 0%, var(--ft-bg-2) 55%, var(--ft-bg-0) 100%);
1843}
1844.site-footer.footer-compact.variant-ember .ft-bottom {
1845  background:rgba(0,0,0,0.25);
1846  backdrop-filter:blur(2px);
1847}
1848
1849/* ========== VARIANT B: TOP BAR ORANGE ========== */
1850.site-footer.footer-compact.variant-bar {
1851  background:var(--ft-bg-1);
1852  position:relative;
1853}
1854.site-footer.footer-compact.variant-bar::before {
1855  content:"";
1856  position:absolute;
1857  left:0;top:0;
1858  width:100%;height:3px;
1859  background:linear-gradient(90deg,var(--ft-orange-dark),var(--ft-orange));
1860  box-shadow:0 0 12px -2px var(--ft-orange);
1861}
1862.site-footer.footer-compact.variant-bar .ft-bottom {
1863  background:#041c28;
1864}
1865
1866/* ========== VARIANT C: RADIAL GLOW BAWAH ========== */
1867.site-footer.footer-compact.variant-radial {
1868  background:
1869    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%),
1870    linear-gradient(150deg,#052131 0%, #062d3f 50%, #041924 100%);
1871}
1872.site-footer.footer-compact.variant-radial .ft-bottom {
1873  background:#041c29;
1874}
1875
1876/* ========== VARIANT D: LAYERED STRIPES ========== */
1877.site-footer.footer-compact.variant-stripes {
1878  background:
1879    repeating-linear-gradient(180deg, rgba(255,255,255,0.015) 0 38px, rgba(255,255,255,0) 38px 76px),
1880    linear-gradient(130deg,#052636 0%, #062a3c 60%, #041d28 100%);
1881  position:relative;
1882}
1883.site-footer.footer-compact.variant-stripes::after {
1884  content:"";
1885  position:absolute;
1886  left:0;bottom:0;
1887  width:100%;height:110px;
1888  background:linear-gradient(0deg,rgba(0,0,0,.35),rgba(0,0,0,0));
1889  pointer-events:none;
1890}
1891.site-footer.footer-compact.variant-stripes .ft-bottom {
1892  background:#031821;
1893}
1894
1895/* Garis aksen oranye tipis di atas bottom */
1896.site-footer.footer-compact.variant-stripes .ft-bottom {
1897  position:relative;
1898}
1899.site-footer.footer-compact.variant-stripes .ft-bottom::before {
1900  content:"";
1901  position:absolute;
1902  left:0;top:0;
1903  width:100%;height:2px;
1904  background:linear-gradient(90deg,rgba(242,140,26,0),rgba(242,140,26,.65),rgba(242,140,26,0));
1905}
1906
1907/* ========== VARIANT E: GLASS PANEL ========== */
1908.site-footer.footer-compact.variant-glass {
1909  background:linear-gradient(145deg,#051c29 0%, #041620 65%, #03121b 100%);
1910}
1911.site-footer.footer-compact.variant-glass .ft-grid {
1912  position:relative;
1913  padding:32px clamp(10px,2vw,24px);
1914  border-radius:22px;
1915  background:linear-gradient(140deg,rgba(255,255,255,0.06),rgba(255,255,255,0.02));
1916  border:1px solid rgba(255,255,255,0.08);
1917  box-shadow:0 12px 28px -12px rgba(0,0,0,0.45), 0 0 0 1px rgba(255,255,255,0.04) inset;
1918  backdrop-filter:blur(8px);
1919  -webkit-backdrop-filter:blur(8px);
1920}
1921.site-footer.footer-compact.variant-glass .ft-bottom {
1922  background:#02141d;
1923}
1924
1925/* HOVER ACCENTS */
1926.site-footer.footer-compact a {
1927  transition:color .25s, background .25s, box-shadow .25s;
1928}
1929.site-footer.footer-compact .ft-links a:hover {
1930  box-shadow:0 2px 6px -3px rgba(0,0,0,.5),
1931             0 0 0 1px rgba(242,140,26,0.55);
1932}
1933.site-footer.footer-compact .ft-social a {
1934  background:#0E3445;
1935  color:#bcd4dd;
1936}
1937.site-footer.footer-compact .ft-social a:hover {
1938  background:var(--ft-orange);
1939  color:#fff;
1940  box-shadow:0 0 0 3px rgba(242,140,26,0.28);
1941}
1942
1943/* Divider di atas footer (opsional) */
1944.footer-divider-top {
1945  height:1px;
1946  background:linear-gradient(90deg,transparent,rgba(255,255,255,0.15),transparent);
1947  width:100%;
1948  margin:0 0 4px;
1949}
1950
1951/* ==== MOBILE FOOTER COLLAPSE (Non-Homepage Only) ==== */
1952
1953/* Desktop: wrapper tidak mengganggu grid layout */
1954.ft-collapsible-content {
1955  display: contents;
1956}
1957
1958@media (max-width: 768px) {
1959  /* Wrapper untuk konten yang bisa collapse */
1960  .ft-top.ft-collapsible .ft-collapsible-content {
1961    display: block;
1962    max-height: 80px;
1963    overflow: hidden;
1964    position: relative;
1965    transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1);
1966  }
1967
1968  /* Fade gradient overlay */
1969  .ft-top.ft-collapsible .ft-collapsible-content::after {
1970    content: '';
1971    position: absolute;
1972    bottom: 0;
1973    left: 0;
1974    right: 0;
1975    height: 70px;
1976    background: linear-gradient(to bottom, transparent 0%, rgba(6, 43, 69, 0.6) 40%, #062b45 100%);
1977    pointer-events: none;
1978    opacity: 1;
1979    transition: opacity 0.3s ease;
1980    z-index: 1;
1981  }
1982
1983  /* State: Expanded */
1984  .ft-top.ft-collapsible.expanded .ft-collapsible-content {
1985    max-height: 1500px;
1986  }
1987
1988  .ft-top.ft-collapsible.expanded .ft-collapsible-content::after {
1989    opacity: 0;
1990  }
1991
1992  /* Expand Button - Di luar collapsible, selalu visible */
1993  .ft-expand-btn {
1994    display: flex;
1995    align-items: center;
1996    justify-content: center;
1997    gap: 8px;
1998    width: 100%;
1999    margin: -8px 0 0;
2000    padding: 12px 20px;
2001    background: rgba(255,255,255,0.12);
2002    border: 1px solid rgba(255,255,255,0.25);
2003    border-radius: 10px;
2004    color: #e5f2f7;
2005    font-size: 13.5px;
2006    font-weight: 500;
2007    cursor: pointer;
2008    transition: all 0.3s ease;
2009    outline: none;
2010    position: relative;
2011    z-index: 10;
2012  }
2013
2014  .ft-expand-btn:hover {
2015    background: rgba(255,255,255,0.18);
2016    border-color: rgba(255,255,255,0.35);
2017    color: #fff;
2018    transform: translateY(-2px);
2019  }
2020
2021  .ft-expand-btn:active {
2022    transform: scale(0.98);
2023  }
2024
2025  .ft-expand-btn svg {
2026    width: 18px;
2027    height: 18px;
2028    transition: transform 0.3s ease;
2029    fill: currentColor;
2030  }
2031
2032  .ft-top.ft-collapsible.expanded .ft-expand-btn {
2033    margin-top: 15px;
2034  }
2035
2036  .ft-top.ft-collapsible.expanded .ft-expand-btn svg {
2037    transform: rotate(180deg);
2038  }
2039}
2040
2041/* Desktop: hide expand button */
2042@media (min-width: 769px) {
2043  .ft-expand-btn {
2044    display: none !important;
2045  }
2046}
2047
2048/* RESPONSIVE (jika background panel butuh penyesuaian) */
2049@media (max-width:640px){
2050  .site-footer.footer-compact.variant-glass .ft-grid {
2051    padding:26px 18px;
2052  }
2053}
2054
2055</style>
2056  <style data-inlined="bottom-nav.css">
2057/* Mobile Bottom Navigation */
2058.bottom-nav {
2059  position: fixed;
2060  bottom: 0;
2061  left: 0;
2062  right: 0;
2063  background: #ffffff;
2064  border-top: 1px solid #e5e7eb;
2065  z-index: 9998; /* Below chat widget */
2066  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
2067  display: none; /* Hidden on desktop */
2068}
2069
2070.bottom-nav-inner {
2071  display: flex;
2072  justify-content: space-around;
2073  align-items: center;
2074  padding: 2px 0;
2075  max-width: 640px;
2076  margin: 0 auto;
2077}
2078
2079.bottom-nav-item {
2080  flex: 1;
2081  display: flex;
2082  flex-direction: column;
2083  align-items: center;
2084  justify-content: center;
2085  gap: 4px;
2086  padding: 8px 8px;
2087  background: none;
2088  border: none;
2089  cursor: pointer;
2090  text-decoration: none;
2091  color: #6b7280;
2092  transition: all 0.3s ease;
2093  position: relative;
2094}
2095
2096.bottom-nav-item:hover {
2097  color: #F28C1A;
2098}
2099
2100.bottom-nav-item.active {
2101  flex-direction: row;
2102  gap: 8px;
2103  background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
2104  color: white;
2105  border-radius: 20px;
2106  padding: 8px 16px;
2107  box-shadow: 0 2px 8px rgba(242, 140, 26, 0.3);
2108}
2109
2110.bottom-nav-item-icon {
2111  width: 24px;
2112  height: 24px;
2113  position: relative;
2114}
2115
2116.bottom-nav-item-icon svg {
2117  width: 100%;
2118  height: 100%;
2119}
2120
2121/* Icon variant switching */
2122.bottom-nav-item .icon-outlined {
2123  display: block;
2124}
2125
2126.bottom-nav-item .icon-filled {
2127  display: none;
2128}
2129
2130.bottom-nav-item.active .icon-outlined {
2131  display: none;
2132}
2133
2134.bottom-nav-item.active .icon-filled {
2135  display: block;
2136}
2137
2138.bottom-nav-item-label {
2139  font-size: 12px;
2140  font-weight: 600;
2141  line-height: 1;
2142  display: none;
2143  white-space: nowrap;
2144}
2145
2146.bottom-nav-item.active .bottom-nav-item-label {
2147  display: block;
2148}
2149
2150/* Avatar styles for bottom-nav */
2151.bottom-nav-avatar-img,
2152.bottom-nav-avatar-initials {
2153  width: 24px;
2154  height: 24px;
2155  border-radius: 50%;
2156  display: block;
2157}
2158
2159.bottom-nav-avatar-img {
2160  object-fit: cover;
2161}
2162
2163.bottom-nav-avatar-initials {
2164  background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
2165  color: white;
2166  font-size: 10px;
2167  font-weight: 700;
2168  display: flex;
2169  align-items: center;
2170  justify-content: center;
2171  line-height: 1;
2172  letter-spacing: 0.5px;
2173}
2174
2175/* Hide SVG when avatar is shown */
2176.bottom-nav-item-icon:has(.bottom-nav-avatar-img) .bottom-nav-svg-icon,
2177.bottom-nav-item-icon:has(.bottom-nav-avatar-initials) .bottom-nav-svg-icon {
2178  display: none;
2179}
2180
2181/* Active state adjustments for avatar */
2182.bottom-nav-item.active .bottom-nav-avatar-initials {
2183  background: white;
2184  color: #F28C1A;
2185  border: 2px solid rgba(255, 255, 255, 0.8);
2186  box-sizing: border-box;
2187}
2188
2189.bottom-nav-item.active .bottom-nav-avatar-img {
2190  border: 2px solid rgba(255, 255, 255, 0.9);
2191  box-sizing: border-box;
2192}
2193
2194.bottom-nav-item-badge {
2195  position: absolute;
2196  top: -4px;
2197  right: -4px;
2198  background: #ef4444;
2199  color: white;
2200  border-radius: 10px;
2201  padding: 2px 5px;
2202  font-size: 10px;
2203  font-weight: 600;
2204  min-width: 18px;
2205  height: 18px;
2206  display: flex;
2207  align-items: center;
2208  justify-content: center;
2209  line-height: 1;
2210}
2211
2212/* Show on mobile only */
2213@media (max-width: 640px) {
2214  .bottom-nav {
2215    display: block;
2216  }
2217
2218  /* Add bottom padding to body to prevent content being hidden */
2219  body {
2220    padding-bottom: 60px;
2221  }
2222}
2223
2224/* Mobile User Sheet - Bottom Sheet for Profile Menu */
2225.mobile-user-sheet {
2226  position: fixed;
2227  bottom: 0;
2228  left: 0;
2229  right: 0;
2230  z-index: 10000;
2231  pointer-events: none;
2232}
2233
2234.mobile-user-sheet:not([hidden]) {
2235  pointer-events: auto;
2236}
2237
2238.mobile-user-sheet-backdrop {
2239  position: fixed;
2240  inset: 0;
2241  background: rgba(0, 0, 0, 0.5);
2242  opacity: 0;
2243  transition: opacity 0.2s ease-out;
2244}
2245
2246.mobile-user-sheet.active .mobile-user-sheet-backdrop {
2247  opacity: 1;
2248}
2249
2250.mobile-user-sheet-content {
2251  position: absolute;
2252  bottom: 0;
2253  left: 0;
2254  right: 0;
2255  background: white;
2256  border-radius: 16px 16px 0 0;
2257  max-height: 80vh;
2258  overflow-y: auto;
2259  transform: translateY(100%);
2260  transition: transform 0.3s ease-out;
2261}
2262
2263.mobile-user-sheet.active .mobile-user-sheet-content {
2264  transform: translateY(0);
2265}
2266
2267.mobile-user-sheet-handle {
2268  width: 40px;
2269  height: 4px;
2270  background: #d1d5db;
2271  border-radius: 2px;
2272  margin: 12px auto 16px;
2273}
2274
2275.mobile-user-sheet-header {
2276  padding: 0 20px 16px;
2277  border-bottom: 1px solid #e5e7eb;
2278}
2279
2280.mobile-user-info {
2281  display: flex;
2282  align-items: center;
2283  gap: 12px;
2284}
2285
2286.mobile-user-avatar {
2287  width: 48px;
2288  height: 48px;
2289  background: linear-gradient(135deg, #FFE7CC 0%, #FFD9A8 50%, #FFCC80 100%);
2290  border-radius: 50%;
2291  display: flex;
2292  align-items: center;
2293  justify-content: center;
2294  font-size: 24px;
2295  color: #D97809;
2296  font-weight: 600;
2297}
2298
2299.mobile-user-details {
2300  flex: 1;
2301}
2302
2303.mobile-user-name {
2304  font-weight: 600;
2305  font-size: 16px;
2306  color: #1f2937;
2307  margin-bottom: 4px;
2308}
2309
2310.mobile-user-email {
2311  font-size: 13px;
2312  color: #6b7280;
2313}
2314
2315.mobile-user-sheet-body {
2316  padding: 8px 0;
2317}
2318
2319.mobile-user-sheet-item {
2320  display: flex;
2321  align-items: center;
2322  gap: 12px;
2323  padding: 14px 20px;
2324  background: none;
2325  border: none;
2326  width: 100%;
2327  text-align: left;
2328  cursor: pointer;
2329  text-decoration: none;
2330  color: #374151;
2331  transition: background 0.2s;
2332}
2333
2334.mobile-user-sheet-item:hover {
2335  background: #f9fafb;
2336}
2337
2338.mobile-user-sheet-icon {
2339  font-size: 20px;
2340  width: 24px;
2341  text-align: center;
2342}
2343
2344.mobile-user-sheet-label {
2345  font-size: 15px;
2346  font-weight: 500;
2347}
2348
2349.mobile-user-sheet-divider {
2350  height: 1px;
2351  background: #e5e7eb;
2352  margin: 8px 0;
2353}
2354
2355@keyframes fadeIn {
2356  from {
2357    opacity: 0;
2358  }
2359  to {
2360    opacity: 1;
2361  }
2362}
2363
2364@keyframes slideUp {
2365  from {
2366    transform: translateY(100%);
2367  }
2368  to {
2369    transform: translateY(0);
2370  }
2371}
2372
2373/* Ensure chat widget works well with bottom nav */
2374@media (max-width: 640px) {
2375  .chat-widget {
2376    bottom: 0 !important; /* Chat opens from bottom */
2377  }
2378
2379  .chat-popup {
2380    /* Chat popup should be above bottom nav */
2381    z-index: 9999;
2382  }
2383}
2384
2385</style>
2386  <style data-inlined="search-suggest.css">
2387.search {
2388  position: relative;
2389}
2390
2391/* AI Bot Toggle Button */
2392.ai-bot-toggle {
2393  display: flex;
2394  align-items: center;
2395  justify-content: center;
2396  background: transparent;
2397  border: none;
2398  padding: 8px;
2399  cursor: pointer;
2400  border-radius: 8px;
2401  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
2402  margin-left: 4px;
2403  flex-shrink: 0;
2404  display: none;
2405}
2406
2407.ai-bot-toggle .bot-icon {
2408  color: #9ca3af;
2409  transition: all 0.3s ease;
2410}
2411
2412.ai-bot-toggle:hover .bot-icon {
2413  color: #6b7280;
2414  transform: scale(1.1);
2415}
2416
2417.ai-bot-toggle.active .bot-icon {
2418  color: #fb923c;
2419  animation: botPulse 2s ease-in-out infinite;
2420}
2421
2422.ai-bot-toggle.active {
2423  background: rgba(251, 146, 60, 0.1);
2424}
2425
2426.ai-bot-toggle.active:hover .bot-icon {
2427  color: #f97316;
2428}
2429
2430@keyframes botPulse {
2431  0%, 100% { transform: scale(1); }
2432  50% { transform: scale(1.12); }
2433}
2434
2435/* Search Suggest Box */
2436.search-suggest {
2437  position: absolute;
2438  top: 100%; left: 0; right: 0;
2439  background: #fff;
2440  border: 1px solid #e1e4e8;
2441  border-top: none;
2442  box-shadow: 0 4px 12px rgba(0,0,0,0.08);
2443  max-height: 400px;
2444  overflow-y: auto;
2445  z-index: 100;
2446  padding: 0;
2447  font-size: 14px;
2448  border-radius: 0 0 8px 8px;
2449}
2450
2451.suggest-item {
2452  display: flex;
2453  align-items: center;
2454  gap: 8px;
2455  padding: 10px 16px;
2456  cursor: pointer;
2457  transition: all 0.2s ease;
2458  border-bottom: 1px solid #f1f5f9;
2459}
2460
2461.suggest-item:last-child {
2462  border-bottom: none;
2463}
2464
2465.suggest-item:hover,
2466.suggest-item.active {
2467  background: linear-gradient(90deg, #f8fafc 0%, #f1f5f9 100%);
2468  padding-left: 20px;
2469}
2470
2471.suggest-thumb {
2472  width: 42px;
2473  height: 42px;
2474  object-fit: cover;
2475  border: 1px solid #ddd;
2476  border-radius: 4px;
2477  background: #fafafa;
2478}
2479
2480.suggest-meta { flex: 1; min-width: 0; }
2481
2482.suggest-name {
2483  font-weight: 500;
2484  color: #222;
2485  line-height: 1.2;
2486  white-space: nowrap;
2487  overflow: hidden;
2488  text-overflow: ellipsis;
2489}
2490
2491.suggest-price {
2492  color: #d32f2f;
2493  font-size: 12px;
2494  margin-top: 2px;
2495}
2496
2497/* AI Insights */
2498.suggest-ai-insight {
2499  display: flex;
2500  align-items: center;
2501  gap: 8px;
2502  padding: 8px 12px;
2503  background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
2504  border-bottom: 1px solid #bae6fd;
2505  color: #0369a1;
2506  font-size: 12px;
2507  font-weight: 500;
2508  line-height: 1.4;
2509}
2510
2511.suggest-ai-insight svg {
2512  flex-shrink: 0;
2513  opacity: 0.8;
2514}
2515
2516.suggest-ai-insight span {
2517  flex: 1;
2518}
2519
2520/* AI Progress States */
2521.ai-progress-container {
2522  padding: 20px 16px;
2523  background: linear-gradient(135deg, #fafbff 0%, #f5f7ff 100%);
2524}
2525
2526.ai-progress-header {
2527  display: flex;
2528  align-items: center;
2529  gap: 10px;
2530  margin-bottom: 16px;
2531}
2532
2533.ai-progress-icon {
2534  width: 36px;
2535  height: 36px;
2536  display: flex;
2537  align-items: center;
2538  justify-content: center;
2539  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
2540  border-radius: 50%;
2541  color: #fff;
2542  font-size: 18px;
2543  animation: aiIconSpin 3s linear infinite;
2544}
2545
2546@keyframes aiIconSpin {
2547  0% { transform: rotate(0deg); }
2548  100% { transform: rotate(360deg); }
2549}
2550
2551.ai-progress-text {
2552  flex: 1;
2553}
2554
2555.ai-progress-title {
2556  font-weight: 600;
2557  color: #1e293b;
2558  font-size: 14px;
2559  margin-bottom: 2px;
2560}
2561
2562.ai-progress-subtitle {
2563  font-size: 12px;
2564  color: #64748b;
2565}
2566
2567/* Progress Steps */
2568.ai-progress-steps {
2569  display: flex;
2570  flex-direction: column;
2571  gap: 10px;
2572  margin-top: 16px;
2573}
2574
2575.ai-progress-step {
2576  display: flex;
2577  align-items: center;
2578  gap: 10px;
2579  padding: 8px 12px;
2580  background: #fff;
2581  border-radius: 8px;
2582  border: 1px solid #e2e8f0;
2583  transition: all 0.3s ease;
2584}
2585
2586.ai-progress-step.active {
2587  border-color: #667eea;
2588  background: #f8f9ff;
2589  box-shadow: 0 2px 8px rgba(102, 126, 234, 0.1);
2590}
2591
2592.ai-progress-step.completed {
2593  border-color: #10b981;
2594  background: #f0fdf4;
2595}
2596
2597.ai-step-icon {
2598  width: 20px;
2599  height: 20px;
2600  display: flex;
2601  align-items: center;
2602  justify-content: center;
2603  border-radius: 50%;
2604  font-size: 11px;
2605  flex-shrink: 0;
2606}
2607
2608.ai-progress-step.active .ai-step-icon {
2609  background: #667eea;
2610  color: #fff;
2611  animation: pulse 1.5s ease-in-out infinite;
2612}
2613
2614.ai-progress-step.completed .ai-step-icon {
2615  background: #10b981;
2616  color: #fff;
2617}
2618
2619.ai-progress-step.pending .ai-step-icon {
2620  background: #e2e8f0;
2621  color: #94a3b8;
2622}
2623
2624.ai-step-text {
2625  flex: 1;
2626  font-size: 13px;
2627  color: #475569;
2628  font-weight: 500;
2629}
2630
2631.ai-progress-step.completed .ai-step-text {
2632  color: #059669;
2633}
2634
2635/* AI Analysis Result Banner */
2636.ai-result-banner {
2637  padding: 12px 16px;
2638  background: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%);
2639  border-bottom: 1px solid #86efac;
2640  display: flex;
2641  align-items: center;
2642  gap: 10px;
2643}
2644
2645.ai-result-icon {
2646  font-size: 20px;
2647  animation: bounceIn 0.5s ease;
2648}
2649
2650@keyframes bounceIn {
2651  0% { transform: scale(0); opacity: 0; }
2652  50% { transform: scale(1.1); }
2653  100% { transform: scale(1); opacity: 1; }
2654}
2655
2656.ai-result-text {
2657  flex: 1;
2658}
2659
2660.ai-result-title {
2661  font-weight: 600;
2662  color: #065f46;
2663  font-size: 13px;
2664  margin-bottom: 2px;
2665}
2666
2667.ai-result-description {
2668  font-size: 12px;
2669  color: #047857;
2670  opacity: 0.9;
2671}
2672
2673/* Product Items Section Header */
2674.suggest-section-header {
2675  padding: 10px 16px;
2676  background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
2677  border-bottom: 1px solid #e2e8f0;
2678}
2679
2680.suggest-header-main {
2681  display: flex;
2682  align-items: center;
2683  gap: 6px;
2684  margin-bottom: 4px;
2685}
2686
2687.suggest-header-icon {
2688  font-size: 14px;
2689}
2690
2691.suggest-header-text {
2692  font-size: 12px;
2693  font-weight: 600;
2694  color: #64748b;
2695  text-transform: uppercase;
2696  letter-spacing: 0.5px;
2697}
2698
2699.suggest-header-hint {
2700  font-size: 11px;
2701  color: #94a3b8;
2702  font-weight: 400;
2703  text-transform: none;
2704  letter-spacing: 0;
2705  display: flex;
2706  align-items: center;
2707  gap: 4px;
2708}
2709
2710.suggest-header-hint kbd {
2711  display: inline-block;
2712  padding: 2px 6px;
2713  font-size: 10px;
2714  font-weight: 600;
2715  line-height: 1;
2716  color: #475569;
2717  background: #fff;
2718  border: 1px solid #cbd5e1;
2719  border-radius: 4px;
2720  box-shadow: 0 1px 2px rgba(0,0,0,0.05);
2721  font-family: 'Segoe UI', monospace;
2722}
2723
2724/* Footer Hint */
2725.suggest-footer-hint {
2726  display: flex;
2727  align-items: center;
2728  justify-content: center;
2729  padding: 12px 16px;
2730  background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
2731  border-top: 1px solid #fbbf24;
2732  color: #92400e;
2733  font-size: 12px;
2734  font-weight: 500;
2735  text-align: center;
2736  cursor: default;
2737}
2738
2739.suggest-footer-hint svg {
2740  color: #f59e0b;
2741  flex-shrink: 0;
2742}
2743
2744.suggest-footer-hint kbd {
2745  display: inline-block;
2746  padding: 3px 7px;
2747  margin: 0 3px;
2748  font-size: 11px;
2749  font-weight: 600;
2750  line-height: 1;
2751  color: #78350f;
2752  background: #fff;
2753  border: 1px solid #f59e0b;
2754  border-radius: 4px;
2755  box-shadow: 0 1px 3px rgba(245, 158, 11, 0.2);
2756  font-family: 'Segoe UI', monospace;
2757}
2758
2759.suggest-footer-link {
2760  color: #ea580c;
2761  font-weight: 600;
2762  text-decoration: underline;
2763  text-underline-offset: 2px;
2764  text-decoration-thickness: 1px;
2765  cursor: pointer;
2766  transition: all 0.2s ease;
2767}
2768
2769.suggest-footer-link:hover {
2770  color: #c2410c;
2771  text-decoration-thickness: 2px;
2772  text-shadow: 0 0 8px rgba(234, 88, 12, 0.3);
2773}
2774
2775/* Loading state */
2776.suggest-loading {
2777  display: flex;
2778  align-items: center;
2779  justify-content: center;
2780  gap: 8px;
2781  padding: 16px 12px;
2782  color: #64748b;
2783  font-size: 13px;
2784}
2785
2786@keyframes pulse {
2787  0%, 100% { opacity: 1; }
2788  50% { opacity: 0.5; }
2789}
2790
2791.suggest-loading {
2792  animation: pulse 1.5s ease-in-out infinite;
2793}
2794
2795/* Responsive Mobile Styles */
2796@media (max-width: 768px) {
2797  .ai-bot-toggle {
2798    padding: 6px;
2799    margin-left: 0;
2800  }
2801
2802  .ai-bot-toggle .bot-icon {
2803    width: 20px;
2804    height: 20px;
2805  }
2806
2807  .search-icon-btn {
2808    margin-right: 4px;
2809  }
2810
2811  .search-icon-btn svg {
2812    width: 18px;
2813    height: 18px;
2814  }
2815
2816  .search-suggest {
2817    max-height: 350px;
2818  }
2819
2820  /* Mobile hint adjustments */
2821  .suggest-header-hint {
2822    font-size: 10px;
2823  }
2824
2825  .suggest-header-hint kbd {
2826    padding: 1px 4px;
2827    font-size: 9px;
2828  }
2829
2830  .suggest-footer-hint {
2831    padding: 10px 12px;
2832    font-size: 11px;
2833  }
2834
2835  .suggest-footer-hint svg {
2836    width: 14px;
2837    height: 14px;
2838  }
2839
2840  .suggest-footer-hint kbd {
2841    padding: 2px 5px;
2842    font-size: 10px;
2843  }
2844
2845  .ai-progress-container {
2846    padding: 16px 12px;
2847  }
2848
2849  .ai-progress-icon {
2850    width: 32px;
2851    height: 32px;
2852    font-size: 16px;
2853  }
2854
2855  .ai-progress-title {
2856    font-size: 13px;
2857  }
2858
2859  .ai-progress-subtitle {
2860    font-size: 11px;
2861  }
2862
2863  .ai-progress-step {
2864    padding: 6px 10px;
2865  }
2866
2867  .ai-step-text {
2868    font-size: 12px;
2869  }
2870
2871  .suggest-item {
2872    padding: 8px 12px;
2873  }
2874
2875  .suggest-thumb {
2876    width: 36px;
2877    height: 36px;
2878  }
2879
2880  .suggest-name {
2881    font-size: 13px;
2882  }
2883
2884  .suggest-price {
2885    font-size: 11px;
2886  }
2887}
2888
2889@media (max-width: 480px) {
2890  .ai-bot-toggle {
2891    padding: 4px;
2892  }
2893
2894  .ai-bot-toggle .bot-icon {
2895    width: 18px;
2896    height: 18px;
2897  }
2898
2899  .search-icon-btn svg {
2900    width: 16px;
2901    height: 16px;
2902  }
2903
2904  /* Ultra-compact hints for small screens */
2905  .suggest-header-hint {
2906    display: none; /* Hide header hint on very small screens */
2907  }
2908
2909  .suggest-footer-hint {
2910    padding: 8px 10px;
2911    font-size: 10px;
2912    flex-direction: column;
2913    gap: 4px;
2914  }
2915
2916  .suggest-footer-hint svg {
2917    display: none; /* Hide icon to save space */
2918  }
2919
2920  .suggest-footer-hint kbd {
2921    padding: 2px 4px;
2922    font-size: 9px;
2923  }
2924
2925  .ai-progress-steps {
2926    gap: 8px;
2927  }
2928
2929  .ai-step-text {
2930    font-size: 11px;
2931  }
2932}
2933
2934/* AI Search Prompt Box */
2935.ai-search-prompt {
2936  padding: 24px;
2937  background: linear-gradient(135deg, #fff5f0 0%, #ffffff 100%);
2938}
2939
2940.ai-prompt-header {
2941  display: flex;
2942  align-items: center;
2943  gap: 12px;
2944  margin-bottom: 16px;
2945}
2946
2947.ai-prompt-icon {
2948  width: 48px;
2949  height: 48px;
2950  background: linear-gradient(135deg, #fb923c 0%, #f97316 100%);
2951  border-radius: 12px;
2952  display: flex;
2953  align-items: center;
2954  justify-content: center;
2955  font-size: 24px;
2956  box-shadow: 0 4px 12px rgba(251, 146, 60, 0.3);
2957}
2958
2959.ai-prompt-text {
2960  flex: 1;
2961}
2962
2963.ai-prompt-title {
2964  font-size: 16px;
2965  font-weight: 700;
2966  color: #1f2937;
2967  margin-bottom: 4px;
2968}
2969
2970.ai-prompt-subtitle {
2971  font-size: 13px;
2972  color: #6b7280;
2973}
2974
2975.ai-prompt-query {
2976  background: white;
2977  border: 2px solid #fb923c;
2978  border-radius: 8px;
2979  padding: 12px 16px;
2980  margin-bottom: 16px;
2981}
2982
2983.ai-query-label {
2984  font-size: 11px;
2985  font-weight: 600;
2986  color: #fb923c;
2987  text-transform: uppercase;
2988  letter-spacing: 0.5px;
2989  margin-bottom: 4px;
2990}
2991
2992.ai-query-text {
2993  font-size: 15px;
2994  font-weight: 600;
2995  color: #1f2937;
2996}
2997
2998.ai-search-trigger-btn {
2999  width: 100%;
3000  padding: 14px 20px;
3001  background: linear-gradient(135deg, #fb923c 0%, #f97316 100%);
3002  color: white;
3003  border: none;
3004  border-radius: 10px;
3005  font-size: 15px;
3006  font-weight: 700;
3007  cursor: pointer;
3008  display: flex;
3009  align-items: center;
3010  justify-content: center;
3011  gap: 8px;
3012  transition: all 0.3s ease;
3013  box-shadow: 0 4px 12px rgba(251, 146, 60, 0.4);
3014}
3015
3016.ai-search-trigger-btn:hover {
3017  transform: translateY(-2px);
3018  box-shadow: 0 6px 20px rgba(251, 146, 60, 0.5);
3019}
3020
3021.ai-search-trigger-btn:active {
3022  transform: translateY(0);
3023}
3024
3025.ai-btn-icon {
3026  font-size: 18px;
3027  animation: sparkle 2s ease-in-out infinite;
3028}
3029
3030@keyframes sparkle {
3031  0%, 100% { opacity: 1; transform: scale(1); }
3032  50% { opacity: 0.7; transform: scale(1.2); }
3033}
3034
3035.ai-btn-text {
3036  font-size: 15px;
3037}
3038
3039.ai-prompt-footer {
3040  margin-top: 12px;
3041  text-align: center;
3042  font-size: 12px;
3043  color: #9ca3af;
3044  font-style: italic;
3045}
3046
3047/* ==================== TRENDING SEARCH STYLES ==================== */
3048
3049.search-suggest.trending-mode {
3050  border-top: 3px solid #fb923c;
3051  max-width: 900px;
3052  min-width: 600px;
3053}
3054
3055/* Two-column container */
3056.trending-container {
3057  display: flex;
3058  gap: 0;
3059  padding: 0 !important;
3060}
3061
3062.trending-column {
3063  flex: 1;
3064  min-width: 0;
3065  padding: 0 !important;
3066}
3067
3068.trending-column.trending-searches {
3069  border-right: 2px solid #e2e8f0; padding: 0 !important;
3070}
3071
3072/* Mobile: stack columns vertically */
3073@media (max-width: 768px) {
3074  .search-suggest.trending-mode {
3075    min-width: auto;
3076    max-width: 100%;
3077  }
3078
3079  .trending-container {
3080    flex-direction: column;
3081  }
3082
3083  .trending-column.trending-searches {
3084    border-right: none;
3085    border-bottom: 2px solid #e2e8f0;
3086  }
3087}
3088
3089.trending-header {
3090  display: flex;
3091  align-items: center;
3092  gap: 8px;
3093  padding: 14px 16px;
3094  border-bottom: 1px solid #e2e8f0;
3095  background: linear-gradient(135deg, #fb923c 0%, #f97316 100%);
3096  color: white;
3097  position: sticky;
3098  top: 0;
3099  z-index: 10;
3100}
3101
3102.trending-icon {
3103  font-size: 20px;
3104  animation: trendingPulse 2s infinite;
3105}
3106
3107@keyframes trendingPulse {
3108  0%, 100% {
3109    transform: scale(1);
3110    filter: drop-shadow(0 0 0 rgba(251, 146, 60, 0));
3111  }
3112  50% {
3113    transform: scale(1.15);
3114    filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.8));
3115  }
3116}
3117
3118.trending-title {
3119  font-weight: 700;
3120  font-size: 14px;
3121  letter-spacing: 0.3px;
3122}
3123
3124.trending-subtitle {
3125  font-size: 11px;
3126  opacity: 0.9;
3127  margin-left: auto;
3128  font-style: italic;
3129}
3130
3131/* Trending searches item */
3132.trending-item {
3133  display: flex;
3134  align-items: center;
3135  gap: 12px;
3136  padding: 12px 16px;
3137  cursor: pointer;
3138  transition: all 0.2s ease;
3139  border-bottom: 1px solid #f1f5f9;
3140  position: relative;
3141}
3142
3143.trending-item:hover {
3144  background: linear-gradient(90deg, #fff7ed 0%, #ffedd5 100%);
3145  padding-left: 20px;
3146}
3147
3148.trending-item:hover::before {
3149  content: '';
3150  position: absolute;
3151  left: 0;
3152  top: 0;
3153  bottom: 0;
3154  width: 4px;
3155  background: linear-gradient(180deg, #fb923c 0%, #f97316 100%);
3156}
3157
3158.trending-item:last-child {
3159  border-bottom: none;
3160}
3161
3162.trending-rank {
3163  font-size: 20px;
3164  flex-shrink: 0;
3165  width: 32px;
3166  text-align: center;
3167}
3168
3169.trending-query {
3170  flex: 1;
3171  font-size: 14px;
3172  color: #1f2937;
3173  font-weight: 500;
3174  line-height: 1.4;
3175}
3176
3177.trending-category {
3178  font-size: 11px;
3179  color: #f97316;
3180  background: #fff7ed;
3181  padding: 3px 10px;
3182  border-radius: 12px;
3183  white-space: nowrap;
3184  font-weight: 500;
3185  border: 1px solid #fed7aa;
3186}
3187
3188/* Trending products item */
3189.trending-product-item {
3190  display: flex;
3191  align-items: center;
3192  gap: 12px;
3193  padding: 12px 16px;
3194  cursor: pointer;
3195  transition: all 0.2s ease;
3196  border-bottom: 1px solid #f1f5f9;
3197  position: relative;
3198}
3199
3200.trending-product-item:hover {
3201  background: linear-gradient(90deg, #fef3c7 0%, #fde68a 100%);
3202  padding-left: 20px;
3203}
3204
3205.trending-product-item:hover::before {
3206  content: '';
3207  position: absolute;
3208  left: 0;
3209  top: 0;
3210  bottom: 0;
3211  width: 4px;
3212  background: linear-gradient(180deg, #fbbf24 0%, #f59e0b 100%);
3213}
3214
3215.trending-product-item:last-child {
3216  border-bottom: none;
3217}
3218
3219.trending-product-thumb {
3220  width: 48px;
3221  height: 48px;
3222  object-fit: cover;
3223  border-radius: 8px;
3224  border: 1px solid #e5e7eb;
3225  flex-shrink: 0;
3226  background: white;
3227}
3228
3229.trending-product-info {
3230  flex: 1;
3231  min-width: 0;
3232}
3233
3234.trending-product-name {
3235  font-size: 13px;
3236  font-weight: 400;
3237  color: #1f2937;
3238  line-height: 1.4;
3239  margin-bottom: 0;
3240  padding: 0 !important;
3241  overflow: hidden;
3242  text-overflow: ellipsis;
3243  display: -webkit-box;
3244  -webkit-line-clamp: 2;
3245  -webkit-box-orient: vertical;
3246}
3247
3248.trending-product-price {
3249  font-size: 12px;
3250  font-weight: 700;
3251  color: #f59e0b;
3252  padding: 4px 0 !important;
3253}
3254.trending-product { padding: 0;}
3255/* ==================== END TRENDING STYLES ==================== */
3256
3257</style>
3258  <style data-inlined="product-detail.css">
3259/* === PRODUCT DETAIL v3 (Tokopedia-like) — Full stylesheet === */
3260
3261/* Root / Container */
3262.product-single {
3263  --pd-gap: 2rem;
3264  --pd-border: #e2e8f0;
3265  --pd-bg: #ffffff;
3266  --pd-muted: #64748b;
3267  --pd-dark: #0f172a;
3268  --pd-accent: #0d6efd; /* tema utama situs */
3269  --pd-radius: 12px;
3270  --pd-soft: #f8fafc;
3271  --pd-soft-border: #e5e7eb;
3272  --pd-danger: #dc2626;
3273  --pd-success-bg: #dcfce7;
3274  --pd-success-text: #166534;
3275  --pd-danger-bg: #fee2e2;
3276  --pd-danger-text: #b91c1c;
3277  --pd-shadow: 0 6px 20px -10px rgba(0,0,0,.12);
3278  --pd-transition: .25s cubic-bezier(.4,.0,.2,1);
3279  max-width:1280px;
3280  margin-inline:auto;
3281}
3282
3283/* === Sticky Product Header Bar (muncul saat scroll) === */
3284.pd-sticky-header {
3285  position: fixed;
3286  top: 0;
3287  left: 0;
3288  right: 0;
3289  z-index: 998;
3290  background: rgba(255, 255, 255, 0.98);
3291  backdrop-filter: blur(12px);
3292  -webkit-backdrop-filter: blur(12px);
3293  border-bottom: 1px solid rgba(226, 232, 240, 0.8);
3294  box-shadow: 0 4px 16px -4px rgba(0, 0, 0, 0.08);
3295  transform: translateY(-100%);
3296  opacity: 0;
3297  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
3298              opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);
3299  pointer-events: none;
3300}
3301
3302.pd-sticky-header.visible {
3303  transform: translateY(0);
3304  opacity: 1;
3305  pointer-events: auto;
3306}
3307
3308.pd-sticky-header-container {
3309  max-width: 1280px;
3310  margin: 0 auto;
3311  padding: 12px 20px;
3312  display: flex;
3313  align-items: center;
3314  gap: 16px;
3315}
3316
3317.pd-sticky-thumb {
3318  width: 48px;
3319  height: 48px;
3320  flex-shrink: 0;
3321  border-radius: 8px;
3322  border: 1px solid #e2e8f0;
3323  overflow: hidden;
3324  background: #f8fafc;
3325}
3326
3327.pd-sticky-thumb img {
3328  width: 100%;
3329  height: 100%;
3330  object-fit: cover;
3331  display: block;
3332}
3333
3334.pd-sticky-info {
3335  flex: 1;
3336  min-width: 0;
3337  display: flex;
3338  flex-direction: column;
3339  gap: 4px;
3340}
3341
3342.pd-sticky-title {
3343  font-size: 0.9rem;
3344  font-weight: 600;
3345  color: #0f172a;
3346  line-height: 1.3;
3347  white-space: nowrap;
3348  overflow: hidden;
3349  text-overflow: ellipsis;
3350  margin: 0;
3351}
3352
3353.pd-sticky-price-row {
3354  display: flex;
3355  align-items: center;
3356  gap: 8px;
3357  flex-wrap: wrap;
3358}
3359
3360.pd-sticky-price {
3361  font-size: 1.1rem;
3362  font-weight: 700;
3363  color: #0d6efd;
3364  line-height: 1;
3365}
3366
3367.pd-sticky-price-original {
3368  font-size: 0.75rem;
3369  color: #94a3b8;
3370  text-decoration: line-through;
3371  line-height: 1;
3372}
3373
3374.pd-sticky-discount {
3375  background: #dc2626;
3376  color: #fff;
3377  font-size: 0.65rem;
3378  font-weight: 600;
3379  padding: 2px 6px;
3380  border-radius: 4px;
3381  letter-spacing: 0.3px;
3382  line-height: 1;
3383}
3384
3385.pd-sticky-actions {
3386  display: flex;
3387  gap: 8px;
3388  flex-shrink: 0;
3389  align-items: center;
3390}
3391
3392.pd-sticky-share-btn {
3393  padding: 8px 16px;
3394  border-radius: 8px;
3395  border: 1px solid #e2e8f0;
3396  background: #fff;
3397  color: #64748b;
3398  cursor: pointer;
3399  transition: all 0.2s ease;
3400  display: inline-flex;
3401  align-items: center;
3402  justify-content: center;
3403  flex-shrink: 0;
3404}
3405
3406.pd-sticky-share-btn:hover {
3407  background: #f8fafc;
3408  border-color: #cbd5e1;
3409  color: #0f172a;
3410  transform: translateY(-1px);
3411}
3412
3413.pd-sticky-share-btn:active {
3414  transform: translateY(0);
3415}
3416
3417.pd-sticky-share-btn svg {
3418  display: block;
3419  width: 16px;
3420  height: 16px;
3421}
3422
3423.pd-sticky-btn-chat svg {
3424  width: 16px !important;
3425  height: 16px !important;
3426  max-width: none !important;
3427  margin-right: 4px;
3428  flex-shrink: 0;
3429}
3430
3431.pd-sticky-btn {
3432  padding: 8px 16px;
3433  border-radius: 8px;
3434  font-size: 0.85rem;
3435  font-weight: 600;
3436  cursor: pointer;
3437  transition: all 0.2s ease;
3438  border: 0;
3439  display: inline-flex;
3440  align-items: center;
3441  justify-content: center;
3442  white-space: nowrap;
3443}
3444
3445.pd-sticky-btn-cart {
3446  background: #fff;
3447  color: #0d6efd;
3448  border: 1px solid #0d6efd;
3449}
3450
3451.pd-sticky-btn-cart:hover {
3452  background: rgba(13, 110, 253, 0.06);
3453}
3454
3455.pd-sticky-btn-chat {
3456  background: #fff;
3457  color: #64748b;
3458  border: 1px solid #cbd5e1;
3459  flex: 0.8;
3460}
3461
3462.pd-sticky-btn-chat:hover {
3463  background: #f8fafc;
3464  border-color: #94a3b8;
3465  color: #475569;
3466}
3467
3468.pd-sticky-btn-buy {
3469  background: #0d6efd;
3470  color: #fff;
3471  border: 1px solid #0d6efd;
3472  box-shadow: 0 2px 8px -2px rgba(13, 110, 253, 0.4);
3473}
3474
3475.pd-sticky-btn-buy:hover {
3476  filter: brightness(0.95);
3477}
3478
3479.pd-sticky-btn:disabled {
3480  opacity: 0.5;
3481  cursor: not-allowed;
3482}
3483
3484/* Hide on mobile (use existing FAB instead) */
3485@media (max-width: 880px) {
3486  .pd-sticky-header {
3487    display: none;
3488  }
3489}
3490
3491/* === PRINT STYLES (for PDF Export) === */
3492@media print {
3493  /* Reset & Base */
3494  * {
3495    -webkit-print-color-adjust: exact !important;
3496    print-color-adjust: exact !important;
3497  }
3498
3499  body {
3500    background: white !important;
3501    margin: 0;
3502    padding: 0;
3503  }
3504
3505  /* Hide all navigation, buttons, and interactive elements */
3506  nav, header, footer,
3507  nav *, header *, footer *,
3508  .navbar, .navbar *,
3509  .nav-item, .nav-link,
3510  .pd-sticky-header,
3511  .pd-sticky-header.visible,
3512  .pd-breadcrumb,
3513  .pd-sidebar,
3514  .pd-mobile-fab,
3515  .pd-share-btn,
3516  .pd-sticky-share-btn,
3517  #pd-share-btn,
3518  .share-button,
3519  [data-share-btn],
3520  button,
3521  .btn-add-cart,
3522  .btn-buy,
3523  .pd-actions,
3524  .pd-compare-section,
3525  .pd-compare-btn,
3526  .pd-compare-info,
3527  #pd-compare-btn,
3528  .compare-note,
3529  .compare-container,
3530  .compare-dropdown,
3531  .compare-panel,
3532  .compare-wrapper,
3533  [data-compare],
3534  [id*="compare"],
3535  [class*="compare"],
3536  .pd-thumb-gallery,
3537  .pd-installment-card,
3538  .pd-installment-card2,
3539  .inst-alt-banner--compact,
3540  .qoala-banner-card,
3541  .tradein-compact-card,
3542  .pwp-deals-section,
3543  #pwp-deals-section,
3544  .pwp-trigger-badge,
3545  .pd-related-full,
3546  #pd-similar,
3547  .pd-similar-strip,
3548  .pd-scroll-top,
3549  .stock-info-btn,
3550  .stock-popover,
3551  .pd-countdown-modern,
3552  .pd-share-modal-overlay,
3553  [id*="promo-"],
3554  .bottom-nav,
3555  .pd-buy-form,
3556  .pd-buy-card,
3557  .pd-extra-meta,
3558  .pd-reviews-section,
3559  #reviews,
3560  hr,
3561  .divider,
3562  .separator {
3563    display: none !important;
3564    visibility: hidden !important;
3565    opacity: 0 !important;
3566    height: 0 !important;
3567    max-height: 0 !important;
3568    overflow: hidden !important;
3569    margin: 0 !important;
3570    padding: 0 !important;
3571    border: none !important;
3572  }
3573
3574  /* Extra aggressive reset for any remaining borders */
3575  .pd-info-card *,
3576  .pd-meta-inline *,
3577  .pd-variants * {
3578    border: none !important;
3579    border-top: none !important;
3580    border-bottom: none !important;
3581  }
3582
3583  /* Layout restructure */
3584  .product-single {
3585    max-width: 100% !important;
3586    margin: 0 !important;
3587    padding: 0 !important;
3588    box-shadow: none !important;
3589  }
3590
3591  /* Reset all shadows and borders for print */
3592  * {
3593    box-shadow: none !important;
3594  }
3595
3596  .pd-top-layout {
3597    display: grid !important;
3598    grid-template-columns: 280px 1fr !important;
3599    gap: 24px !important;
3600    margin: 0 0 24px 0 !important;
3601    padding: 0 !important;
3602    page-break-inside: avoid;
3603    align-items: start !important;
3604  }
3605
3606  /* Print header with logo - centered and larger */
3607  .product-single::before {
3608    content: "" !important;
3609    display: block !important;
3610    width: 150px !important;
3611    height: 50px !important;
3612    background: url('/assets/img/logo.png') no-repeat center !important;
3613    background-size: contain !important;
3614    margin: 0 auto 20px !important;
3615    visibility: visible !important;
3616    opacity: 1 !important;
3617  }
3618
3619  /* Media card - left column */
3620  .pd-media-card {
3621    border: none !important;
3622    padding: 0 !important;
3623    margin: 0 !important;
3624    text-align: center;
3625    page-break-inside: avoid;
3626  }
3627
3628  /* Hide desktop image, use mobile slider for both desktop and mobile */
3629  .pd-main-image {
3630    display: none !important;
3631  }
3632
3633  /* Mobile slider - show first image only in print */
3634  .pd-mobile-slider {
3635    display: block !important;
3636    overflow: visible !important;
3637  }
3638
3639  .pd-mslide {
3640    display: none !important;
3641  }
3642
3643  .pd-mslide:first-child {
3644    display: block !important;
3645    border: 1px solid #e2e8f0 !important;
3646    max-width: 280px !important;
3647    margin: 0 auto !important;
3648    border-radius: 8px !important;
3649  }
3650
3651  .pd-mslide:first-child img {
3652    max-height: 280px !important;
3653    width: 100% !important;
3654    height: auto !important;
3655    object-fit: contain !important;
3656  }
3657
3658  /* Info card - right column */
3659  .pd-info-card {
3660    border: none !important;
3661    border-top: none !important;
3662    border-bottom: none !important;
3663    padding: 0 !important;
3664    margin: 0 !important;
3665    background: white !important;
3666    box-shadow: none !important;
3667  }
3668
3669  /* Title */
3670  .pd-title-row {
3671    display: block !important;
3672    margin-bottom: 10px !important;
3673    border: none !important;
3674    border-top: none !important;
3675    border-bottom: none !important;
3676    padding: 0 !important;
3677    box-shadow: none !important;
3678    text-decoration: none !important;
3679  }
3680
3681  /* Hide share button in print */
3682  .pd-title-row .pd-share-btn,
3683  .pd-title-row #pd-share-btn {
3684    display: none !important;
3685  }
3686
3687  .pd-title {
3688    font-size: 13pt !important;
3689    color: #000 !important;
3690    margin: 0 0 10px 0 !important;
3691    page-break-after: avoid;
3692    font-weight: 700 !important;
3693    line-height: 1.3 !important;
3694    border: none !important;
3695    border-top: none !important;
3696    border-bottom: none !important;
3697    padding-bottom: 0 !important;
3698    box-shadow: none !important;
3699    text-decoration: none !important;
3700  }
3701
3702  /* Price and QR container - side by side */
3703  .pd-price-qr-container {
3704    display: flex !important;
3705    gap: 16px !important;
3706    align-items: flex-start !important;
3707    margin-bottom: 14px !important;
3708    box-shadow: none !important;
3709    border: none !important;
3710    text-decoration: none !important;
3711  }
3712
3713  /* Price block - left side */
3714  .pd-price-block {
3715    flex: 1 !important;
3716    margin: 0 !important;
3717    padding-bottom: 0 !important;
3718    border-bottom: none !important;
3719    box-shadow: none !important;
3720  }
3721
3722  .pd-price-row {
3723    margin-bottom: 8px !important;
3724    border: none !important;
3725    border-top: none !important;
3726    border-bottom: none !important;
3727    padding: 0 !important;
3728  }
3729
3730  /* Hide all empty or unnecessary elements in price row */
3731  .pd-price-row > span:empty,
3732  .pd-price-row > .sold-inline,
3733  #pd-sold-inline {
3734    display: none !important;
3735  }
3736
3737  .pd-price-current {
3738    font-size: 18pt !important;
3739    color: #0d6efd !important;
3740  }
3741
3742  /* Show price range for variant products in print */
3743  .pd-price-current[data-price-range]::after {
3744    content: attr(data-price-range) !important;
3745    display: block !important;
3746    font-size: 11pt !important;
3747    color: #64748b !important;
3748    margin-top: 4px !important;
3749    font-weight: normal !important;
3750  }
3751
3752  /* Hide 'Range:' prefix text */
3753  .pd-price-current[data-price-range]::after {
3754    content: attr(data-price-range) !important;
3755  }
3756
3757  .pd-price-original {
3758    font-size: 12pt !important;
3759    color: #64748b !important;
3760  }
3761
3762  .pd-discount-badge {
3763    font-size: 10pt !important;
3764    padding: 2px 6px !important;
3765  }
3766
3767  /* QR Code section - right side beside price */
3768  .pd-qr-print {
3769    display: block !important;
3770    flex: 0 0 100px !important;
3771    margin: 0 !important;
3772    padding: 8px !important;
3773    background: #f8fafc !important;
3774    border-radius: 8px !important;
3775    text-align: center !important;
3776    page-break-inside: avoid !important;
3777    border: 1px solid #cbd5e1 !important;
3778  }
3779
3780  .pd-qr-label {
3781    font-size: 6.5pt !important;
3782    color: #64748b !important;
3783    margin-bottom: 5px !important;
3784    line-height: 1.2 !important;
3785    font-weight: 600 !important;
3786  }
3787
3788  .pd-qr-image {
3789    width: 80px !important;
3790    height: 80px !important;
3791    display: block !important;
3792    margin: 0 auto !important;
3793  }
3794
3795  /* Meta info */
3796  .pd-meta-inline {
3797    display: flex !important;
3798    gap: 12px !important;
3799    margin-bottom: 0 !important;
3800    padding-bottom: 14px !important;
3801    font-size: 10pt !important;
3802    border: none !important;
3803    border-top: none !important;
3804    border-bottom: none !important;
3805    padding-top: 0 !important;
3806    box-shadow: none !important;
3807    text-decoration: none !important;
3808    background: transparent !important;
3809  }
3810
3811  /* Hide stock badge in print */
3812  .stock-badge,
3813  .stock-info-btn {
3814    display: none !important;
3815  }
3816
3817  .sku {
3818    color: #64748b !important;
3819  }
3820
3821  /* Hide sold badge if empty/zero */
3822  .sold-inline {
3823    display: none !important;
3824  }
3825
3826  /* Hide variant summary section in print */
3827  .pd-variant-combined,
3828  #pd-variant-combined,
3829  .pd-variant-summary,
3830  [data-role="sold-chip"] {
3831    display: none !important;
3832  }
3833
3834  /* Hide compare section completely in print */
3835  .pd-compare-section,
3836  .pd-compare-btn,
3837  #pd-compare-btn,
3838  .pd-compare-info,
3839  .compare-note,
3840  .compare-text,
3841  .compare-remove {
3842    display: none !important;
3843    visibility: hidden !important;
3844    height: 0 !important;
3845    margin: 0 !important;
3846    padding: 0 !important;
3847    overflow: hidden !important;
3848  }
3849
3850  /* Variants - show all options with selected highlighted */
3851  .pd-variants {
3852    margin: 0 !important;
3853    padding: 0 !important;
3854    page-break-inside: avoid !important;
3855    border: none !important;
3856    border-top: none !important;
3857    border-bottom: none !important;
3858    box-shadow: none !important;
3859    background: transparent !important;
3860  }
3861
3862  /* Add 'Variant:' label before variant groups */
3863  .pd-variants::before {
3864    content: "Variant:" !important;
3865    display: block !important;
3866    font-size: 11pt !important;
3867    font-weight: 700 !important;
3868    color: #0f172a !important;
3869    margin-bottom: 10px !important;
3870    border: none !important;
3871    padding: 0 !important;
3872    border-top: none !important;
3873    border-bottom: none !important;
3874  }
3875
3876  .v-group {
3877    display: flex !important;
3878    align-items: center !important;
3879    gap: 8px !important;
3880    margin-bottom: 8px !important;
3881    line-height: 1 !important;
3882    page-break-inside: avoid !important;
3883    border: none !important;
3884    padding: 0 !important;
3885  }
3886
3887  .v-group:last-child {
3888    margin-bottom: 0 !important;
3889  }
3890
3891  .v-label {
3892    font-weight: 700 !important;
3893    margin: 0 !important;
3894    font-size: 9pt !important;
3895    color: #475569 !important;
3896    min-width: 60px !important;
3897    flex-shrink: 0 !important;
3898    border: none !important;
3899    padding: 0 !important;
3900  }
3901
3902  .v-options {
3903    display: flex !important;
3904    gap: 6px !important;
3905    flex-wrap: wrap !important;
3906    border: none !important;
3907    padding: 0 !important;
3908    margin: 0 !important;
3909  }
3910
3911  .v-chip {
3912    border: 1px solid #cbd5e1 !important;
3913    padding: 3px 10px !important;
3914    font-size: 9pt !important;
3915    background: #f8fafc !important;
3916    border-radius: 6px !important;
3917    display: inline-flex !important;
3918    align-items: center !important;
3919    page-break-inside: avoid !important;
3920  }
3921
3922  /* Highlight selected variant with blue background */
3923  .v-chip.selected {
3924    background: #eff6ff !important;
3925    border-color: #2563eb !important;
3926    font-weight: 600 !important;
3927  }
3928
3929  /* Hide radio inputs */
3930  .v-chip input[type="radio"] {
3931    display: none !important;
3932  }
3933
3934  /* Hide "Habis" badge in print */
3935  .v-chip .chip-badge {
3936    display: none !important;
3937  }
3938
3939  /* Short description - HIDDEN in print */
3940  .pd-short-desc {
3941    display: none !important;
3942  }
3943
3944  /* QR Code section - right side beside price */
3945  .pd-qr-print {
3946    display: block !important;
3947    flex: 0 0 100px !important;
3948    margin: 0 !important;
3949    padding: 8px !important;
3950    background: #f8fafc !important;
3951    border-radius: 8px !important;
3952    text-align: center !important;
3953    page-break-inside: avoid !important;
3954    border: 1px solid #cbd5e1 !important;
3955  }
3956
3957  .pd-qr-label {
3958    font-size: 6.5pt !important;
3959    color: #64748b !important;
3960    margin-bottom: 5px !important;
3961    line-height: 1.2 !important;
3962    font-weight: 600 !important;
3963  }
3964
3965  .pd-qr-image {
3966    width: 80px !important;
3967    height: 80px !important;
3968    display: block !important;
3969    margin: 0 auto !important;
3970  }
3971
3972  /* Full description - below 2 column layout */
3973  .pd-full-description {
3974    border: none !important;
3975    padding: 0 !important;
3976    margin: 24px 0 0 0 !important;
3977    page-break-before: avoid;
3978    grid-column: 1 / -1 !important;
3979  }
3980
3981  .pd-full-desc-title {
3982    font-size: 13pt !important;
3983    font-weight: 700 !important;
3984    margin: 0 0 12px 0 !important;
3985    color: #000 !important;
3986    border-top: none !important;
3987    border-bottom: none !important;
3988    padding-top: 16px !important;
3989  }
3990
3991  .pd-full-desc-body {
3992    font-size: 10pt !important;
3993    line-height: 1.6 !important;
3994    color: #334155 !important;
3995  }
3996
3997  .pd-full-desc-body img {
3998    max-height: 200px !important;
3999    width: auto !important;
4000    page-break-inside: avoid;
4001    display: none !important; /* Hide images in print to save space */
4002  }
4003
4004  /* Also target #deskripsi img if exists */
4005  #deskripsi img {
4006    max-width: 350px !important;
4007    height: auto !important;
4008  }
4009
4010  .pd-full-desc-body table {
4011    border-collapse: collapse !important;
4012    width: 100% !important;
4013    margin: 12px 0 !important;
4014    font-size: 9pt !important;
4015  }
4016
4017  .pd-full-desc-body th,
4018  .pd-full-desc-body td {
4019    border: 1px solid #cbd5e1 !important;
4020    padding: 6px 8px !important;
4021  }
4022
4023  /* Print footer */
4024  .product-single::after {
4025    content: "Plaza IT - plazait.co.id | Printed: " attr(data-print-date);
4026    display: block;
4027    margin-top: 30px;
4028    padding-top: 16px;
4029    border-top: 1px solid #e2e8f0;
4030    font-size: 9pt;
4031    color: #64748b;
4032    text-align: center;
4033  }
4034
4035  /* Page breaks */
4036  .pd-media-card,
4037  .pd-price-block,
4038  .pd-full-desc-title {
4039    page-break-after: avoid;
4040  }
4041
4042  .pd-full-description {
4043    page-break-before: auto;
4044  }
4045}
4046
4047/* === Breadcrumb (truncate last item) === */
4048.pd-breadcrumb{
4049  display:flex;
4050  flex-wrap:nowrap;
4051  align-items:center;
4052  gap:.5rem;
4053  font-size:.72rem;
4054  margin:.7rem 0 1.2rem .5rem;
4055  color:var(--pd-muted);
4056  overflow:hidden;
4057}
4058.pd-breadcrumb a{color:var(--pd-accent);text-decoration:none}
4059.pd-breadcrumb a:hover{text-decoration:underline}
4060.pd-breadcrumb a,
4061.pd-breadcrumb span{flex:0 0 auto}
4062.pd-breadcrumb .current{
4063  flex:1 1 auto;
4064  min-width:0;
4065  white-space:nowrap;
4066  overflow:hidden;
4067  text-overflow:ellipsis;
4068  font-weight:600;
4069  color:var(--pd-dark);
4070  padding-right:2px;
4071}
4072
4073/* === Top layout === */
4074.pd-top-layout{
4075  display:grid;
4076  gap:var(--pd-gap);
4077  grid-template-columns:minmax(260px,340px) minmax(460px,1fr) 300px;
4078  align-items:start;
4079  margin-bottom:2.4rem;
4080}
4081
4082/* === Media column === */
4083.pd-media-card{
4084  background:#fff;
4085  border:1px solid var(--pd-border);
4086  border-radius:var(--pd-radius);
4087  padding:1rem 1rem 1.2rem;
4088}
4089.pd-main-image{margin:0 0 .8rem;border:1px solid var(--pd-soft-border);border-radius:10px;overflow:hidden;background:#fff;position:relative}
4090.pd-main-image img{width:100%;display:block;object-fit:contain;aspect-ratio:1/1}
4091
4092/* Gallery Thumbnails */
4093.pd-thumb-gallery{margin-top:.6rem;display:flex;flex-wrap:wrap;gap:.55rem}
4094.pd-thumb-btn{
4095  border:1px solid var(--pd-soft-border);
4096  background:#fff;
4097  cursor:pointer;
4098  width:64px;height:64px;padding:2px;
4099  display:flex;align-items:center;justify-content:center;
4100  overflow:hidden;border-radius:8px;
4101  transition:border-color .18s ease, box-shadow .18s ease, transform .18s ease;
4102  position:relative;
4103}
4104.pd-thumb-btn img{max-width:100%;max-height:100%;object-fit:contain;display:block}
4105.pd-thumb-btn:hover{border-color:var(--pd-accent)}
4106.pd-thumb-btn.active{border-color:var(--pd-accent);box-shadow:0 0 0 2px rgba(13,110,253,.25)}
4107.pd-thumb-btn:focus-visible{outline:2px solid var(--pd-accent);outline-offset:2px}
4108.pd-thumb-btn:active{transform:scale(.96)}
4109
4110/* === Info column === */
4111.pd-info-card{
4112  background:#fff;border:1px solid var(--pd-border);border-radius:var(--pd-radius);
4113  padding:1.4rem 1.6rem 2rem;
4114}
4115.pd-title{margin:0 0 1rem;font-size:1.2rem;line-height:1.25;font-weight:600;color:var(--pd-dark)}
4116.pd-price-block{margin:0 0 1rem}
4117.pd-price-row{display:flex;flex-wrap:wrap;align-items:center;gap:.75rem;margin:0 0 .35rem}
4118.pd-price-current{font-size:2rem;font-weight:700;color:var(--pd-accent)}
4119.pd-price-original{font-size:.9rem;text-decoration:line-through;color:#94a3b8}
4120.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}
4121.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}
4122.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}
4123.stock-badge.in{background:var(--pd-success-bg);color:var(--pd-success-text)}
4124.stock-badge.out{background:var(--pd-danger-bg);color:var(--pd-danger-text)}
4125.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}
4126.sku{color:var(--pd-muted)}
4127.pd-short-desc{font-size:.85rem;line-height:1.5;margin:0 0 1.2rem;color:#334155;overflow-wrap:anywhere}
4128.pd-short-desc p{margin:0 0 .75rem}
4129.pd-short-desc ul,.pd-short-desc ol{margin:.5rem 0 .9rem 1.1rem;font-size:.85rem;line-height:1.45}
4130
4131/* Meta row for sold and rating badges */
4132.pd-meta-row {
4133  display: flex;
4134  align-items: center;
4135  flex-wrap: wrap;
4136  gap: 5px;
4137  margin-top: 8px;
4138  margin-bottom: 4px;
4139}
4140
4141/* Sales (Terjual) badge inline */
4142.sold-inline{
4143  display:inline-block;font-size:.65rem;font-weight:600;color:#475569;
4144  background:#f1f5f9;padding:3px 8px 4px;border-radius:6px;letter-spacing:.4px;margin-left:0;
4145}
4146
4147/* Price row divider */
4148.price-divider {
4149  display: inline-block;
4150  margin: 0 .4rem;
4151  color: #cbd5e1;
4152  font-weight: 400;
4153}
4154
4155/* Rating badge inline */
4156.rating-inline {
4157  display: inline-flex;
4158  align-items: center;
4159  gap: 4px;
4160  padding: 3px 8px 4px;
4161  background: #fff7ed;
4162  border-radius: 6px;
4163  font-size: .65rem;
4164  font-weight: 600;
4165  color: #ea580c;
4166  text-decoration: none;
4167  transition: all .2s ease;
4168  cursor: pointer;
4169  margin-left: .5rem;
4170  white-space: nowrap;
4171}
4172
4173.rating-inline:hover {
4174  background: #ffedd5;
4175  transform: translateY(-1px);
4176  box-shadow: 0 2px 4px rgba(234, 88, 12, 0.1);
4177}
4178
4179.rating-inline .star-icon {
4180  font-size: .75rem;
4181  color: #f59e0b;
4182  line-height: 1;
4183}
4184
4185.rating-inline .rating-value {
4186  color: #ea580c;
4187}
4188
4189.rating-inline .rating-badge-count {
4190  color: #9a3412;
4191  opacity: 0.85;
4192}
4193
4194@media (max-width: 640px) {
4195  .rating-inline {
4196    font-size: .65rem;
4197    padding: 3px 8px 4px;
4198  }
4199  .rating-inline .star-icon {
4200    font-size: .7rem;
4201  }
4202}
4203
4204/* Highlight animation for review section */
4205@keyframes highlightFlash {
4206  0%, 100% { background-color: transparent; }
4207  50% { background-color: rgba(251, 146, 60, 0.1); }
4208}
4209
4210.highlight-flash {
4211  animation: highlightFlash 1.5s ease;
4212}
4213
4214/* === Variant chips === */
4215.pd-variants{margin-top:14px}
4216.v-group{margin-bottom:12px}
4217.v-label{font-weight:600;margin-bottom:6px}
4218.v-options{display:flex;flex-wrap:wrap;gap:8px}
4219.v-chip{
4220  display:inline-flex;align-items:center;gap:8px;
4221  padding:6px 10px;border:1px solid #d6d9de;border-radius:999px;
4222  cursor:pointer;background:#fff;transition:.15s ease;user-select:none
4223}
4224.v-chip:hover{border-color:#9aa3af;box-shadow:0 1px 0 rgba(0,0,0,.04)}
4225.v-chip.selected{border-color:#2563eb;background:#eff6ff}
4226.v-chip.disabled{opacity:.45;cursor:not-allowed}
4227.v-chip.out{border-style:dashed}
4228.v-chip input{display:none}
4229.v-chip .dot{width:14px;
4229height:14px;border-radius:50%;background:var(--dot,#999);box-shadow:inset 0 0 0 1px rgba(0,0,0,.2)}
4230.v-chip .dot--neutral{background:#9aa3af}
4231.v-chip .txt{font-size:.95rem;white-space:nowrap}
4232.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}
4233.pd-variant-summary{margin-top:8px;color:#475569;font-size:.92rem}
4234.pd-variant-summary .vs-item{display:inline-flex;gap:4px;margin-right:10px}
4235
4236/* === Buy form (di sidebar kanan untuk desktop) === */
4237.pd-buy-form{display:flex;flex-wrap:wrap;align-items:flex-end;gap:1rem 1.4rem;margin:0}
4238.qty-label{display:block;font-size:.6rem;font-weight:600;letter-spacing:.7px;text-transform:uppercase;color:var(--pd-muted);margin-bottom:.35rem}
4239.pd-buy-form input[type=number]{width:90px;padding:.55rem .6rem;border:1px solid var(--pd-soft-border);border-radius:6px;font-size:.85rem}
4240.pd-actions{display:flex;gap:.8rem;flex-wrap:wrap}
4241
4242/* Tombol senada tema */
4243.btn-add-cart,.btn-buy,.btn-chat-icon{
4244  cursor:pointer;border:0;font-weight:700;border-radius:10px;font-size:.9rem;
4245  display:inline-flex;align-items:center;justify-content:center;gap:.4rem;
4246  min-height:44px;padding:0 1rem;transition:var(--pd-transition)
4247}
4248.btn-add-cart{background:var(--brand-500);color:#fff;box-shadow:0 2px 8px -2px rgba(13,110,253,.35)}
4249.btn-add-cart:hover{filter:brightness(.95)}
4250.btn-buy{background:#fff;color:var(--brand-500);border:2px solid var(--brand-500)}
4251.btn-buy:hover{background:rgba(13,110,253,.06)}
4252.btn-chat-icon{background:#fff;color:#fb923c;border:2px solid #fb923c;padding:0;width:44px;min-width:44px}
4253.btn-chat-icon:hover{background:rgba(251, 146, 60, 0.06);border-color:#f97316}
4254.btn-chat-icon svg{width:20px;height:20px;flex-shrink:0}
4255.btn-add-cart:disabled,.btn-buy:disabled,.btn-chat-icon:disabled{opacity:.5;cursor:not-allowed}
4256
4257/* Buy with Voucher Button - Cleaner, more compact design */
4258.btn-buy-voucher{
4259  cursor:pointer;border:0;font-weight:600;border-radius:10px;
4260  display:inline-flex;align-items:center;justify-content:space-between;gap:10px;
4261  min-height:44px;padding:0 14px 0 12px;transition:all 0.3s ease;
4262  background:linear-gradient(135deg, #7c3aed 0%, #9333ea 100%);
4263  color:#fff;
4264  box-shadow:0 3px 10px rgba(124,58,237,0.3);
4265  position:relative;
4266  overflow:hidden;
4267  flex:1 1 auto
4268}
4269.btn-buy-voucher:hover{
4270  transform:translateY(-2px);
4271  box-shadow:0 5px 16px rgba(124,58,237,0.4)
4272}
4273.btn-buy-voucher:active{
4274  transform:translateY(0);
4275  box-shadow:0 2px 6px rgba(124,58,237,0.3)
4276}
4277.btn-buy-voucher .bv-icon{
4278  font-size:20px;
4279  line-height:1;
4280  flex-shrink:0
4281}
4282.btn-buy-voucher .bv-text{
4283  display:flex;
4284  flex-direction:column;
4285  align-items:flex-start;
4286  gap:2px;
4287  flex:1;
4288  min-width:0;
4289  font-size:14px;
4290  line-height:1.2
4291}
4292.btn-buy-voucher .bv-text small{
4293  font-size:10px;
4294  opacity:0.85;
4295  font-weight:500;
4296  letter-spacing:0.3px;
4297  font-family:monospace
4298}
4299.btn-buy-voucher .bv-save{
4300  font-size:12px;
4301  font-weight:700;
4302  background:rgba(255,255,255,0.25);
4303  padding:4px 8px;
4304  border-radius:6px;
4305  white-space:nowrap;
4306  flex-shrink:0
4307}
4308
4309/* Extra meta (opsional) */
4310.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}
4311.pd-extra-meta span{font-weight:600;color:var(--pd-muted);margin-right:.3rem}
4312.pd-extra-meta a{color:var(--pd-accent);text-decoration:none}
4313.pd-extra-meta a:hover{text-decoration:underline}
4314
4315/* === Sidebar === */
4316.pd-sidebar{display:flex;flex-direction:column;gap:1.2rem}
4317.pd-side-card{background:#fff;border:1px solid var(--pd-border);border-radius:var(--pd-radius);padding:1rem 1.1rem 1.2rem}
4318.pd-side-card h3{margin:0 0 .7rem;font-size:.8rem;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:var(--pd-dark)}
4319.pd-side-card ul{list-style:none;margin:0;padding:0;display:grid;gap:.45rem;font-size:.7rem;color:#334155}
4320.pd-side-card li{position:relative;padding-left:13px;line-height:1.25}
4321.pd-side-card li:before{content:'';width:6px;height:6px;background:var(--pd-accent);border-radius:50%;position:absolute;
4321left:0;top:.45rem}
4322.pay-logos{display:flex;gap:.4rem;flex-wrap:wrap;margin:.1rem 0 .4rem}
4323.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}
4324
4325/* Buy card hanya desktop (mobile pakai FAB) */
4326.pd-buy-card{display:block}
4327@media (max-width:880px){ .pd-buy-card{display:none} .pd-title-row {padding-right: 0 !important;}}
4328
4329/* === Full Description === */
4330.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}
4331.pd-full-desc-title{margin:0 0 1.1rem;font-size:1.15rem;font-weight:600;color:var(--pd-dark)}
4332.pd-full-desc-body{font-size:.9rem;line-height:1.6;color:#334155;overflow-wrap:anywhere}
4333.pd-full-desc-body p{margin:0 0 1rem}
4334.pd-full-desc-body ul,.pd-full-desc-body ol{margin:.5rem 0 1rem 1.2rem}
4335.pd-full-desc-body table{width:100%;border-collapse:collapse;margin:1rem 0 1.2rem}
4336.pd-full-desc-body table,.pd-full-desc-body th,.pd-full-desc-body td{border-bottom:1px dotted #cbd5e1;vertical-align: top}
4337.pd-full-desc-body th,.pd-full-desc-body td{padding:.45rem .55rem;text-align:left}
4338.pd-full-desc-body img{max-width:100%;height:auto;display:block;}
4339
4340/* === Related (container) === */
4341.pd-related-full{
4342  background:#fff;border:1px solid var(--pd-border);
4343  border-radius:var(--pd-radius);padding:1.4rem 1.6rem 1.9rem;
4344  margin:0 0 2rem;
4345}
4346.pd-related-title{margin:0 0 1.1rem;font-size:1.05rem;font-weight:600;color:var(--pd-dark)}
4347
4348/* Related Grid (dirapikan: gabung selector agar tidak duplikat) */
4349.pd-related-grid,
4350.pd-related-grid--full{
4351  display:grid !important;
4352  gap:16px !important;
4353  grid-template-columns:repeat(auto-fill,minmax(170px,1fr)) !important;
4354  align-items:stretch;
4355}
4356
4357/* Fallback jika grid tidak didukung */
4358@supports not (display:grid){
4359  .pd-related-grid,
4360  .pd-related-grid--full{display:flex !important;flex-wrap:wrap}
4361  .pd-related-grid .pd-related-card,
4362  .pd-related-grid--full .pd-related-card{flex:0 0 calc(20% - 16px);max-width:calc(20% - 16px)}
4363  @media (max-width:1100px){
4364    .pd-related-grid .pd-related-card,
4365    .pd-related-grid--full .pd-related-card{flex:0 0 calc(25% - 16px);max-width:calc(25% - 16px)}
4366  }
4367  @media (max-width:820px){
4368    .pd-related-grid .pd-related-card,
4369    .pd-related-grid--full .pd-related-card{flex:0 0 calc(33.333% - 16px);max-width:calc(33.333% - 16px)}
4370  }
4371  @media (max-width:580px){
4372    .pd-related-grid .pd-related-card,
4373    .pd-related-grid--full .pd-related-card{flex:0 0 calc(50% - 16px);max-width:calc(50% - 16px)}
4374  }
4375}
4376
4377/* Related Card */
4378.pd-related-card{
4379  display:flex;flex-direction:column;border:1px solid var(--pd-soft-border);
4380  background:#fff;border-radius:10px;text-decoration:none;color:inherit;
4381  overflow:hidden;transition:var(--pd-transition);position:relative;min-width:0;
4382}
4383.pd-related-card:hover{transform:translateY(-4px);box-shadow:var(--pd-shadow)}
4384.pd-related-card .thumb{aspect-ratio:1/1;background:#f1f5f9;overflow:hidden;position:relative}
4385.pd-related-card img{width:100%;height:100%;object-fit:cover;display:block}
4386.mini-disc{position:absolute;top:6px;left:6px;background:#dc2626;color:#fff;font-size:.55rem;padding:3px 6px 2px;border-radius:4px;font-weight:600}
4387.pd-related-card .info{padding:.55rem .65rem .75rem;display:flex;flex-direction:column;gap:.25rem}
4388.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}
4389.pd-related-card .price{font-size:.7rem;font-weight:600;color:var(--pd-accent)}
4390.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;}
4391.pd-related-card .price .final {font-size: .82rem; font-weight: 700; color: #0d6efd;}
4392.pd-related-card .price .ori {font-size: .6rem; text-decoration: line-through; color: #94a3b8;font-weight: 500;}
4393
4394/* === Mobile slider (scroll-snap) — gambar utama tetap tampil === */
4395.pd-mobile-slider{display:none}
4396.pd-mobile-slider img{width:100%;display:block;object-fit:contain;aspect-ratio:1/1}
4397
4398/* Gallery counter badge */
4399.pd-gallery-counter{
4400  position:absolute;
4401  bottom:12px;
4402  left:12px;
4403  background:rgba(255,255,255,0.25);
4404  color:#9E9E9E;
4405  padding:4px 10px;
4406  border-radius:10px;
4407  font-size:12px;
4408  font-weight:600;
4409  z-index:100;
4410  backdrop-filter:blur(6px);
4411  -webkit-backdrop-filter:blur(6px);
4412  font-variant-numeric:tabular-nums;
4413  pointer-events:none;
4414  display:none;
4415  box-shadow:0 1px 4px rgba(0,0,0,.15);
4416  line-height:1;
4417  border:1px solid rgba(0,0,0,0.08);
4418}
4419
4420@media (max-width:640px){
4421  .pd-media-card{border: 0;padding: 0 1rem 0;}
4422  .pd-info-card {border: 0; padding: 0 1.3rem 0; }
4423  .pd-side-card {flex: 1 1 !important;}
4424  .pd-top-layout {gap: 0; margin-bottom: 0;}
4425  .pd-variants {margin-bottom: 1.5rem;}
4426  .pd-main-image{display:none}
4427  .pd-gallery-counter{display:block}
4428  .pd-mobile-slider-wrapper{
4429    position:relative;
4430    margin-bottom:.6rem;
4431  }
4432  .pd-mobile-slider{
4433    display:flex;gap:8px;overflow-x:auto;
4434    scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;
4435  }
4436  .pd-mslide{
4437    min-width:100%;flex:0 0 100%;scroll-snap-align:center;
4438    border:1px solid var(--pd-soft-border);border-radius:10px;overflow:hidden;background:#fff
4439  }
4440  .pd-thumb-gallery{display:none} /* hide thumbnails on mobile */
4441}
4442
4443/* === Mobile Floating Action Bar === */
4444.pd-mobile-fab{
4445  position:fixed;
4446  left:0;
4447  right:0;
4448  bottom:42px; /* Position above bottom-nav (60px min-height + 6px padding) */
4449  display:none;
4450  align-items:center;
4451  gap:.75rem;
4452  padding:12px 16px;
4453  background:rgba(255,255,255,0.95);
4454  backdrop-filter:blur(20px);
4455  -webkit-backdrop-filter:blur(20px);
4456  border-top:1px solid rgba(228,231,235,0.6);
4457  box-shadow:0 -4px 16px -4px rgba(0,0,0,0.12);
4458  z-index:999; /* Below bottom-nav (1000) */
4459  transition:bottom .3s cubic-bezier(0.4, 0.0, 0.2, 1);
4460}
4461
4462/* When bottom-nav is hidden, move FAB to bottom */
4463body:has(.bottom-nav.hidden) .pd-mobile-fab {
4464  bottom: 0;
4465}
4466.pd-mobile-fab .mf-actions{display:flex;gap:.5rem;width:100%}
4467.mf-btn{
4468  border-radius:10px;
4469  padding:.6rem 1rem;
4470  font-weight:600;
4471  font-size:14px;
4472  cursor:pointer;
4473  transition:all .2s;
4474  -webkit-tap-highlight-color:transparent;
4475  display:inline-flex;
4476  align-items:center;
4477  justify-content:center;
4478  gap:4px;
4479}
4480.mf-buy{
4481  background:#fb923c;
4482  color:#fff;
4483  border:0;
4484  box-shadow:0 2px 8px rgba(251, 146, 60, 0.3);
4485  flex:1;
4486  flex-direction:column;
4487  padding:.55rem 1rem;
4488  line-height:1;
4489  min-height:44px;
4490  font-family:inherit;
4491}
4492.mf-buy .mf-buy-text{
4493  font-size:11px;
4494  font-weight:600;
4495  opacity:0.95;
4496  font-family:inherit;
4497}
4498.mf-buy .mf-buy-price{
4499  font-size:13px;
4500  font-weight:800;
4501  letter-spacing:-0.3px;
4502  font-family:inherit;
4503  line-height:1;
4504}
4505.mf-buy:active{transform:scale(0.97)}
4506.mf-cart{
4507  background:#fff;
4508  color:#fb923c;
4509  border:2px solid #fb923c;
4510  width:44px;
4511  height:44px;
4512  min-height:44px;
4513  padding:0;
4514  flex-shrink:0;
4515  font-family:inherit;
4516}
4517.mf-cart:active{transform:scale(0.95)}
4518.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}
4519.mf-chat:active{transform:scale(0.95)}
4520.mf-chat svg{width:20px;height:20px;flex-shrink:0}
4521.mf-share{
4522  background:#fff;
4523  color:#fb923c;
4524  border:2px solid #fb923c;
4525  width:44px;
4526  height:44px;
4527  padding:0;
4528  flex-shrink:0;
4529}
4530.mf-share:active{transform:scale(0.95)}
4531.mf-btn:disabled{opacity:.5;cursor:not-allowed}
4532@media (max-width:640px){
4533  .pd-mobile-fab{display:flex}
4534  .product-single{padding-bottom:0;} /* Space for FAB + bottom-nav */
4535}
4536
4537/* === Installment cards === */
4538.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}
4539.pd-installment-card h3{margin:0 0 .2rem;font-size:.78rem;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:var(--pd-dark)}
4540
4541.inst-tabs{display:flex;gap:.4rem}
4542.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)}
4543.inst-tab.active,.inst-tab:hover{background:var(--pd-accent);color:#fff;border-color:var(--pd-accent)}
4544.inst-tab:focus{outline:2px solid var(--pd-accent);outline-offset:2px}
4545
4546.inst-tenors{display:flex;gap:.4rem;flex-wrap:wrap}
4547.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}
4548.tenor-btn.active,.tenor-btn:hover{background:#1d7df0;color:#fff;border-color:#1d7df0}
4549
4550.inst-result{display:flex;flex-direction:column;gap:.45rem;font-size:.63rem}
4551.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}
4552.inst-line.highlight{background:#1d7df0;color:#fff;border-color:#1d7df0;font-weight:600}
4553.inst-line .lbl{opacity:.8}
4554.inst-line .val{font-weight:600;letter-spacing:.3px}
4555.inst-footnote{font-size:.53rem;line-height:1.25;color:#64748b;margin-top:.2rem}
4556@media (max-width: 860px){
4557  .pd-installment-card{order:3}
4558  .inst-tabs .inst-tab,.inst-tenors .tenor-btn{font-size:.65rem}
4559
4560  /* Voucher button responsive - make it simpler */
4561  .btn-buy-voucher{
4562    padding:0 12px;
4563    gap:8px
4564  }
4565  .btn-buy-voucher .bv-icon{
4566    font-size:18px
4567  }
4568  .btn-buy-voucher .bv-text{
4569    font-size:13px
4570  }
4571  .btn-buy-voucher .bv-text small{
4572    font-size:9px
4573  }
4574  .btn-buy-voucher .bv-save{
4575    font-size:11px;
4576    padding:3px 6px
4577  }
4578}
4579
4580/* Installment List v2 (eye-catching) */
4581.pd-installment-card2{
4582  background:linear-gradient(135deg,#ffffff 0%,#f1f7ff 65%,#e8f2ff 100%);
4583  border:1px solid var(--pd-border);border-radius:var(--pd-radius);
4584  padding:1rem 1.05rem 1.2rem;display:flex;flex-direction:column;gap:.5rem;
4585  position:relative;overflow:hidden
4586}
4587.pd-installment-card2:before{
4588  content:'';position:absolute;width:140px;height:140px;
4589  background:radial-gradient(circle at center,rgba(29,125,240,.18),rgba(29,125,240,0));
4590  top:-40px;right:-40px;pointer-events:none
4591}
4592.pd-installment-card2 h3{margin:0;font-size:.8rem;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:#0f2f54}
4593.pd-installment-card2 .inst-head{display:flex;align-items:center;justify-content:space-between;gap:.5rem}
4594.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)}
4595
4596.inst-bank-tabs{display:flex;gap:.45rem}
4597.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}
4598.bank-tab.active,.bank-tab:hover{background:#1d7df0;color:#fff;border-color:#1d7df0;box-shadow:0 3px 10px -4px rgba(29,125,240,.6)}
4599.bank-tab:focus{outline:2px solid #1d7df0;outline-offset:2px}
4600
4601.inst-month-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.55rem}
4602.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}
4603.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}
4604.inst-month-item:hover{transform:translateY(-2px);box-shadow:0 6px 14px -6px rgba(0,40,120,.25)}
4605.inst-month-item:hover:before{opacity:1}
4606.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)}
4607.amount{flex:1;font-size:.8rem;font-weight:600;color:#0f2f54;letter-spacing:.3px}
4608.tag{font-size:.55rem;font-weight:600;padding:.28rem .55rem;border-radius:30px;letter-spacing:.4px;text-transform:uppercase;white-space:nowrap}
4609.tag-zero{background:#16a34a;color:#fff;box-shadow:0 2px 8px -3px rgba(16,180,90,.55)}
4610
4611.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}
4612.inst-disclaimer{font-size:.52rem;line-height:1.25;color:#64748b;margin-top:-.2rem}
4613
4614@media (max-width:860px){
4615  .pd-installment-card2{order:3}
4616  .tenor-badge{min-width:38px;font-size:.75rem}
4617  .amount{font-size:.78rem}
4618}
4619
4620/* Alt Fintech Banner (compact) */
4621.inst-alt-banner--compact{
4622  background:linear-gradient(142deg,#1d7df0 0%,#3c98ff 55%,#67bdff 100%);
4623  color:#fff;border:0;padding:.95rem .95rem 1.05rem;border-radius:16px;
4624  box-shadow:0 6px 18px -6px rgba(0,70,160,.45);position:relative;overflow:hidden;min-height:auto;
4625}
4626.inst-alt-banner--compact:before,
4627.inst-alt-banner--compact:after{
4628  content:'';position:absolute;border-radius:50%;background:rgba(255,255,255,.15);filter:blur(2px);pointer-events:none;
4629}
4630.inst-alt-banner--compact:before{width:130px;height:130px;top:-50px;right:-35px}
4631.inst-alt-banner--compact:after{width:90px;height:90px;bottom:-40px;left:-30px}
4632.inst-alt-banner--compact .iab-top{margin:0 0 .55rem;display:flex;flex-direction:column;gap:.25rem}
4633.inst-alt-banner--compact .iab-title{font-size:.74rem;font-weight:700;letter-spacing:.7px;line-height:1.15}
4634.inst-alt-banner--compact .iab-sub{font-size:.55rem;font-weight:500;opacity:.95;letter-spacing:.4px}
4635
4636.iab-logos-compact{display:flex;gap:.55rem;align-items:center;margin:0 0 .6rem;flex-wrap:nowrap;justify-content:space-between}
4637.iab-logos-compact .logo-fin{width:30%}
4638.iab-logos-compact .logo{
4639  width:48px;height:48px;border-radius:14px;display:flex;align-items:center;justify-content:center;
4640  font-size:.62rem;font-weight:700;letter-spacing:.3px;position:relative;color:#fff;
4641  box-shadow:0 4px 10px -4px rgba(0,0,0,.35);background:rgba(255,255,255,.15);
4642  backdrop-filter:blur(4px);transition:.25s;text-shadow:0 1px 2px rgba(0,0,0,.4);user-select:none;
4643}
4644.iab-logos-compact .logo:before{
4645  content:'';position:absolute;inset:0;border-radius:inherit;
4646  background:linear-gradient(140deg,rgba(255,255,255,.35),rgba(255,255,255,0));mix-blend-mode:overlay;opacity:.55;pointer-events:none;
4647}
4648.iab-logos-compact .logo:hover{transform:translateY(-3px) scale(1.05);box-shadow:0 8px 18px -6px rgba(0,0,0,.45)}
4649
4650.iab-cta--compact{
4651  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;
4652}
4653.iab-cta--compact:hover{transform:translateY(-2px);box-shadow:0 8px 18px -7px rgba(0,0,0,.45)}
4654.iab-footnote--compact{margin-top:.45rem;font-size:.46rem;opacity:.85;letter-spacing:.25px}
4655@media (max-width:860px){
4656  .inst-alt-banner--compact{order:4;margin-top:.4rem;flex:1 1}
4657  .iab-logos-compact .logo{width:46px;height:46px}
4658}
4659
4660/* === Scroll To Top (baru) === */
4661.pd-scroll-top{
4662  position:fixed;
4663  right:20px;
4664  bottom:90px;
4665  width:48px;
4666  height:48px;
4667  border-radius:50%;
4668  background:linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
4669  color:#0f2f54;
4670  border:1px solid rgba(226, 232, 240, 0.8);
4671  box-shadow:0 8px 20px -8px rgba(0,0,0,.3), 0 2px 6px rgba(0,0,0,.1);
4672  display:flex;
4673  align-items:center;
4674  justify-content:center;
4675  cursor:pointer;
4676  z-index:1105;
4677  opacity:0;
4678  transform:translateY(12px) scale(.92);
4679  transition:all .3s cubic-bezier(0.4, 0, 0.2, 1);
4680  backdrop-filter:blur(8px);
4681  -webkit-backdrop-filter:blur(8px);
4682}
4683.pd-scroll-top:hover{
4684  box-shadow:0 12px 28px -10px rgba(0,0,0,.4), 0 4px 12px rgba(0,0,0,.15);
4685  transform:translateY(0) scale(1);
4686  background:linear-gradient(135deg, #ffffff 0%, #fefefe 100%);
4687  border-color:rgba(226, 232, 240, 1);
4688}
4689.pd-scroll-top:active{
4690  transform:translateY(0) scale(.96);
4691}
4692.pd-scroll-top:focus-visible{outline:2px solid var(--pd-accent);outline-offset:2px}
4693.pd-scroll-top.visible{opacity:1;transform:translateY(0) scale(1)}
4694@media (max-width:640px){
4695  /* Dynamic positioning via JavaScript */
4696  .pd-scroll-top{
4697    right:14px;
4698    bottom:var(--scroll-top-bottom, calc(68px + 18px + env(safe-area-inset-bottom)));
4699  }
4700}
4701
4702/* === Responsive layout tweaks === */
4703@media (max-width:1250px){
4704  .pd-top-layout{grid-template-columns:minmax(260px,330px) 1fr}
4705  .pd-sidebar{grid-column:1 / -1;flex-direction:row;flex-wrap:wrap;gap:1rem}
4706  /* Other cards 2-per-row; buy card always takes full row to avoid height-mismatch gaps */
4707  .pd-side-card{flex:1 1 calc(50% - .5rem)}
4708  .pd-buy-card{flex:0 0 100%}
4709}
4710@media (max-width:880px){
4711  .pd-top-layout{grid-template-columns:1fr}
4712  .pd-sidebar{flex-direction:column; padding: 0 1rem;}
4713  .pd-title{font-size:1.2rem}
4714  .pd-price-current{font-size:1.5rem}
4715}
4716@media (max-width:640px){
4717  /* Related: 2 kolom di mobile */
4718  .pd-related-full{padding:1.2rem;border:0;padding-top: .6rem;margin-bottom:.6rem;}
4719  .pd-related-grid,.pd-related-grid--full{grid-template-columns:repeat(2,1fr) !important;gap:12px !important}
4720}
4721@media (max-width:560px){
4722  .pd-actions{flex-direction:column}
4723  .btn-add-cart,.btn-buy,.btn-buy-voucher{width:100%}
4724  .pd-full-description{padding:1.2rem;border:0;padding-bottom:0;}
4725}
4726@media (max-width:380px){
4727  .pd-related-full{padding:.9rem .7rem 1.1rem}
4728  .pd-related-grid,.pd-related-grid--full{gap:8px !important}
4729  .pd-related-card .info{padding:.4rem .48rem .55rem}
4730}
4731
4732/* === Modern Countdown (Product Detail) === */
4733.pd-countdown-modern{
4734  --cd-bg1:#0ea5e9;          /* sky-500 */
4735  --cd-bg2:#6366f1;          /* indigo-500 */
4736  --cd-text:#0b1220;
4737  --cd-contrast:#ffffff;
4738  --cd-shadow: 0 8px 24px -10px rgba(0,0,0,.25);
4739  --cd-border: rgba(255,255,255,.25);
4740  --cd-box: rgba(255,255,255,.65);
4741  --cd-box-text:#0f172a;
4742  --cd-danger:#ef4444;
4743
4744  display:flex; gap:.75rem; align-items:center;
4745  background:linear-gradient(135deg,var(--cd-bg1),var(--cd-bg2));
4746  border:1px solid var(--cd-border);
4747  color:var(--cd-contrast);
4748  border-radius:14px;
4749  padding:.6rem .8rem;
4750  box-shadow:var(--cd-shadow);
4751  position:relative;
4752  overflow:hidden;
4753  margin-bottom: 1rem;
4754}
4755
4756.pd-countdown-modern .pd-cd-icon{
4757  width:28px;height:28px;flex:0 0 28px;
4758  display:grid;place-items:center;
4759  background:rgba(255,255,255,.22);
4760  border:1px solid var(--cd-border);
4761  border-radius:10px;
4762  box-shadow: inset 0 1px 0 rgba(255,255,255,.35);
4763}
4764.pd-countdown-modern .pd-cd-icon svg{width:18px;height:18px;fill:#fff;opacity:.95}
4765
4766.pd-countdown-modern .pd-cd-body{display:flex;flex-direction:column;gap:.35rem;min-width:0}
4767
4768.pd-countdown-modern .pd-cd-label{
4769  font-size:.72rem;font-weight:700;letter-spacing:.4px;opacity:.95;line-height:1;
4770  text-shadow:0 1px 0 rgba(0,0,0,.08);
4771}
4772
4773.pd-countdown-modern .pd-cd-time{
4774  display:flex; align-items:end; gap:.35rem; flex-wrap:wrap;
4775  line-height:1;
4776}
4777
4778.pd-countdown-modern .unit{
4779  display:flex; flex-direction:column; align-items:center; gap:.18rem;
4780  min-width:58px;
4781}
4782.pd-countdown-modern .num{
4783  display:inline-flex; align-items:center; justify-content:center;
4784  min-width:58px; height:40px; padding:0 .4rem;
4785  background:var(--cd-box);
4786  color:var(--cd-box-text);
4787  border:1px solid rgba(15,23,42,.08);
4788  border-radius:10px;
4789  font: 800 1.05rem ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
4790  letter-spacing:.5px;
4791  box-shadow: 0 4px 14px -6px rgba(0,0,0,.4), inset 0 1px 0 rgba(255,255,255,.55);
4792}
4793.pd-countdown-modern .unit-label{
4794  font-size:.62rem; font-weight:700; opacity:.9; text-shadow:0 1px 0 rgba(255,255,255,.15);
4795}
4796
4797.pd-countdown-modern .colon{
4798  font-weight:900; font-size:1.05rem; opacity:.95; transform: translateY(-2px);
4799  animation: cdBlink 1.2s infinite steps(2, jump-none);
4800}
4801@keyframes cdBlink { 0%{opacity:1} 50%{opacity:.25} 100%{opacity:1} }
4802
4803.pd-countdown-modern .pd-cd-progress{
4804  display:none;
4805  position:relative;
4806  height:6px; border-radius:999px; overflow:hidden;
4807  background:rgba(255,255,255,.25);
4808  border:1px solid rgba(255,255,255,.28);
4809}
4810.pd-countdown-modern .pd-cd-progress .bar{
4811  height:100%; width:0%; background:linear-gradient(90deg,#22c55e,#a3e635);
4812  box-shadow: 0 2px 8px rgba(0,0,0,.25) inset;
4813  transition:width .35s ease;
4814}
4815
4816/* State saat hampir habis */
4817.pd-countdown-modern.danger{
4818  --cd-bg1:#f97316; /* orange-500 */
4819  --cd-bg2:#ef4444; /* red-500   */
4820}
4821
4822/* Responsive */
4823@media (max-width:640px){
4824  .pd-countdown-modern{
4825    padding:.55rem .65rem; border-radius:12px; gap:.6rem;
4826  }
4827  .pd-countdown-modern .unit{min-width:52px}
4828  .pd-countdown-modern .num{min-width:52px;height:36px;font-size:.98rem}
4829}
4830
4831/* === Modern Countdown polish (shine + compact) === */
4832.pd-countdown-modern{
4833  position:relative;
4834}
4835.pd-countdown-modern::after{
4836  content:"";
4837  position:absolute; inset:-1px;
4838  background: linear-gradient(120deg, rgba(255,255,255,.12), rgba(255,255,255,0) 35%),
4839              radial-gradient(120% 120% at 0% 0%, rgba(255,255,255,.08), rgba(255,255,255,0) 60%);
4840  border-radius: inherit;
4841  pointer-events:none;
4842  mix-blend-mode: screen;
4843  animation: sheen 4.5s ease-in-out infinite;
4844}
4845@keyframes sheen {
4846  0%   { opacity:.35; transform: translateX(-10%) }
4847  50%  { opacity:.15; transform: translateX(4%) }
4848  100% { opacity:.35; transform: translateX(-10%) }
4849}
4850
4851/* Mode compact (aktifkan dengan data-mode="compact" di elemen) */
4852.pd-countdown-modern[data-mode="compact"]{
4853  gap:.55rem; padding:.5rem .6rem; border-radius:12px;
4854}
4855.pd-countdown-modern[data-mode="compact"] .unit{
4856  min-width:46px;
4857}
4858.pd-countdown-modern[data-mode="compact"] .num{
4859  min-width:46px; height:34px; font-size:.92rem;
4860}
4861.pd-countdown-modern[data-mode="compact"] .pd-cd-label{
4862  font-size:.66rem;
4863}
4864.pd-countdown-modern[data-mode="compact"] .pd-cd-icon{
4865  width:24px; height:24px; border-radius:8px;
4866}
4867.pd-countdown-modern[data-mode="compact"] .pd-cd-progress{
4868  height:5px;
4869}
4870
4871/* ============================================================
4872   Sidebar: shared refinements for medium + tablet (641px – 1250px)
4873   Buy card = form & paylater on the same row.
4874   Alt-fintech logos compact (no over-stretching).
4875   ============================================================ */
4876@media (min-width:641px) and (max-width:1250px) {
4877  /* Buy card: put qty/buy buttons and paylater promo inline */
4878  .pd-buy-card {
4879    display: flex !important;
4880    flex-direction: row;
4881    flex-wrap: wrap;
4882    align-items: center;
4883    gap: .6rem .75rem;
4884  }
4885  .pd-buy-card .pd-buy-form { flex: 1 1 0; min-width: 0; }
4886  .pd-buy-card .paylater-promo {
4887    flex: 0 0 auto;
4888    align-self: center;
4889    margin: 0;
4890    max-width: 44%;
4891  }
4892
4893  /* Alt-fintech banner: tighter logos */
4894  .iab-logos-compact {
4895    justify-content: center;
4896    gap: 1.25rem;
4897  }
4898  .iab-logos-compact .logo-fin {
4899    width: auto;
4900    max-height: 28px;
4901    object-fit: contain;
4902  }
4903}
4904
4905/* ============================================================
4906   Tablet layout (641px – 880px)
4907   Restores 2-column grid + horizontal sidebar for mid-size screens.
4908   Overrides the generic ≤880px mobile-collapse rules above.
4909   ============================================================ */
4910@media (min-width:641px) and (max-width:880px) {
4911  /* 2-column grid: image-card | info-card */
4912  .pd-top-layout {
4913    grid-template-columns: minmax(260px, 40%) 1fr;
4914    gap: 1rem;
4915  }
4916
4917  /* Sidebar: full-width horizontal flex row below the two main columns */
4918  .pd-sidebar {
4919    grid-column: 1 / -1;
4920    flex-direction: row;
4921    flex-wrap: wrap;
4922    gap: .75rem;
4923    padding: 0;
4924  }
4925  /* Banner cards fill 2-per-row */
4926  .pd-side-card { flex: 1 1 calc(50% - .375rem); }
4927  /* Buy card: full row (tallest card – avoids white-space gap if shared with shorter banners) */
4928  .pd-buy-card  { display: block !important; flex: 0 0 100%; }
4929
4930  /* Hide installment simulator + alt-fintech banner on tablet (too much vertical space) */
4931  .pd-installment-card2,
4932  .inst-alt-banner--compact { display: none !important; }
4933
4934  /* Restore sticky add-to-cart header (hidden at ≤880px by default) */
4935  .pd-sticky-header { display: flex !important; }
4936
4937  /* Typography – tighten slightly to fit the narrower info column */
4938  .pd-price-current { font-size: 1.65rem; }
4939  .pd-title         { font-size: 1.1rem;  }
4940  .pd-info-card     { padding: 1.1rem 1.2rem 1.5rem; }
4941}
4942</style>
4943  <style data-inlined="product-grid.css">
4944/* Product Grid (Firebird) */
4945
4946.product-archive {
4947  max-width: 1320px;
4948  margin-inline: auto;
4949  padding: 0 0 3rem;
4950  /*font-family: system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,sans-serif;*/
4951}
4952
4953.pa-toolbar {
4954  display: flex;
4955  flex-wrap: wrap;
4956  gap: 1rem;
4957  justify-content: space-between;
4958  align-items: flex-start;
4959}
4960
4961.pa-title {
4962  font-size: 1.4rem;
4963  margin: 0;
4964  font-weight: 600;
4965  line-height: 1.2;
4966  color: #0f172a;
4967}
4968
4969.pa-left {
4970  display: flex;
4971  flex-wrap: wrap;
4972  gap: .75rem;
4973  align-items: center;
4974}
4975
4976.pa-filter-badge {
4977  background: #f1f5f9;
4978  border: 1px solid #e2e8f0;
4979  padding: .45rem .6rem;
4980  border-radius: 8px;
4981  font-size: .7rem;
4982  display: flex;
4983  align-items: center;
4984  gap: .4rem;
4985  line-height: 1;
4986  color: #475569;
4987}
4988.pa-filter-badge strong { color: #0f172a; }
4989.pa-filter-badge .clear {
4990  display:inline-block;
4991  text-decoration:none;
4992  background:#e2e8f0;
4993  color:#475569;
4994  font-weight:600;
4995  padding:0 .35rem .05rem;
4996  border-radius:4px;
4997  transition:.2s;
4998  font-size:.75rem;
4999  line-height:1;
5000}
5001.pa-filter-badge .clear:hover { background:#cbd5e1; color:#1e293b; }
5002
5003.pa-right {
5004  display:flex;
5005  gap:1rem;
5006  flex-wrap:wrap;
5007  align-items:flex-start;
5008  margin-bottom: 0.5rem;
5009}
5010
5011.pa-search {
5012  display:flex;
5013  gap:.5rem;
5014  align-items:center;
5015  background:#fff;
5016  border:1px solid #e2e8f0;
5017  padding:.4rem .5rem;
5018  border-radius:8px;
5019}
5020.pa-search input[type=text] {
5021  border:0;
5022  outline:none;
5023  font-size:.8rem;
5024  padding:.35rem .4rem;
5025  min-width:200px;
5026}
5027.pa-search button {
5028  background:#0d6efd;
5029  border:0;
5030  color:#fff;
5031  font-size:.7rem;
5032  padding:.45rem .75rem;
5033  border-radius:6px;
5034  cursor:pointer;
5035  font-weight:600;
5036  letter-spacing:.3px;
5037  transition:.25s;
5038}
5039.pa-search button:hover { background:#0b5dc4; }
5040
5041.pa-sort {
5042  display:flex;
5043  gap:.4rem;
5044  align-items:center;
5045  font-size:.7rem;
5046  background:#fff;
5047  border:1px solid #e2e8f0;
5048  padding:.4rem .55rem;
5049  border-radius:8px;
5050}
5051.pa-sort select {
5052  font-size:.75rem;
5053  padding:.35rem .5rem;
5054  border:1px solid #cbd5e1;
5055  border-radius:6px;
5056  background:#fff;
5057  outline:none;
5058}
5059
5060.pa-empty {
5061  border:1px dashed #cbd5e1;
5062  padding:3rem 1rem;
5063  text-align:center;
5064  border-radius:12px;
5065  background:#fff;
5066}
5067.pa-empty p {
5068  margin:0 0 1rem;
5069  font-size:.9rem;
5070  color:#475569;
5071}
5072.btn-reset {
5073  display:inline-block;
5074  background:#0d6efd;
5075  color:#fff;
5076  font-size:.7rem;
5077  padding:.55rem 1rem;
5078  border-radius:8px;
5079  text-decoration:none;
5080  font-weight:600;
5081  letter-spacing:.4px;
5082  transition:.25s;
5083}
5084.btn-reset:hover { background:#0a58c7; }
5085
5086/* Grid */
5087.pa-grid {
5088  display:grid;
5089  gap:1rem;
5090  grid-template-columns:repeat(auto-fill, minmax(180px,1fr));
5091  align-items:stretch;
5092}
5093
5094.pa-card {
5095  background:#fff;
5096  border:1px solid #e2e8f0;
5097  border-radius:12px;
5098  display:flex;
5099  flex-direction:column;
5100  text-decoration:none;
5101  color:inherit;
5102  position:relative;
5103  overflow:hidden;
5104  transition:.28s cubic-bezier(.4,0,.2,1);
5105  min-width:0;
5106  padding: 0;
5107}
5108.pa-card:hover {
5109  transform:translateY(-4px);
5110  box-shadow:0 8px 22px -8px rgba(0,0,0,.15);
5111  border-color:#dce2ea;
5112}
5113
5114.pa-card .thumb {
5115  aspect-ratio:1/1;
5116  background:#f1f5f9;
5117  display:flex;
5118  align-items:center;
5119  justify-content:center;
5120  overflow:hidden;
5121  position:relative;
5122}
5123.pa-card img {
5124  width:100%;
5125  height:100%;
5126  object-fit:cover;
5127  display:block;
5128}
5129.badge-disc {
5130  position:absolute;
5131  top:8px;
5132  left:8px;
5133  background:#dc2626;
5134  color:#fff;
5135  font-size:.6rem;
5136  padding:4px 6px 3px;
5137  border-radius:6px;
5138  font-weight:700;
5139  letter-spacing:.4px;
5140}
5141
5142.pa-card .body {
5143  padding:.65rem .7rem .8rem;
5144  display:flex;
5145  flex-direction:column;
5146  gap:.45rem;
5147  min-height:90px;
5148}
5149
5150.pa-card .title {
5151  font-size:.75rem;
5152  line-height:1.25;
5153  margin:0;
5154  font-weight:600;
5155  color:#1e293b;
5156  height:2.4em;
5157  overflow:hidden;
5158}
5159
5160.pa-card .prices {
5161  display:flex;
5162  flex-wrap:wrap;
5163  align-items:baseline;
5164  gap:.4rem;
5165}
5166.pa-card .prices .final {
5167  font-size:.83rem;
5168  font-weight:700;
5169  color:#0d6efd;
5170}
5171.pa-card .prices .ori {
5172  font-size:.63rem;
5173  text-decoration:line-through;
5174  color:#94a3b8;
5175  font-weight:500;
5176}
5177
5178.pa-card .cat {
5179  margin-top:auto;
5180  font-size:.55rem;
5181  letter-spacing:.6px;
5182  text-transform:uppercase;
5183  background:#f1f5f9;
5184  border:1px solid #e2e8f0;
5185  padding:.28rem .45rem .25rem;
5186  border-radius:20px;
5187  font-weight:600;
5188  color:#475569;
5189  align-self:flex-start;
5190}
5191.pa-card .meta-line {
5192    margin-top:4px;
5193    font-size:.65rem;
5194    color:#475569;
5195}
5196.pa-card .meta-line .sold {
5197    display:inline-block;
5198    background:#f1f5f9;
5199    padding:2px 6px 3px;
5200    border-radius:4px;
5201    font-weight:600;
5202    letter-spacing:.3px;
5203    color:#334155;
5204}
5205
5206/* Pagination */
5207.pa-pagination {
5208  margin-top:2rem;
5209  display:flex;
5210  flex-wrap:wrap;
5211  gap:.4rem;
5212  justify-content:center;
5213  font-size:.7rem;
5214  align-items:center;
5215}
5216.pa-pagination a,
5217.pa-pagination span {
5218  min-width:34px;
5219  height:34px;
5220  display:flex;
5221  align-items:center;
5222  justify-content:center;
5223  border-radius:8px;
5224  text-decoration:none;
5225  font-weight:600;
5226  padding:0 .6rem;
5227  box-sizing:border-box;
5228  border:1px solid #e2e8f0;
5229  background:#fff;
5230  color:#334155;
5231  transition:.25s;
5232}
5233.pa-pagination a:hover {
5234  background:#f1f5f9;
5235  color:#0f172a;
5236}
5237.pa-pagination .current {
5238  background:#0d6efd;
5239  color:#fff;
5240  border-color:#0d6efd;
5241}
5242.pa-pagination .gap {
5243  border:none;
5244  background:transparent;
5245  font-weight:400;
5246  color:#94a3b8;
5247}
5248
5249/* =============== Mobile Filter Bar (Tokopedia-like) =============== */
5250.pa-mobile-filterbar{display:none}
5251@media (max-width: 640px){
5252  /* sembunyikan tombol lama */
5253  .pa-btn-filter{display:none}
5254
5255  .pa-mobile-filterbar{
5256    display:block;
5257    position:sticky;
5258    top:0; /* sesuaikan jika ada header fixed di atas */
5259    z-index:5;
5260    background:#fff;
5261    border-bottom:1px solid #e2e8f0;
5262    padding:.4rem .5rem;
5263    margin:-.4rem -1rem .6rem; /* full-bleed di container mobile */
5264  }
5265  .pa-mobile-filterbar .mf-scroll{
5266    display:flex;align-items:center;gap:.5rem;
5267    overflow-x:auto;
5268    -webkit-overflow-scrolling:touch;
5269    scrollbar-width:none;
5270  }
5271  .pa-mobile-filterbar .mf-scroll::-webkit-scrollbar{display:none}
5272
5273  .mf-chip{
5274    flex:0 0 auto;
5275    background:#fff;
5276    color:#334155;
5277    text-decoration:none;
5278    border:1px solid #e2e8f0;
5279    border-radius:999px;
5280    padding:.44rem .7rem;
5281    font-size:.78rem;
5282    font-weight:600;
5283    letter-spacing:.2px;
5284    white-space:nowrap;
5285  }
5286  .mf-chip:active{transform:scale(.98)}
5287  .mf-chip.active{
5288    background:#eaf2ff;
5289    border-color:#0d6efd;
5290    color:#0d6efd;
5291  }
5292
5293  .mf-filter-btn{
5294    position:relative;
5295    flex:0 0 auto;
5296    width:38px;height:38px;
5297    border-radius:50%;
5298    border:1.5px solid #0d6efd;
5299    background:#fff;
5300    display:inline-flex;align-items:center;justify-content:center;
5301    color:#0d6efd;
5302    cursor:pointer;
5303  }
5304  .mf-filter-btn:active{transform:scale(.96)}
5305  .mf-filter-btn svg{width:18px;height:18px;display:block}
5306  .mf-filter-btn .mf-badge{
5307    position:absolute;top:0;right:-4px;
5308    min-width:18px;height:18px;border-radius:9px;
5309    background:#ef4444;color:#fff;
5310    font-size:.65rem;font-weight:700;
5311    display:flex;align-items:center;justify-content:center;
5312    padding:0 4px;line-height:1;
5313    box-shadow:0 2px 6px -2px rgba(239,68,68,.6);
5314  }
5315}
5316
5317/* Responsive */
5318@media (max-width: 640px) {
5319  .product-archive { padding: 0 1rem 3rem; }
5320  .pa-grid { grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); }
5321  .pa-search input[type=text]{ min-width:140px; }
5322  .pa-toolbar { flex-direction:column; align-items:stretch; margin-bottom: 0; display: none;}
5323  .pa-right { display: none; }
5324  .pa-sort { width: 100%; justify-content:center; }
5325  .pa-mobile-filterbar { display: flex !important; }
5326}
5327
5328.pa-mobile-filterbar {
5329  display: none;
5330  align-items: center;
5331  gap: 8px;
5332  padding: 8px 0 8px 12px;
5333  background: #fff;
5334  width: 100vw;
5335}
5336.pa-mobile-filterbar .mf-scroll {
5337  display: flex;
5338  gap: 8px;
5339  overflow-x: auto;
5340  -webkit-overflow-scrolling: touch;
5341  scrollbar-width: none; /* Firefox */
5342  flex: 1 1 auto; /* takes remaining width */
5343}
5344.pa-mobile-filterbar .mf-scroll::-webkit-scrollbar { display: none; } /* WebKit */
5345.pa-mobile-filterbar .mf-chip {
5346  flex: 0 0 auto; /* prevent shrinking, keep width based on content */
5347  white-space: nowrap;
5348}
5349.pa-mobile-filterbar .mf-filter-btn {
5350  flex: 0 0 auto; /* do not shrink */
5351  position: relative; /* keep in place, not inside scroller */
5352  display: inline-flex;
5353  align-items: center;
5354  gap: 6px;
5355}
5356
5357/* Optional: ensure on small screens the button stays visible with min width */
5358@media (max-width: 768px) {
5359  .pa-mobile-filterbar .mf-filter-btn {
5360    padding: 6px 10px;
5361  }
5362}
5363
5364/* Product Specification Chips */
5365.pa-card .spec-chips {
5366  font-size: .6rem;
5367  color: #64748b;
5368  font-weight: 500;
5369  letter-spacing: .2px;
5370  margin-top: 2px;
5371  margin-bottom: 4px;
5372  line-height: 1.2;
5373  opacity: 0.9;
5374  max-width: 100%;
5375  overflow: hidden;
5376  text-overflow: ellipsis;
5377  white-space: nowrap;
5378}
5379
5380.pa-card .spec-chips .spec-divider {
5381  color: #94a3b8;
5382  font-weight: 400;
5383  margin: 0 1px;
5384}
5385</style>
5386  <style data-inlined="site-notice.css">
5387/**
5388 * Site Notice Banner - Reusable Notification System
5389 * Dismissible top bar for announcements, holidays, maintenance, etc.
5390 */
5391
5392.holiday-notice {
5393    position: fixed;
5394    top: 0;
5395    left: 0;
5396    right: 0;
5397    z-index: 9999;
5398    background: linear-gradient(135deg, #d32f2f 0%, #f57c00 100%);
5399    border-bottom: 2px solid rgba(255, 255, 255, 0.2);
5400    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
5401    animation: slideDown 0.4s cubic-bezier(0.4, 0, 0.2, 1);
5402    transform-origin: top;
5403}
5404
5405.holiday-notice.hidden {
5406    animation: slideUp 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;
5407}
5408
5409@keyframes slideDown {
5410    from {
5411        transform: translateY(-100%);
5412        opacity: 0;
5413    }
5414    to {
5415        transform: translateY(0);
5416        opacity: 1;
5417    }
5418}
5419
5420@keyframes slideUp {
5421    from {
5422        transform: translateY(0);
5423        opacity: 1;
5424    }
5425    to {
5426        transform: translateY(-100%);
5427        opacity: 0;
5428    }
5429}
5430
5431.holiday-notice-container {
5432    max-width: 1400px;
5433    margin: 0 auto;
5434    padding: 12px 20px;
5435    display: flex;
5436    align-items: center;
5437    justify-content: space-between;
5438    gap: 16px;
5439}
5440
5441.holiday-notice-content {
5442    display: flex;
5443    align-items: center;
5444    gap: 12px;
5445    flex: 1;
5446}
5447
5448.holiday-notice-icon {
5449    font-size: 20px;
5450    line-height: 1;
5451    flex-shrink: 0;
5452}
5453
5454.holiday-notice-text {
5455    color: #ffffff;
5456    font-size: 14px;
5457    font-weight: 500;
5458    line-height: 1.5;
5459    margin: 0;
5460    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
5461}
5462
5463.holiday-notice-emoji {
5464    display: inline-block;
5465    animation: bounce 2s ease-in-out infinite;
5466}
5467
5468@keyframes bounce {
5469    0%, 100% {
5470        transform: translateY(0);
5471    }
5472    50% {
5473        transform: translateY(-4px);
5474    }
5475}
5476
5477.holiday-notice-close {
5478    background: rgba(255, 255, 255, 0.2);
5479    border: 1px solid rgba(255, 255, 255, 0.3);
5480    border-radius: 6px;
5481    padding: 6px;
5482    cursor: pointer;
5483    color: #ffffff;
5484    transition: all 0.2s ease;
5485    flex-shrink: 0;
5486    display: flex;
5487    align-items: center;
5488    justify-content: center;
5489    width: 32px;
5490    height: 32px;
5491}
5492
5493.holiday-notice-close:hover {
5494    background: rgba(255, 255, 255, 0.3);
5495    transform: scale(1.05);
5496}
5497
5498.holiday-notice-close:active {
5499    transform: scale(0.95);
5500}
5501
5502.holiday-notice-close:focus {
5503    outline: 2px solid rgba(255, 255, 255, 0.5);
5504    outline-offset: 2px;
5505}
5506
5507/* Body padding adjustment when banner is visible */
5508body.has-holiday-notice {
5509    padding-top: 52px;
5510}
5511
5512/* Tablet Styles */
5513@media (max-width: 1024px) {
5514    .holiday-notice-container {
5515        padding: 10px 16px;
5516    }
5517
5518    .holiday-notice-text {
5519        font-size: 13px;
5520    }
5521
5522    .holiday-notice-icon {
5523        font-size: 18px;
5524    }
5525
5526    body.has-holiday-notice {
5527        padding-top: 48px;
5528    }
5529}
5530
5531/* Mobile Styles */
5532@media (max-width: 640px) {
5533    .holiday-notice-container {
5534        padding: 10px 12px;
5535        gap: 8px;
5536    }
5537
5538    .holiday-notice-content {
5539        gap: 8px;
5540    }
5541
5542    .holiday-notice-text {
5543        font-size: 12px;
5544        line-height: 1.4;
5545    }
5546
5547    .holiday-notice-icon {
5548        font-size: 16px;
5549    }
5550
5551    .holiday-notice-close {
5552        width: 28px;
5553        height: 28px;
5554    }
5555
5556    .holiday-notice-close svg {
5557        width: 16px;
5558        height: 16px;
5559    }
5560
5561    body.has-holiday-notice {
5562        padding-top: 44px;
5563    }
5564}
5565
5566/* Ensure header doesn't get covered - removed z-index as it conflicts with dropdown stacking */
5567/* .header,
5568.navbar,
5569header {
5570    position: relative;
5571    z-index: 100;
5572} */
5573
5574</style>
5575        <style data-inlined="benchmark.css">
5576/**
5577 * Benchmark Section Styles
5578 * For product detail page benchmark display
5579 */
5580
5581.pd-benchmark-section {
5582    background: #fff;
5583    border-radius: 12px;
5584    padding: 24px;
5585    margin: 32px 0;
5586    box-shadow: 0 2px 8px rgba(0,0,0,0.06);
5587    border: 1px solid #e5e7eb;
5588}
5589
5590.benchmark-header {
5591    margin-bottom: 20px;
5592}
5593
5594.benchmark-title {
5595    font-size: 22px;
5596    font-weight: 700;
5597    color: #1a1a1a;
5598    margin: 0 0 12px 0;
5599    display: flex;
5600    align-items: center;
5601    gap: 8px;
5602}
5603
5604.benchmark-hint {
5605    font-size: 14px;
5606    color: #6b7280;
5607    margin: 0;
5608}
5609
5610/* Preview Badges */
5611.benchmark-preview-badges {
5612    display: flex;
5613    flex-wrap: wrap;
5614    gap: 10px;
5615    margin-top: 12px;
5616}
5617
5618.benchmark-badge {
5619    display: inline-flex;
5620    align-items: center;
5621    gap: 6px;
5622    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
5623    color: #fff;
5624    padding: 8px 16px;
5625    border-radius: 20px;
5626    font-size: 13px;
5627    font-weight: 500;
5628    box-shadow: 0 2px 6px rgba(102, 126, 234, 0.25);
5629    transition: all 0.2s ease;
5630}
5631
5632.benchmark-badge:hover {
5633    transform: translateY(-2px);
5634    box-shadow: 0 4px 12px rgba(102, 126, 234, 0.35);
5635}
5636
5637.benchmark-badge strong {
5638    font-weight: 600;
5639    opacity: 0.9;
5640}
5641
5642/* Load Button */
5643.btn-load-benchmark {
5644    display: flex;
5645    align-items: center;
5646    justify-content: center;
5647    gap: 10px;
5648    width: 100%;
5649    max-width: 420px;
5650    padding: 14px 24px;
5651    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
5652    color: #fff;
5653    border: none;
5654    border-radius: 8px;
5655    font-size: 15px;
5656    font-weight: 600;
5657    cursor: pointer;
5658    transition: all 0.3s ease;
5659    box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
5660    margin: 0 auto;
5661}
5662
5663.btn-load-benchmark:hover:not(:disabled) {
5664    transform: translateY(-2px);
5665    box-shadow: 0 6px 16px rgba(102, 126, 234, 0.4);
5666}
5667
5668.btn-load-benchmark:disabled {
5669    opacity: 0.6;
5670    cursor: not-allowed;
5671}
5672
5673.btn-load-benchmark .btn-icon {
5674    font-size: 20px;
5675}
5676
5677.btn-load-benchmark.loading .btn-icon {
5678    animation: spin 1s linear infinite;
5679}
5680
5681@keyframes spin {
5682    from { transform: rotate(0deg); }
5683    to { transform: rotate(360deg); }
5684}
5685
5686/* Benchmark Content */
5687.benchmark-content {
5688    margin-top: 24px;
5689    padding-top: 24px;
5690    border-top: 2px solid #f3f4f6;
5691}
5692
5693.benchmark-loading {
5694    text-align: center;
5695    padding: 40px 20px;
5696    color: #6b7280;
5697}
5698
5699.benchmark-loading-spinner {
5700    width: 48px;
5701    height: 48px;
5702    margin: 0 auto 16px;
5703    border: 4px solid #f3f4f6;
5704    border-top-color: #667eea;
5705    border-radius: 50%;
5706    animation: spin 1s linear infinite;
5707}
5708
5709.benchmark-error {
5710    text-align: center;
5711    padding: 32px 20px;
5712    color: #ef4444;
5713    background: #fee2e2;
5714    border-radius: 8px;
5715}
5716
5717/* Benchmark Data Display */
5718.benchmark-data-grid {
5719    display: grid;
5720    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
5721    gap: 16px;
5722    margin-bottom: 24px;
5723}
5724
5725.benchmark-card {
5726    background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%);
5727    border: 1px solid #e5e7eb;
5728    border-radius: 10px;
5729    padding: 18px;
5730    text-align: center;
5731    transition: all 0.2s ease;
5732}
5733
5734.benchmark-card:hover {
5735    transform: translateY(-3px);
5736    box-shadow: 0 6px 16px rgba(102, 126, 234, 0.15);
5737}
5738
5739.benchmark-card-label {
5740    font-size: 13px;
5741    color: #6b7280;
5742    font-weight: 500;
5743    margin-bottom: 8px;
5744    text-transform: uppercase;
5745    letter-spacing: 0.5px;
5746}
5747
5748.benchmark-card-value {
5749    font-size: 24px;
5750    font-weight: 700;
5751    color: #1a1a1a;
5752    line-height: 1.2;
5753}
5754
5755.benchmark-card-value.large {
5756    font-size: 28px;
5757}
5758
5759.benchmark-card-subtitle {
5760    font-size: 12px;
5761    color: #9ca3af;
5762    margin-top: 4px;
5763}
5764
5765/* Spec Details */
5766.benchmark-specs {
5767    background: #f9fafb;
5768    border-radius: 10px;
5769    padding: 20px;
5770    margin-top: 20px;
5771}
5772
5773.benchmark-specs-title {
5774    font-size: 16px;
5775    font-weight: 600;
5776    color: #1a1a1a;
5777    margin: 0 0 16px 0;
5778}
5779
5780.benchmark-spec-row {
5781    display: flex;
5782    justify-content: space-between;
5783    padding: 10px 0;
5784    border-bottom: 1px solid #e5e7eb;
5785}
5786
5787.benchmark-spec-row:last-child {
5788    border-bottom: none;
5789}
5790
5791.benchmark-spec-label {
5792    font-size: 14px;
5793    color: #6b7280;
5794    font-weight: 500;
5795}
5796
5797.benchmark-spec-value {
5798    font-size: 14px;
5799    color: #1a1a1a;
5800    font-weight: 600;
5801    text-align: right;
5802}
5803
5804/* Score Badges */
5805.benchmark-score-badge {
5806    display: inline-block;
5807    padding: 6px 14px;
5808    border-radius: 20px;
5809    font-size: 13px;
5810    font-weight: 600;
5811    margin-top: 8px;
5812}
5813
5814.benchmark-score-badge.excellent {
5815    background: #10b981;
5816    color: #fff;
5817}
5818
5819.benchmark-score-badge.good {
5820    background: #3b82f6;
5821    color: #fff;
5822}
5823
5824.benchmark-score-badge.average {
5825    background: #f59e0b;
5826    color: #fff;
5827}
5828
5829.benchmark-score-badge.poor {
5830    background: #ef4444;
5831    color: #fff;
5832}
5833
5834/* Attribution */
5835.benchmark-attribution {
5836    text-align: center;
5837    margin-top: 24px;
5838    padding-top: 20px;
5839    border-top: 1px solid #e5e7eb;
5840    font-size: 13px;
5841    color: #9ca3af;
5842}
5843
5844.benchmark-attribution a {
5845    color: #667eea;
5846    text-decoration: none;
5847    font-weight: 500;
5848}
5849
5850.benchmark-attribution a:hover {
5851    text-decoration: underline;
5852}
5853
5854/* Responsive */
5855@media (max-width: 768px) {
5856    .pd-benchmark-section {
5857        padding: 16px;
5858        margin: 20px 0;
5859    }
5860
5861    .benchmark-title {
5862        font-size: 18px;
5863    }
5864
5865    .benchmark-preview-badges {
5866        flex-direction: column;
5867        gap: 8px;
5868    }
5869
5870    .benchmark-badge {
5871        justify-content: center;
5872    }
5873
5874    .btn-load-benchmark {
5875        font-size: 14px;
5876        padding: 12px 20px;
5877    }
5878
5879    .benchmark-data-grid {
5880        grid-template-columns: 1fr 1fr;
5881        gap: 12px;
5882    }
5883
5884    .benchmark-card {
5885        padding: 14px;
5886    }
5887
5888    .benchmark-card-value {
5889        font-size: 20px;
5890    }
5891
5892    .benchmark-spec-row {
5893        flex-direction: column;
5894        gap: 4px;
5895    }
5896
5897    .benchmark-spec-value {
5898        text-align: left;
5899    }
5900}
5901
5902@media (max-width: 480px) {
5903    .benchmark-data-grid {
5904        grid-template-columns: 1fr;
5905    }
5906}
5907
5908</style>
5909    <style data-inlined="product-filters.css">
5910/* =========================================================
5911   Product Filters (Desktop + Mobile Drawer)
5912   Light Only + FIX:
5913   - Desktop: f-actions selalu tampil (static bottom)
5914   - Mobile: konten filter dapat discroll
5915   ========================================================= */
5916
5917/* ========== Layout wrapper (tidak mengubah grid produk) ========== */
5918.product-archive.with-filters .pa-layout {
5919  display:flex;
5920  gap:24px;
5921  align-items:flex-start;
5922}
5923.pa-main { flex:1; min-width:0; }
5924
5925/* =========================================================
5926   DESKTOP PANEL
5927   ========================================================= */
5928.pa-filters {
5929  width:300px;
5930  background:linear-gradient(145deg,#ffffff 0%,#f8fafc 55%,#f1f5f9 100%);
5931  border:1px solid #e2e8f0;
5932  padding:18px 18px 12px; /* bottom dikurangi karena f-actions punya padding sendiri */
5933  border-radius:18px;
5934  position:sticky;
5935  top:80px;
5936  height:calc(100vh - 100px); /* tinggi stabil untuk inner scroll */
5937  font-size:14px;
5938  box-shadow:0 1px 2px rgba(0,0,0,.04),0 4px 14px rgba(15,23,42,.08);
5939  backdrop-filter:blur(3px);
5940  -webkit-backdrop-filter:blur(3px);
5941  transition:box-shadow .3s, background .4s;
5942  display:flex;
5943  flex-direction:column;
5944  overflow:hidden; /* cegah double scroll, inner yang scroll */
5945}
5946.pa-filters::before {
5947  content:"";
5948  position:absolute;
5949  inset:0 auto 0 0;
5950  width:7px;
5951  border-radius:18px 0 0 18px;
5952  background:linear-gradient(180deg,#f59e0b 0%,#ffb53a 35%,#1d6ce0 100%);
5953}
5954.pa-filters.active { box-shadow:0 8px 32px -4px rgba(15,23,42,.35); }
5955
5956/* Scroll area (desktop/mode shared) */
5957.filters-scroll {
5958  flex:1;
5959  min-height:0;          /* wajib agar overflow terbaca dalam flex container */
5960  overflow:auto;
5961  padding-right:6px;
5962  margin-right:-6px;      /* kompensasi scrollbar */
5963  scrollbar-width:thin;
5964  overscroll-behavior:contain;
5965}
5966.filters-scroll::-webkit-scrollbar { width:8px; }
5967.filters-scroll::-webkit-scrollbar-thumb {
5968  background:#cbd5e1;
5969  border-radius:20px;
5970  border:2px solid #f1f5f9;
5971}
5972.filters-scroll::-webkit-scrollbar-thumb:hover { background:#94a3b8; }
5973
5974/* Heading */
5975.pa-filters h2 {
5976  margin:0 0 14px;
5977  font-size:20px;
5978  font-weight:700;
5979  letter-spacing:.4px;
5980  line-height:1.15;
5981  background:linear-gradient(90deg,#0f172a,#1d6ce0 60%,#0f172a);
5982  -webkit-background-clip:text;
5983  color:transparent;
5984  background-size:200% 100%;
5985  animation:titleSheen 8s linear infinite;
5986}
5987@keyframes titleSheen {
5988  0%{background-position:0 0}
5989  50%{background-position:100% 0}
5990  100%{background-position:0 0}
5991}
5992
5993/* Form groups */
5994.pa-filters .f-group { margin-bottom:18px; position:relative; }
5995.pa-filters label {
5996  display:block;
5997  font-weight:600;
5998  margin:0 0 6px;
5999  font-size:13px;
6000  color:#0f172a;
6001  letter-spacing:.3px;
6002}
6003
6004/* Inputs / selects */
6005.pa-filters input[type=text],
6006.pa-filters input[type=number],
6007.pa-filters select {
6008  width:100%;
6009  padding:7px 10px;
6010  border:1px solid #cbd5e1;
6011  border-radius:8px;
6012  font-size:13px;
6013  min-height:34px;
6014  background:#ffffff;
6015  box-sizing:border-box;
6016  color:#0f172a;
6017  transition:border-color .25s, box-shadow .25s, background .25s;
6018  -webkit-appearance:none;
6019  -moz-appearance:none;
6020  appearance:none;
6021}
6022.pa-filters select {
6023  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");
6024  background-repeat:no-repeat;
6025  background-position:right 10px center;
6026  background-size:12px;
6027  padding-right:32px;
6028}
6029.pa-filters input[type=text]:hover,
6030.pa-filters input[type=number]:hover,
6031.pa-filters select:hover { border-color:#94b5ec; }
6032.pa-filters input[type=text]:focus,
6033.pa-filters input[type=number]:focus,
6034.pa-filters select:focus {
6035  outline:none;
6036  border-color:#1d6ce0;
6037  box-shadow:0 0 0 3px rgba(29,108,224,.25);
6038  background:#f8fbff;
6039}
6040.pa-filters .range { display:flex; gap:10px; }
6041.pa-filters .range input { flex:1; }
6042.pa-filters .hint { font-size:11px; color:#64748b; margin-top:6px; }
6043
6044/* Spec blocks */
6045.f-specs { margin-top:4px; }
6046.spec-attr {
6047  margin:0 0 14px;
6048  padding:12px 12px 10px;
6049  border:1px solid #e2e8f0;
6050  border-radius:14px;
6051  background:linear-gradient(130deg,#ffffff 0%,#f7faff 85%);
6052  position:relative;
6053  transition:border-color .3s, box-shadow .3s, background .4s;
6054}
6055.spec-attr.collapsed { padding-bottom:8px; }
6056.spec-attr.has-selected {
6057  border-color:#1d6ce0;
6058  box-shadow:0 0 0 2px rgba(29,108,224,.15),0 2px 8px -2px rgba(29,108,224,.35);
6059  background:linear-gradient(135deg,#ffffff 0%,#e9f2ff 100%);
6060}
6061.spec-attr-title {
6062  font-weight:600;
6063  margin:0 0 8px;
6064  font-size:13px;
6065  display:flex;
6066  justify-content:space-between;
6067  align-items:center;
6068  cursor:pointer;
6069  color:#0f172a;
6070  user-select:none;
6071}
6072.spec-attr-title .chevron {
6073  font-size:10px;
6074  transition:transform .25s, opacity .25s;
6075  opacity:.65;
6076}
6077.spec-attr.collapsed .spec-attr-title .chevron {
6078  transform:rotate(-90deg);
6079  opacity:.5;
6080}
6081
6082/* Spec options */
6083.spec-attr-options { display:flex; flex-wrap:wrap; gap:6px; }
6084.spec-attr-options label {
6085  display:inline-flex;
6086  align-items:center;
6087  gap:4px;
6088  font-weight:500;
6089  border:1px solid #d4dde7;
6090  padding:6px 9px;
6091  border-radius:30px;
6092  cursor:pointer;
6093  background:#f3f6fa;
6094  font-size:12px;
6095  line-height:1.1;
6096  transition:background .25s, border-color .25s, color .25s, box-shadow .25s;
6097  position:relative;
6098}
6099.spec-attr-options label:hover { background:#e8f1ff; border-color:#b1cff3; }
6100.spec-attr-options input {
6101  margin:0;
6102  position:absolute;
6103  inset:0;
6104  opacity:0;
6105  cursor:pointer;
6106}
6107.spec-attr-options label:has(input:checked) {
6108  background:linear-gradient(135deg,#1d6ce0 0%,#155ab8 100%);
6109  border-color:#1d6ce0;
6110  color:#ffffff;
6111  box-shadow:0 2px 6px -1px rgba(29,108,224,.55);
6112}
6113.spec-attr-options label:has(input:checked)::after {
6114  content:"";
6115  position:absolute;
6116  right:6px;
6117  top:6px;
6118  width:6px;
6119  height:6px;
6120  background:#fff;
6121  border-radius:50%;
6122  box-shadow:0 0 0 4px rgba(255,255,255,.25),0 0 0 7px rgba(29,108,224,.5);
6123  animation:pulseDot 1.9s ease-out infinite;
6124}
6125@keyframes pulseDot {
6126  0%{transform:scale(.8);opacity:.9}
6127  60%{transform:scale(1.4);opacity:.15}
6128  100%{transform:scale(.8);opacity:.9}
6129}
6130
6131/* Badges */
6132.pa-filter-badges { margin:0 0 18px; display:flex; flex-wrap:wrap; gap:6px; }
6133.pa-filter-badge {
6134  display:inline-flex;
6135  gap:6px;
6136  align-items:center;
6137  background:linear-gradient(135deg,#eef4ff 0%,#e0edff 100%);
6138  border:1px solid #b4cff3;
6139  padding:5px 10px 5px 12px;
6140  border-radius:22px;
6141  margin:2px 4px 0 0;
6142  font-size:11px;
6143  font-weight:500;
6144  line-height:1;
6145  color:#0f172a;
6146  position:relative;
6147  overflow:hidden;
6148}
6149.pa-filter-badge::before {
6150  content:"";
6151  position:absolute;
6152  inset:0;
6153  background:linear-gradient(90deg,rgba(255,255,255,0),rgba(255,255,255,.35),rgba(255,255,255,0));
6154  transform:translateX(-100%);
6155  animation:badgeSheen 6s linear infinite;
6156}
6157@keyframes badgeSheen {
6158  0%{transform:translateX(-100%)}
6159  55%{transform:translateX(120%)}
6160  100%{transform:translateX(120%)}
6161}
6162.pa-filter-badge a.clear {
6163  text-decoration:none;
6164  color:#c02828;
6165  font-weight:700;
6166  padding:0 3px;
6167  display:inline-flex;
6168  align-items:center;
6169  justify-content:center;
6170  border-radius:50%;
6171  line-height:1;
6172  transition:background .25s, color .25s;
6173}
6174.pa-filter-badge a.clear:hover { background:#ffeded; color:#a40000; }
6175.pa-btn-filter { width: 100%;}
6176
6177/* =========================================================
6178   ACTIONS (DESKTOP) - STATIC BOTTOM
6179   ========================================================= */
6180.f-actions {
6181  margin-top:10px;
6182  display:flex;
6183  gap:12px;
6184  flex-wrap:wrap;
6185  padding:16px 0 4px;
6186  border-top:1px solid #e2e8f0;
6187  background:linear-gradient(180deg,rgba(255,255,255,0) 0%,#f1f5f9 70%);
6188  flex-shrink:0;
6189}
6190.f-actions button {
6191  background:linear-gradient(135deg,#1d6ce0 0%,#155ab8 100%);
6192  color:#fff;
6193  border:none;
6194  padding:10px 20px;
6195  border-radius:12px;
6196  cursor:pointer;
6197  font-size:14px;
6198  font-weight:600;
6199  letter-spacing:.3px;
6200  display:inline-flex;
6201  align-items:center;
6202  gap:6px;
6203  box-shadow:0 4px 14px -2px rgba(29,108,224,.45);
6204  transition:transform .25s, box-shadow .3s;
6205}
6206.f-actions button:hover { transform:translateY(-3px); box-shadow:0 8px 26px -4px rgba(29,108,224,.6); }
6207.f-actions button:active { transform:translateY(-1px) scale(.98); box-shadow:0 4px 14px -4px rgba(29,108,224,.55); }
6208.f-actions .reset {
6209  font-size:12px;
6210  text-decoration:none;
6211  color:#d92d2d;
6212  align-self:center;
6213  font-weight:600;
6214  padding:4px 6px;
6215  border-radius:8px;
6216  transition:background .25s, color .25s;
6217}
6218.f-actions .reset:hover { background:#ffe6e6; color:#b80000; text-decoration:underline; }
6219.pa-filters .close-drawer { display: none; }
6220
6221/* Form wrapper: flex container agar inner .filters-scroll bisa scroll di desktop */
6222.filter-form {
6223  flex: 1;
6224  min-height: 0;
6225  display: flex;
6226  flex-direction: column;
6227  overflow: hidden;
6228}
6229
6230/* =========================================================
6231   FILTER TOGGLE (MOBILE TRIGGER)
6232   ========================================================= */
6233.filter-toggle {
6234  display:none;
6235  margin:0 0 14px;
6236  background:linear-gradient(135deg,#1d6ce0 0%,#155ab8 100%);
6237  color:#fff;
6238  border:none;
6239  padding:11px 18px;
6240  font-size:14px;
6241  font-weight:600;
6242  border-radius:14px;
6243  cursor:pointer;
6244  letter-spacing:.4px;
6245  box-shadow:0 4px 14px -2px rgba(29,108,224,.55);
6246  position:relative;
6247  z-index:100;
6248  transition:box-shadow .3s, transform .25s;
6249}
6250.filter-toggle:hover { box-shadow:0 8px 26px -4px rgba(29,108,224,.6); transform:translateY(-2px); }
6251.filter-toggle:active { transform:translateY(0); box-shadow:0 4px 14px -4px rgba(29,108,224,.55); }
6252.filter-toggle .count-badge {
6253  display:inline-block;
6254  background:linear-gradient(135deg,#f59e0b 0%,#ffb43d 55%,#f59e0b 100%);
6255  color:#1d2b44;
6256  font-size:11px;
6257  font-weight:700;
6258  padding:3px 7px 4px;
6259  border-radius:30px;
6260  margin-left:8px;
6261  box-shadow:0 0 0 2px #ffffff,0 0 0 4px #1d6ce0;
6262}
6263
6264/* Overlay (mobile) */
6265.pa-filters-overlay { display:none; }
6266
6267/* =========================================================
6268   MOBILE DRAWER
6269   ========================================================= */
6270@media (max-width:768px){
6271  .product-archive.with-filters .pa-layout { flex-direction:column; gap:12px; }
6272  .filter-toggle { display:inline-flex; align-items:center; gap:6px; }
6273
6274  .pa-filters-overlay {
6275    position:fixed;
6276    inset:0;
6277    background:rgba(0,20,45,.55);
6278    z-index:998;
6279    opacity:0;
6280    transition:opacity .35s;
6281    display:block;
6282    pointer-events:none;
6283    backdrop-filter:blur(2px);
6284  }
6285  .pa-filters-overlay.active { opacity:1; pointer-events:auto; }
6286
6287  .pa-filters {
6288    position:fixed;
6289    top:auto;
6290    left:0; right:0; bottom:0;
6291    max-height:92vh;
6292    height:auto;
6293    width:100%;
6294    border-radius:30px 30px 0 0;
6295    border:1px solid #dbe4ef;
6296    padding:26px 22px 0;
6297    z-index:10000; /* di atas bottom-nav (9998) */
6298    transform:translateY(102%);
6299    transition:transform .55s cubic-bezier(.68,-0.35,.25,1);
6300    box-shadow:0 -4px 40px -6px rgba(15,23,42,.45);
6301    overflow:hidden; /* outer hidden, inner scroll */
6302  }
6303  .pa-filters.active { transform:translateY(0); }
6304
6305  /* Drag handle indicator */
6306  .pa-filters::after {
6307    content:"";
6308    display:block;
6309    position:absolute;
6310    top:10px;
6311    left:50%;
6312    transform:translateX(-50%);
6313    width:36px;
6314    height:4px;
6315    background:#cbd5e1;
6316    border-radius:4px;
6317  }
6318  body.filter-open { overflow:hidden; touch-action:none; }
6319
6320  /* Restore mobile-friendly input sizes (prevent iOS zoom on focus) */
6321  .pa-filters input[type=text],
6322  .pa-filters input[type=number],
6323  .pa-filters select {
6324    font-size:16px;
6325    min-height:44px;
6326    padding:10px 12px;
6327    border-radius:10px;
6328  }
6329  .pa-filters select { padding-right:32px; }
6330
6331  .filters-scroll {
6332    flex:1;
6333    overflow:auto;
6334    padding:0 0 120px; /* ruang di atas bar actions */
6335    margin:0;
6336    -webkit-overflow-scrolling:touch;
6337  }
6338
6339  .pa-filters h2 { font-size:19px; margin-bottom:10px; }
6340
6341  .pa-filters .close-drawer {
6342    display: block;
6343    position:absolute;
6344    right:14px; top:12px;
6345    background:linear-gradient(135deg,#dc2626 0%,#b91c1c 100%);
6346    color:#fff;
6347    padding:7px 14px;
6348    border:none;
6349    border-radius:10px;
6350    font-size:12px;
6351    font-weight:600;
6352    cursor:pointer;
6353    box-shadow:0 3px 10px -2px rgba(220,38,38,.55);
6354    transition:transform .25s, box-shadow .3s;
6355    z-index: 99;
6356  }
6357  .pa-filters .close-drawer:hover { transform:translateY(-2px); box-shadow:0 6px 18px -4px rgba(220,38,38,.6); }
6358
6359  .spec-attr { border-radius:18px; }
6360  .spec-attr-options label { font-size:11px; padding:5px 9px; }
6361
6362  /* Actions fixed di viewport bawah */
6363  .f-actions {
6364    position:fixed;
6365    left:0; right:0; bottom:0;
6366    margin:0;
6367    border-top:1px solid #d6dee8;
6368    padding:14px 16px calc(16px + env(safe-area-inset-bottom));
6369    background:#ffffffea;
6370    backdrop-filter:blur(6px);
6371    box-shadow:0 -4px 22px -6px rgba(15,23,42,.25);
6372    justify-content:space-between;
6373    z-index:10001; /* di atas pa-filters (10000) dan bottom-nav (9998) */
6374  }
6375
6376  /* Sembunyikan bottom-nav saat filter drawer terbuka */
6377  body.filter-open .bottom-nav {
6378    display: none !important;
6379  }
6380  .f-actions button {
6381    flex:1 1 auto;
6382    justify-content:center;
6383    padding:14px 14px;
6384    font-size:15px;
6385    border-radius:14px;
6386  }
6387  .f-actions .reset {
6388    order:-1;
6389    font-size:12px;
6390    padding:2px 8px;
6391  }
6392}
6393
6394/* Scrollbar (outer panel desktop only kept for compatibility) */
6395.pa-filters::-webkit-scrollbar { width:0; } /* disembunyikan karena inner yang scroll */
6396
6397/* Utilities */
6398.hidden { display:none !important; }
6399.gradient-text {
6400  background:linear-gradient(90deg,#1d6ce0,#155ab8,#1d6ce0);
6401  -webkit-background-clip:text;
6402  color:transparent;
6403  animation:titleSheen 10s linear infinite;
6404}
6405.glow-focus:focus {
6406  outline:none !important;
6407  box-shadow:0 0 0 3px rgba(29,108,224,.4) !important;
6408  border-color:#1d6ce0 !important;
6409}
6410
6411/* Simple fade (opsional) */
6412@keyframes fadeIn {
6413  from { opacity:0; transform:translateY(8px); }
6414  to { opacity:1; transform:translateY(0); }
6415}
6416</style>
6417  <style data-inlined="mega-menu-tiles.css">
6418/* Tile Mega Menu — Modern E-commerce Enhanced (2025) */
6419
6420/* Design Tokens */
6421:root {
6422  --mm2-bg: #ffffff;
6423  --mm2-surface: #ffffff;
6424  --mm2-surface-elevated: rgba(255,255,255,0.98);
6425  --mm2-muted: #f8fafc;
6426  --mm2-border: #e2e8f0;
6427  --mm2-border-strong: #cbd5e1;
6428  --mm2-accent: #6366f1;
6429  --mm2-accent-hover: #4f46e5;
6430  --mm2-accent-10: rgba(99,102,241,.10);
6431  --mm2-accent-20: rgba(99,102,241,.20);
6432  --mm2-fg: #0f172a;
6433  --mm2-fg-muted: #64748b;
6434  --mm2-fg-light: #94a3b8;
6435
6436  /* Enhanced Shadows - Multi-layer elevation */
6437  --mm2-shadow-sm: 0 2px 8px rgba(15,23,42,.06), 0 1px 2px rgba(15,23,42,.04);
6438  --mm2-shadow-md: 0 8px 24px rgba(15,23,42,.10), 0 2px 8px rgba(15,23,42,.06);
6439  --mm2-shadow-lg: 0 20px 60px rgba(15,23,42,.14), 0 8px 24px rgba(15,23,42,.08);
6440  --mm2-shadow-xl: 0 32px 80px rgba(15,23,42,.18), 0 12px 32px rgba(15,23,42,.10);
6441  --mm2-shadow-hover: 0 16px 40px rgba(15,23,42,.16), 0 6px 16px rgba(15,23,42,.08);
6442
6443  /* Border Radius */
6444  --mm2-radius-xl: 20px;
6445  --mm2-radius-lg: 16px;
6446  --mm2-radius-md: 12px;
6447  --mm2-radius-sm: 10px;
6448
6449  --mm2-z: 60;
6450
6451  /* Animation Timing */
6452  --mm2-transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
6453  --mm2-transition-base: 250ms cubic-bezier(0.4, 0, 0.2, 1);
6454  --mm2-transition-slow: 350ms cubic-bezier(0.4, 0, 0.2, 1);
6455}
6456
6457.navbar { position: relative; z-index: var(--mm2-z); }
6458
6459/* Trigger Button - Enhanced */
6460.mm2-trigger {
6461  display: inline-flex; align-items: center; gap: 8px;
6462  padding: 10px 14px; border-radius: var(--mm2-radius-md);
6463  border: 1.5px solid var(--mm2-border);
6464  background: var(--mm2-surface); color: var(--mm2-fg);
6465  cursor: pointer; user-select: none;
6466  transition: all var(--mm2-transition-fast);
6467  text-decoration: none;
6468  font-weight: 600;
6469  box-shadow: var(--mm2-shadow-sm);
6470}
6471.mm2-trigger:hover {
6472  border-color: var(--mm2-accent);
6473  background: var(--mm2-muted);
6474  box-shadow: var(--mm2-shadow-md);
6475  transform: translateY(-1px);
6476}
6477.mm2-trigger:active { transform: translateY(0); }
6478.mm2-trigger svg {max-width: unset !important; transition: transform var(--mm2-transition-fast); }
6479.mm2-trigger:hover svg { transform: scale(1.05); }
6480
6481/* Backdrop - Enhanced Glassmorphism */
6482.mm2-backdrop {
6483  position: fixed;
6484  inset: 0;
6485  top: 0;
6486  left: 0;
6487  right: 0;
6488  bottom: 0;
6489  background: rgba(15,23,42,.40);
6490  backdrop-filter: blur(8px) saturate(1.2);
6491  -webkit-backdrop-filter: blur(8px) saturate(1.2);
6492  opacity: 0;
6493  pointer-events: none;
6494  z-index: 499;
6495  transition: opacity var(--mm2-transition-base);
6496}
6497.mm2-open .mm2-backdrop { opacity: 1; pointer-events: auto; }
6498
6499/* Panel - Glassmorphism Enhanced */
6500.mm2-panel {
6501  position: fixed;
6502  left: 50%;
6503  top: 120px;
6504  transform: translateX(-50%) translateY(-16px);
6505  width: min(1200px, 95vw);
6506  background: var(--mm2-surface-elevated);
6507  backdrop-filter: blur(20px) saturate(1.8);
6508  -webkit-backdrop-filter: blur(20px) saturate(1.8);
6509  border: 1.5px solid var(--mm2-border-strong);
6510  border-radius: var(--mm2-radius-xl);
6511  box-shadow: var(--mm2-shadow-xl);
6512  display: none;
6513  overflow: hidden;
6514  opacity: 0;
6515  z-index: 500;
6516  transition: transform var(--mm2-transition-slow), opacity var(--mm2-transition-base);
6517}
6518.mm2-open .mm2-panel {
6519  display: block;
6520  opacity: 1;
6521  transform: translateX(-50%) translateY(0);
6522}
6523
6524/* View switcher */
6525.mm2-view { display: none; }
6526.mm2-open .mm2-view.is-active { display: block; }
6527
6528/* Top grid (level-1 tiles) - Enhanced */
6529.mm2-top {
6530  padding: 24px;
6531  max-height: 75vh; overflow: auto;
6532}
6533.mm2-grid {
6534  display: grid;
6535  grid-template-columns: repeat(4, minmax(0,1fr));
6536  gap: 14px;
6537}
6538@media (max-width: 1200px) { .mm2-grid { grid-template-columns: repeat(3, minmax(0,1fr)); } }
6539@media (max-width: 960px)  { .mm2-grid { grid-template-columns: repeat(2, minmax(0,1fr)); gap: 12px; } }
6540
6541/* Stagger animation for cards */
6542.mm2-open .mm2-card {
6543  animation: slideInUp 0.3s cubic-bezier(0.4, 0, 0.2, 1) backwards;
6544}
6545.mm2-open .mm2-card:nth-child(1)  { animation-delay: 0.02s; }
6546.mm2-open .mm2-card:nth-child(2)  { animation-delay: 0.04s; }
6547.mm2-open .mm2-card:nth-child(3)  { animation-delay: 0.06s; }
6548.mm2-open .mm2-card:nth-child(4)  { animation-delay: 0.08s; }
6549.mm2-open .mm2-card:nth-child(5)  { animation-delay: 0.10s; }
6550.mm2-open .mm2-card:nth-child(6)  { animation-delay: 0.12s; }
6551.mm2-open .mm2-card:nth-child(7)  { animation-delay: 0.14s; }
6552.mm2-open .mm2-card:nth-child(8)  { animation-delay: 0.16s; }
6553.mm2-open .mm2-card:nth-child(9)  { animation-delay: 0.18s; }
6554.mm2-open .mm2-card:nth-child(10) { animation-delay: 0.20s; }
6555.mm2-open .mm2-card:nth-child(11) { animation-delay: 0.22s; }
6556.mm2-open .mm2-card:nth-child(12) { animation-delay: 0.24s; }
6557.mm2-open .mm2-card:nth-child(n+13) { animation-delay: 0.26s; }
6558
6559@keyframes slideInUp {
6560  from {
6561    opacity: 0;
6562    transform: translateY(12px);
6563  }
6564  to {
6565    opacity: 1;
6566    transform: translateY(0);
6567  }
6568}
6569
6570.mm2-card {
6571  position: relative;
6572  display: flex; gap: 14px; align-items: center;
6573  padding: 16px; border-radius: var(--mm2-radius-md);
6574  background: var(--mm2-surface);
6575  border: 1.5px solid var(--mm2-border);
6576  text-decoration: none; color: var(--mm2-fg);
6577  transition: all var(--mm2-transition-fast);
6578  min-height: 72px;
6579  box-shadow: var(--mm2-shadow-sm);
6580}
6581.mm2-card:hover {
6582  border-color: var(--mm2-accent);
6583  background: linear-gradient(135deg, rgba(255,255,255,1), rgba(248,250,252,1));
6584  box-shadow: var(--mm2-shadow-hover);
6585  transform: translateY(-3px);
6586}
6587.mm2-card:active { transform: translateY(-1px); }
6588
6589/* Icon with category-specific gradients */
6590.mm2-card .mm2-ico {
6591  width: 48px; height: 48px; border-radius: 12px;
6592  display: inline-flex; align-items: center; justify-content: center;
6593  background: linear-gradient(135deg, #e0e7ff, #fff);
6594  border: 1.5px solid #c7d2fe;
6595  color: #4f46e5;
6596  flex-shrink: 0;
6597  transition: all var(--mm2-transition-fast);
6598  box-shadow: 0 2px 8px rgba(99,102,241,0.1);
6599}
6600.mm2-card:hover .mm2-ico {
6601  transform: scale(1.08) rotate(2deg);
6602  box-shadow: 0 4px 16px rgba(99,102,241,0.2);
6603}
6604.mm2-card .mm2-ico svg { transition: transform var(--mm2-transition-fast); }
6605.mm2-card:hover .mm2-ico svg { transform: scale(1.1); }
6606
6607.mm2-card .mm2-title {
6608  font-weight: 700;
6609  font-size: 15px;
6610  letter-spacing: -0.01em;
6611  line-height: 1.3;
6612}
6613.mm2-card .mm2-sub {
6614  color: var(--mm2-fg-muted);
6615  font-size: 12.5px;
6616  margin-top: 3px;
6617  display: flex;
6618  align-items: center;
6619  gap: 4px;
6620  white-space: nowrap;
6621  overflow: hidden;
6622  text-overflow: ellipsis;
6623}
6624.mm2-card .mm2-count {
6625  display: inline-flex;
6626  padding: 2px 8px;
6627  border-radius: 6px;
6628  background: var(--mm2-accent-10);
6629  color: var(--mm2-accent);
6630  font-weight: 600;
6631  font-size: 11px;
6632  margin-left: 4px;
6633}
6634
6635/* Detail view (level-2 + level-3) - Enhanced */
6636.mm2-detail {
6637  position: relative;
6638  padding: 16px 24px 24px;
6639  max-height: 70vh; overflow: auto;
6640}
6641.mm2-detail-header {
6642  display: flex; align-items: center; gap: 14px;
6643  padding: 10px 0 16px; border-bottom: 2px solid var(--mm2-muted); margin-bottom: 16px;
6644}
6645.mm2-back {
6646  display: inline-flex; align-items: center; justify-content: center;
6647  width: 40px; height: 40px; border-radius: 12px;
6648  border: 1.5px solid var(--mm2-border); background: #fff; color: var(--mm2-fg);
6649  cursor: pointer;
6650  transition: all var(--mm2-transition-fast);
6651  box-shadow: var(--mm2-shadow-sm);
6652}
6653.mm2-back:hover {
6654  border-color: var(--mm2-accent);
6655  background: var(--mm2-muted);
6656  transform: translateX(-3px);
6657  box-shadow: var(--mm2-shadow-md);
6658}
6659.mm2-detail-title {
6660  font-weight: 800;
6661  font-size: 18px;
6662  letter-spacing: -0.02em;
6663}
6664
6665/* Two-pane layout (desktop) - Enhanced */
6666.mm2-detail-layout {
6667  display: grid;
6668  grid-template-columns: 280px 1fr;
6669  gap: 20px;
6670}
6671
6672/* Left: subnav (level-2 list) - Enhanced */
6673.mm2-subnav {
6674  position: sticky; top: 8px;
6675  align-self: start;
6676  max-height: calc(70vh - 56px);
6677  overflow: auto;
6678  padding-right: 6px;
6679}
6680.mm2-subnav::-webkit-scrollbar { width: 8px; height: 8px; }
6681.mm2-subnav::-webkit-scrollbar-track { background: transparent; }
6682.mm2-subnav::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 8px; }
6683.mm2-subnav::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
6684
6685.mm2-subitem {
6686  display: flex; align-items: center; justify-content: flex-start; gap: 10px;
6687  width: 100%;
6688  padding: 12px 14px;
6689  border-radius: 12px;
6690  border: 1.5px solid transparent;
6691  background: transparent;
6692  color: var(--mm2-fg);
6693  cursor: pointer;
6694  text-align: left;
6695  transition: all var(--mm2-transition-fast);
6696  margin-bottom: 6px;
6697  font-weight: 600;
6698  font-size: 14px;
6699  position: relative;
6700}
6701.mm2-subitem::before {
6702  content: '';
6703  position: absolute;
6704  left: 0;
6705  top: 50%;
6706  transform: translateY(-50%);
6707  width: 3px;
6708  height: 0;
6709  background: var(--mm2-accent);
6710  border-radius: 0 2px 2px 0;
6711  transition: height var(--mm2-transition-fast);
6712}
6713.mm2-subitem:hover {
6714  background: var(--mm2-muted);
6715  color: var(--mm2-accent-hover);
6716}
6717.mm2-subitem.active {
6718  background: linear-gradient(135deg, rgba(99,102,241,0.08), rgba(99,102,241,0.04));
6719  border-color: var(--mm2-accent-20);
6720  color: var(--mm2-accent-hover);
6721  box-shadow: inset 0 0 0 1px rgba(99,102,241,0.1);
6722}
6723.mm2-subitem.active::before {
6724  height: 24px;
6725}
6726
6727/* Right: subcontent (level-3 grid) - Enhanced */
6728.mm2-subcontent { min-height: 240px; }
6729.mm2-links {
6730  display: grid; gap: 12px 14px;
6731  grid-template-columns: repeat(3, minmax(0,1fr));
6732}
6733@media (max-width: 1200px) { .mm2-links { grid-template-columns: repeat(2, minmax(0,1fr)); } }
6734
6735.mm2-link {
6736  display: flex; align-items: center; gap: 12px;
6737  padding: 14px; border: 1.5px solid var(--mm2-border); border-radius: var(--mm2-radius-md);
6738  background: var(--mm2-surface); color: var(--mm2-fg); text-decoration: none;
6739  min-height: 52px;
6740  font-weight: 500;
6741  font-size: 14px;
6742  transition: all var(--mm2-transition-fast);
6743  box-shadow: var(--mm2-shadow-sm);
6744  position: relative;
6745  justify-content: space-between;
6746}
6747.mm2-link:hover {
6748  background: var(--mm2-muted);
6749  border-color: var(--mm2-accent);
6750  transform: translateX(4px);
6751  box-shadow: var(--mm2-shadow-md);
6752}
6753.mm2-link-text {
6754  flex: 1;
6755}
6756
6757/* Product Thumbnails */
6758.mm2-thumbs {
6759  display: flex;
6760  gap: 6px;
6761  align-items: center;
6762  margin-left: auto;
6763  flex-shrink: 0;
6764}
6765.mm2-thumb {
6766  width: 36px;
6767  height: 36px;
6768  border-radius: 8px;
6769  object-fit: cover;
6770  border: 1.5px solid var(--mm2-border);
6771  box-shadow: 0 2px 6px rgba(0,0,0,0.08);
6772  transition: all var(--mm2-transition-fast);
6773  background: var(--mm2-muted);
6774}
6775.mm2-link:hover .mm2-thumb {
6776  border-color: var(--mm2-accent);
6777  box-shadow: 0 4px 10px rgba(99,102,241,0.15);
6778  transform: scale(1.08);
6779}
6780
6781/* Thumbnail Skeleton Loader */
6782.mm2-thumb-skeleton {
6783  width: 36px;
6784  height: 36px;
6785  border-radius: 8px;
6786  background: linear-gradient(90deg, #f1f5f9 25%, #e2e8f0 50%, #f1f5f9 75%);
6787  background-size: 200% 100%;
6788  animation: shimmer 1.5s infinite;
6789  border: 1.5px solid var(--mm2-border);
6790}
6791
6792.mm2-badge {
6793  font-size: 11px; padding: 3px 10px; margin-left: auto;
6794  border-radius: 12px; background: var(--mm2-accent-10); color: var(--mm2-accent);
6795  font-weight: 700;
6796  border: 1px solid var(--mm2-accent-20);
6797}
6798
6799/* "See all" highlight card - Enhanced */
6800.mm2-seeall {
6801  border: 2px dashed var(--mm2-accent);
6802  background: linear-gradient(135deg, rgba(99,102,241,.08), rgba(99,102,241,.02));
6803  font-weight: 700;
6804}
6805.mm2-seeall:hover {
6806  background: linear-gradient(135deg, rgba(99,102,241,.14), rgba(99,102,241,.06));
6807  border-style: solid;
6808  transform: translateX(6px) scale(1.02);
6809}
6810
6811/* Mobile panel styles - Enhanced with drag handle & better touch targets */
6812@media (max-width: 960px) {
6813  .mm2-panel {
6814    position: fixed;
6815    left: 0;
6816    right: 0;
6817    bottom: 0;
6818    top: auto;
6819    width: 100%;
6820    max-height: 88vh;
6821    transform: translateX(0) translateY(100%);
6822    border-radius: var(--mm2-radius-xl) var(--mm2-radius-xl) 0 0;
6823    border-bottom: none;
6824  }
6825  .mm2-open .mm2-panel {
6826    transform: translateX(0) translateY(0); z-index: 9999;
6827  }
6828
6829  /* Enhanced backdrop for mobile */
6830  .mm2-backdrop {
6831    backdrop-filter: blur(6px) saturate(1.2);
6832    -webkit-backdrop-filter: blur(6px) saturate(1.2);
6833  }
6834
6835  /* Mobile close button - Soft glassmorphism */
6836  .mm2-mobile-close {
6837    position: absolute;
6838    top: 4px;
6839    right: 8px;
6840    width: 28px;
6841    height: 28px;
6842    border-radius: 50%;
6843    border: 1px solid rgba(226, 232, 240, 0.6);
6844    background: rgba(255, 255, 255, 0.85);
6845    backdrop-filter: blur(12px) saturate(1.8);
6846    -webkit-backdrop-filter: blur(12px) saturate(1.8);
6847    color: var(--mm2-fg-muted);
6848    display: inline-flex;
6849    align-items: center;
6850    justify-content: center;
6851    cursor: pointer;
6852    transition: all var(--mm2-transition-fast);
6853    box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08);
6854    z-index: 20;
6855  }
6856  .mm2-mobile-close:hover {
6857    background: rgba(254, 242, 242, 0.95);
6858    border-color: rgba(252, 165, 165, 0.8);
6859    color: #dc2626;
6860    box-shadow: 0 4px 12px rgba(220, 38, 38, 0.15);
6861  }
6862  .mm2-mobile-close:active {
6863    transform: scale(0.92);
6864  }
6865
6866  /* Drag handle indicator */
6867  .mm2-top::before,
6868  .mm2-detail::before {
6869    content: '';
6870    position: sticky;
6871    top: 0;
6872    left: 50%;
6873    transform: translateX(-50%);
6874    width: 40px;
6875    height: 4px;
6876    background: var(--mm2-border-strong);
6877    border-radius: 999px;
6878    margin: 12px auto 20px;
6879    display: block;
6880    z-index: 10;
6881    transition: background var(--mm2-transition-fast);
6882  }
6883  .mm2-panel:active .mm2-top::before,
6884  .mm2-panel:active .mm2-detail::before {
6885    background: #94a3b8;
6886  }
6887
6888  .mm2-top {
6889    padding: 10px;
6890  }
6891
6892  .mm2-top, .mm2-detail {
6893    max-height: min(82vh, 720px);
6894    padding-top: 8px;
6895  }
6896
6897  /* Two-pane collapses: subnav becomes GRID (no horizontal scroll) */
6898  .mm2-detail-layout { display: block; }
6899  .mm2-subnav {
6900    position: static; max-height: none; overflow: visible;
6901    display: grid; gap: 10px; padding: 10px 4px 16px; margin-bottom: 16px;
6902    grid-template-columns: repeat(2, minmax(0,1fr));
6903    border-bottom: 2px solid var(--mm2-muted);
6904  }
6905  .mm2-subitem {
6906    margin: 0; width: 100%; text-align: center;
6907    border-radius: 14px;
6908    background: #fff; border: 1.5px solid var(--mm2-border);
6909    white-space: normal; line-height: 1.3; min-height: 48px;
6910    justify-content: center;
6911    padding: 12px 10px;
6912    font-size: 13.5px;
6913    box-shadow: var(--mm2-shadow-sm);
6914  }
6915  .mm2-subitem::before { display: none; }
6916  .mm2-subitem:hover {
6917    transform: scale(1.02);
6918  }
6919  .mm2-subitem.active {
6920    background: linear-gradient(135deg, var(--mm2-accent), var(--mm2-accent-hover));
6921    border-color: var(--mm2-accent-hover);
6922    color: white;
6923    box-shadow: 0 4px 12px rgba(99,102,241,0.3);
6924    font-weight: 700;
6925  }
6926
6927  /* Level-3 grid tetap 2 kolom dengan spacing lebih baik */
6928  .mm2-links {
6929    grid-template-columns: repeat(2, minmax(0,1fr));
6930    gap: 10px;
6931  }
6932
6933  /* Mobile: hide thumbnails untuk save space */
6934  .mm2-thumbs {
6935    display: none;
6936  }
6937
6938  /* Larger touch targets for mobile */
6939  .mm2-card {
6940    min-height: 80px;
6941    padding: 18px 14px;
6942  }
6943  .mm2-card .mm2-ico {
6944    width: 50px;
6945    height: 50px;
6946  }
6947  .mm2-card .mm2-title {
6948    font-size: 14px;
6949  }
6950  .mm2-card .mm2-sub {
6951    font-size: 11.5px;
6952  }
6953  .mm2-link {
6954    min-height: 56px;
6955    padding: 16px;
6956  }
6957  .mm2-back {
6958    width: 44px;
6959    height: 44px;
6960  }
6961
6962  /* Grid spacing */
6963  .mm2-grid {
6964    gap: 10px;
6965  }
6966}
6967</style>
6968  <style data-inlined="product-compare.css">
6969/* Product Compare Feature Styles */
6970
6971/* Compare Button in Product Detail - Modern Chip Style */
6972.pd-compare-section {
6973  margin: 0.5rem 0;
6974  display: flex;
6975  align-items: center;
6976  gap: 0.5rem;
6977}
6978
6979.pd-compare-btn {
6980  display: inline-flex;
6981  align-items: center;
6982  gap: 0.4rem;
6983  padding: 0.5rem 0.85rem;
6984  border: 1px solid #cbd5e1;
6985  border-radius: 20px;
6986  background: #fff;
6987  color: #64748b;
6988  font-weight: 600;
6989  font-size: 0.8rem;
6990  cursor: pointer;
6991  transition: all 0.2s ease;
6992  position: relative;
6993  white-space: nowrap;
6994}
6995
6996.pd-compare-btn:hover {
6997  border-color: #3b82f6;
6998  color: #3b82f6;
6999  background: #f0f9ff;
7000  transform: translateY(-1px);
7001  box-shadow: 0 2px 6px rgba(59, 130, 246, 0.15);
7002}
7003
7004.pd-compare-btn.added {
7005  border-color: #16a34a;
7006  color: #16a34a;
7007  background: #f0fdf4;
7008}
7009
7010.pd-compare-btn .compare-remove {
7011  display: none;
7012}
7013
7014.pd-compare-btn.added .compare-text {
7015  display: none;
7016}
7017
7018.pd-compare-btn.added .compare-remove {
7019  display: flex !important;
7020  align-items: center;
7021  gap: 0.25rem;
7022  font-weight: 600;
7023  font-size: 0.9rem;
7024  color: #dc2626 !important;
7025  opacity: 1 !important;
7026  visibility: visible !important;
7027  z-index: 1;
7028  position: relative;
7029}
7030
7031.pd-compare-btn.added .compare-remove[hidden] {
7032  display: flex !important;
7033}
7034
7035/* Force visibility for added state */
7036.pd-compare-btn.added .compare-remove {
7037  display: flex !important;
7038}
7039
7040[hidden].compare-remove {
7041  display: none !important;
7042}
7043
7044.pd-compare-btn.added [hidden].compare-remove {
7045  display: flex !important;
7046}
7047
7048/* Ensure the added state text is always visible */
7049.pd-compare-btn.added {
7050  border-color: #dc2626 !important;
7051  color: #dc2626 !important;
7052  background: #fef2f2 !important;
7053}
7054
7055.pd-compare-btn.added .compare-remove svg {
7056  color: #dc2626 !important;
7057}
7058
7059.pd-compare-btn.added:hover {
7060  border-color: #15803d;
7061  color: #15803d;
7062  background: #dcfce7;
7063  transform: translateY(-1px);
7064  box-shadow: 0 2px 6px rgba(22, 163, 74, 0.2);
7065}
7066
7067.pd-compare-btn .compare-icon {
7068  width: 16px;
7069  height: 16px;
7070  flex-shrink: 0;
7071}
7072
7073.pd-compare-info {
7074  display: inline-flex;
7075  align-items: center;
7076}
7077
7078.compare-note {
7079  color: #94a3b8;
7080  font-size: 0.7rem;
7081  font-weight: 500;
7082  transition: color 0.2s ease;
7083}
7084
7085.pd-compare-btn.added + .pd-compare-info .compare-note {
7086  color: #16a34a;
7087  font-weight: 600;
7088}
7089
7090/* Compare Badge in Navigation */
7091.compare-indicator {
7092  position: relative;
7093}
7094
7095.compare-indicator .badge {
7096  position: absolute;
7097  top: -8px;
7098  right: -8px;
7099  min-width: 18px;
7100  height: 18px;
7101  padding: 0 5px;
7102  background: #ef4444;
7103  color: white;
7104  border-radius: 9px;
7105  font-size: 11px;
7106
7107
7108/* Floating Compare Badge (Sticky) */
7109.compare-floating-badge {
7110  position: fixed;
7111  right: 16px;
7112  bottom: calc(20px + 60px); /* Above scroll-to-top button */
7113  width: auto;
7114  min-width: 56px;
7115  height: 56px;
7116  padding: 0 16px;
7117  border-radius: 28px;
7118  background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
7119  color: #fff;
7120  box-shadow: 0 8px 24px -8px rgba(59, 130, 246, 0.5);
7121  cursor: pointer;
7122  z-index: 1104;
7123  opacity: 0;
7124  transform: translateY(8px) scale(0.9);
7125  transition: opacity 0.3s ease, transform 0.3s ease, box-shadow 0.2s ease;
7126  display: flex;
7127  align-items: center;
7128  justify-content: center;
7129  gap: 8px;
7130  font-weight: 600;
7131  font-size: 0.85rem;
7132  pointer-events: none;
7133}
7134
7135.compare-floating-badge.visible {
7136  opacity: 1;
7137  transform: translateY(0) scale(1);
7138  pointer-events: auto;
7139}
7140
7141.compare-floating-badge:hover {
7142  box-shadow: 0 12px 32px -10px rgba(59, 130, 246, 0.6);
7143  transform: translateY(-2px) scale(1.02);
7144}
7145
7146.compare-floating-badge:active {
7147  transform: translateY(0) scale(0.98);
7148}
7149
7150.compare-floating-badge .cfb-icon {
7151  width: 20px;
7152  height: 20px;
7153  flex-shrink: 0;
7154}
7155
7156.compare-floating-badge .cfb-count {
7157  font-size: 0.9rem;
7158  font-weight: 700;
7159  min-width: 20px;
7160  text-align: center;
7161  background: rgba(255, 255, 255, 0.25);
7162  padding: 2px 8px;
7163  border-radius: 12px;
7164  line-height: 1;
7165  display: flex;
7166  align-items: center;
7167  justify-content: center;
7168  height: 24px;
7169}
7170
7171.compare-floating-badge .cfb-text {
7172  display: none;
7173}
7174
7175@media (min-width: 641px) {
7176  .compare-floating-badge .cfb-text {
7177    display: block;
7178    white-space: nowrap;
7179  }
7180}
7181
7182@media (max-width: 640px) {
7183  .compare-floating-badge {
7184    right: 14px;
7185    bottom: calc(var(--scroll-top-bottom, calc(68px + 18px + env(safe-area-inset-bottom))) + 60px);
7186    min-width: 48px;
7187    height: 48px;
7188    padding: 0 12px;
7189    border-radius: 24px;
7190  }
7191
7192  .compare-floating-badge .cfb-icon {
7193    width: 18px;
7194    height: 18px;
7195  }
7196
7197  .compare-floating-badge .cfb-count {
7198    font-size: 0.8rem;
7199    padding: 2px 6px;
7200    height: 20px;
7201  }
7202} font-weight: 600;
7203  display: flex;
7204  align-items: center;
7205  justify-content: center;
7206  transform: scale(0);
7207  transition: transform 0.2s ease;
7208}
7209
7210.compare-indicator .badge.active {
7211  transform: scale(1);
7212}
7213
7214/* Compare Sidebar */
7215.compare-sidebar {
7216  position: fixed;
7217  top: 0;
7218  right: -400px;
7219  width: 400px;
7220  height: 100vh;
7221  background: #ffffff;
7222  box-shadow: -4px 0 20px rgba(0, 0, 0, 0.1);
7223  z-index: 9999;
7224  display: flex;
7225  flex-direction: column;
7226  transition: right 0.3s ease;
7227}
7228
7229.compare-sidebar.open {
7230  right: 0;
7231}
7232
7233.compare-overlay {
7234  position: fixed;
7235  top: 0;
7236  left: 0;
7237  width: 100%;
7238  height: 100%;
7239  background: rgba(0, 0, 0, 0.5);
7240  z-index: 9998;
7241  opacity: 0;
7242  visibility: hidden;
7243  transition: all 0.3s ease;
7244}
7245
7246.compare-overlay.active {
7247  opacity: 1;
7248  visibility: visible;
7249}
7250
7251.compare-header {
7252  display: flex;
7253  align-items: center;
7254  justify-content: space-between;
7255  padding: 1.25rem;
7256  border-bottom: 1px solid #e2e8f0;
7257  background: #f8fafc;
7258}
7259
7260.compare-title {
7261  display: flex;
7262  align-items: center;
7263  gap: 0.5rem;
7264  font-size: 1rem;
7265  font-weight: 600;
7266  color: #1e293b;
7267  margin: 0;
7268}
7269
7270.compare-count {
7271  color: #64748b;
7272  font-weight: 400;
7273}
7274
7275.compare-close {
7276  display: flex;
7277  align-items: center;
7278  justify-content: center;
7279  width: 32px;
7280  height: 32px;
7281  border: none;
7282  border-radius: 6px;
7283  background: transparent;
7284  color: #64748b;
7285  cursor: pointer;
7286  transition: all 0.2s ease;
7287}
7288
7289.compare-close:hover {
7290  background: #e2e8f0;
7291  color: #374151;
7292}
7293
7294.compare-content {
7295  flex: 1;
7296  overflow-y: auto;
7297  padding: 1rem;
7298}
7299
7300.compare-empty {
7301  display: flex;
7302  flex-direction: column;
7303  align-items: center;
7304  justify-content: center;
7305  height: 100%;
7306  text-align: center;
7307  padding: 2rem 1rem;
7308}
7309
7310.compare-empty h5 {
7311  margin: 1rem 0 0.5rem;
7312  color: #374151;
7313}
7314
7315.compare-empty p {
7316  color: #64748b;
7317  font-size: 0.875rem;
7318  line-height: 1.5;
7319}
7320
7321.compare-list {
7322  display: flex;
7323  flex-direction: column;
7324  gap: 0.75rem;
7325}
7326
7327.compare-item {
7328  display: flex;
7329  align-items: center;
7330  gap: 0.75rem;
7331  padding: 0.75rem;
7332  border: 1px solid #e2e8f0;
7333  border-radius: 8px;
7334  background: #ffffff;
7335  transition: all 0.2s ease;
7336}
7337
7338.compare-item:hover {
7339  border-color: #cbd5e1;
7340  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
7341}
7342
7343.compare-item-image {
7344  flex-shrink: 0;
7345  width: 48px;
7346  height: 48px;
7347  border-radius: 6px;
7348  overflow: hidden;
7349  border: 1px solid #e2e8f0;
7350}
7351
7352.compare-item-image img {
7353  width: 100%;
7354  height: 100%;
7355  object-fit: cover;
7356}
7357
7358.compare-item-info {
7359  flex: 1;
7360  min-width: 0;
7361}
7362
7363.compare-item-name {
7364  font-weight: 500;
7365  font-size: 0.875rem;
7366  color: #1e293b;
7367  margin-bottom: 0.25rem;
7368  line-height: 1.3;
7369  display: -webkit-box;
7370  -webkit-line-clamp: 2;
7371  -webkit-box-orient: vertical;
7372  overflow: hidden;
7373}
7374
7375.compare-item-price {
7376  font-weight: 600;
7377  font-size: 0.8rem;
7378  color: #059669;
7379}
7380
7381.compare-item-remove {
7382  flex-shrink: 0;
7383  display: flex;
7384  align-items: center;
7385  justify-content: center;
7386  width: 28px;
7387  height: 28px;
7388  border: none;
7389  border-radius: 4px;
7390  background: transparent;
7391  color: #dc2626;
7392  cursor: pointer;
7393  transition: all 0.2s ease;
7394}
7395
7396.compare-item-remove:hover {
7397  background: #fef2f2;
7398  color: #991b1b;
7399}
7400
7401.compare-actions {
7402  padding: 1rem;
7403  border-top: 1px solid #e2e8f0;
7404  display: flex;
7405  gap: 0.75rem;
7406}
7407
7408.compare-actions .btn {
7409  flex: 1;
7410  padding: 0.75rem 1rem;
7411  border-radius: 6px;
7412  font-weight: 600;
7413  font-size: 0.875rem;
7414  display: flex;
7415  align-items: center;
7416  justify-content: center;
7417  gap: 0.5rem;
7418  cursor: pointer;
7419  transition: all 0.2s ease;
7420}
7421
7422.btn-outline-danger {
7423  border: 1px solid #dc2626;
7424  background: transparent;
7425  color: #dc2626;
7426}
7427
7428.btn-outline-danger:hover {
7429  background: #dc2626;
7430  color: white;
7431}
7432
7433.btn-primary {
7434  border: 1px solid #3b82f6;
7435  background: #3b82f6;
7436  color: white;
7437}
7438
7439.btn-primary:hover {
7440  background: #2563eb;
7441  border-color: #2563eb;
7442}
7443
7444.btn-primary:disabled {
7445  opacity: 0.5;
7446  cursor: not-allowed;
7447}
7448
7449.btn-primary:disabled:hover {
7450  background: #3b82f6;
7451  border-color: #3b82f6;
7452}
7453
7454/* AI Comparison Modal */
7455.ai-compare-modal {
7456  position: fixed;
7457  top: 0;
7458  left: 0;
7459  width: 100%;
7460  height: 100%;
7461  z-index: 10000;
7462  display: none;
7463  align-items: center;
7464  justify-content: center;
7465}
7466
7467.ai-compare-modal.open {
7468  display: flex;
7469}
7470
7471.ai-compare-modal .modal-backdrop {
7472  position: absolute;
7473  top: 0;
7474  left: 0;
7475  width: 100%;
7476  height: 100%;
7477  background: rgba(0, 0, 0, 0.6);
7478  backdrop-filter: blur(4px);
7479}
7480
7481.modal-container {
7482  position: relative;
7483  width: 90%;
7484  max-width: 1000px;
7485  max-height: 90vh;
7486  background: #ffffff;
7487  border-radius: 12px;
7488  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
7489  display: flex;
7490  flex-direction: column;
7491  overflow: hidden;
7492}
7493
7494.modal-header {
7495  display: flex;
7496  align-items: center;
7497  justify-content: space-between;
7498  padding: 1.5rem 2rem;
7499  border-bottom: 1px solid #e2e8f0;
7500  background: #f8fafc;
7501}
7502
7503.modal-title {
7504  display: flex;
7505  align-items: center;
7506  gap: 0.75rem;
7507  font-size: 1.25rem;
7508  font-weight: 600;
7509  color: #1e293b;
7510  margin: 0;
7511}
7512
7513.modal-close {
7514  display: flex;
7515  align-items: center;
7516  justify-content: center;
7517  width: 36px;
7518  height: 36px;
7519  border: none;
7520  border-radius: 8px;
7521  background: transparent;
7522  color: #64748b;
7523  cursor: pointer;
7524  transition: all 0.2s ease;
7525}
7526
7527.modal-close:hover {
7528  background: #e2e8f0;
7529  color: #374151;
7530}
7531
7532.modal-content {
7533  flex: 1;
7534  overflow-y: auto;
7535  padding: 2rem;
7536}
7537
7538/* AI Loading State */
7539.ai-loading {
7540  display: flex;
7541  flex-direction: column;
7542  align-items: center;
7543  justify-content: center;
7544  padding: 3rem 2rem;
7545  text-align: center;
7546}
7547
7548.loading-animation {
7549  margin-bottom: 1rem;
7550}
7551
7552.typing-indicator {
7553  display: flex;
7554  align-items: center;
7555  justify-content: center;
7556  gap: 4px;
7557  margin-bottom: 1rem;
7558}
7559
7560.typing-indicator span {
7561  width: 8px;
7562  height: 8px;
7563  background: #3b82f6;
7564  border-radius: 50%;
7565  animation: typing 1.4s infinite ease-in-out;
7566}
7567
7568.typing-indicator span:nth-child(2) {
7569  animation-delay: 0.2s;
7570}
7571
7572.typing-indicator span:nth-child(3) {
7573  animation-delay: 0.4s;
7574}
7575
7576@keyframes typing {
7577  0%, 80%, 100% {
7578    opacity: 0.3;
7579    transform: scale(0.8);
7580  }
7581  40% {
7582    opacity: 1;
7583    transform: scale(1);
7584  }
7585}
7586
7587.ai-loading p {
7588  font-weight: 500;
7589  color: #1e293b;
7590  margin-bottom: 0.5rem;
7591}
7592
7593.ai-loading small {
7594  color: #64748b;
7595}
7596
7597/* AI Error State */
7598.ai-error {
7599  display: flex;
7600  flex-direction: column;
7601  align-items: center;
7602  justify-content: center;
7603  padding: 3rem 2rem;
7604  text-align: center;
7605}
7606
7607.ai-error h4 {
7608  margin: 1rem 0 0.5rem;
7609  color: #dc2626;
7610}
7611
7612.ai-error p {
7613  color: #64748b;
7614  margin-bottom: 1.5rem;
7615}
7616
7617/* AI Results */
7618.ai-results > div {
7619  margin-bottom: 2rem;
7620}
7621
7622.ai-summary-card,
7623.ai-comparison-table,
7624.ai-detailed-analysis,
7625.ai-recommendations,
7626.ai-tips {
7627  background: #f8fafc;
7628  border: 1px solid #e2e8f0;
7629  border-radius: 12px;
7630  padding: 1.5rem;
7631}
7632
7633.ai-results h4 {
7634  display: flex;
7635  align-items: center;
7636  gap: 0.5rem;
7637  font-size: 1.1rem;
7638  font-weight: 600;
7639  color: #1e293b;
7640  margin-bottom: 1rem;
7641}
7642
7643.summary-text {
7644  font-size: 1rem;
7645  line-height: 1.6;
7646  color: #374151;
7647  margin-bottom: 1rem;
7648}
7649
7650.winner-badge {
7651  padding: 1rem;
7652  background: linear-gradient(135deg, #10b981 0%, #059669 100%);
7653  color: white;
7654  border-radius: 8px;
7655}
7656
7657.winner-badge strong {
7658  display: block;
7659  margin-bottom: 0.25rem;
7660}
7661
7662.winner-badge small {
7663  opacity: 0.9;
7664  font-size: 0.875rem;
7665}
7666
7667/* Comparison Table */
7668.comparison-grid {
7669  display: grid;
7670  gap: 1rem;
7671  margin-top: 1rem;
7672}
7673
7674.comparison-category {
7675  background: white;
7676  border: 1px solid #e2e8f0;
7677  border-radius: 8px;
7678  overflow: hidden;
7679}
7680
7681.category-header {
7682  background: #f1f5f9;
7683  padding: 0.75rem 1rem;
7684  font-weight: 600;
7685  color: #1e293b;
7686  border-bottom: 1px solid #e2e8f0;
7687}
7688
7689.category-products {
7690  display: grid;
7691  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
7692  gap: 1px;
7693  background: #e2e8f0;
7694}
7695
7696.product-score {
7697  background: white;
7698  padding: 1rem;
7699  text-align: center;
7700}
7701
7702.score-value {
7703  display: inline-flex;
7704  align-items: center;
7705  gap: 0.25rem;
7706  margin-bottom: 0.5rem;
7707}
7708
7709.score-stars {
7710  color: #fbbf24;
7711}
7712
7713.score-note {
7714  font-size: 0.8rem;
7715  color: #64748b;
7716  line-height: 1.3;
7717}
7718
7719/* Product name header in comparison table */
7720.product-name-header {
7721  margin-bottom: 0.5rem;
7722}
7723
7724.product-name-header strong {
7725  font-size: .8rem; /* h5 size (18px) */
7726  font-weight: 600;
7727  color: #1e293b;
7728  line-height: 1.3;
7729  display: block;
7730  word-break: break-word;
7731}
7732
7733.product-price {
7734  font-size: 0.8rem;
7735  font-weight: 600;
7736  color: #059669;
7737}
7738
7739/* Detailed Analysis */
7740.products-analysis {
7741  display: grid;
7742  gap: 1rem;
7743  margin-top: 1rem;
7744}
7745
7746.product-analysis {
7747  background: white;
7748  border: 1px solid #e2e8f0;
7749  border-radius: 8px;
7750  padding: 1rem;
7751}
7752
7753.product-analysis h5 {
7754  color: #1e293b;
7755  margin-bottom: 0.75rem;
7756  font-weight: 600;
7757}
7758
7759.analysis-section {
7760  margin-bottom: 0.75rem;
7761}
7762
7763.analysis-section:last-child {
7764  margin-bottom: 0;
7765}
7766
7767.analysis-section h6 {
7768  font-size: 0.875rem;
7769  font-weight: 600;
7770  margin-bottom: 0.5rem;
7771}
7772
7773.analysis-section h6.strengths {
7774  color: #059669;
7775}
7776
7777.analysis-section h6.weaknesses {
7778  color: #dc2626;
7779}
7780
7781.analysis-section h6.best-for {
7782  color: #7c3aed;
7783}
7784
7785.analysis-section ul {
7786  list-style: none;
7787  padding: 0;
7788  margin: 0;
7789}
7790
7791.analysis-section li {
7792  display: flex;
7793  align-items: flex-start;
7794  gap: 0.5rem;
7795  margin-bottom: 0.25rem;
7796  font-size: 0.875rem;
7797  line-height: 1.4;
7798}
7799
7800.analysis-section li:before {
7801  content: "•";
7802  color: #64748b;
7803  font-weight: bold;
7804  margin-top: 0.1rem;
7805}
7806
7807.best-for-text {
7808  font-size: 0.875rem;
7809  color: #374151;
7810  font-style: italic;
7811}
7812
7813/* Recommendations */
7814.recommendations-grid {
7815  display: grid;
7816  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
7817  gap: 1rem;
7818  margin-top: 1rem;
7819}
7820
7821.recommendation-card {
7822  background: white;
7823  border: 1px solid #e2e8f0;
7824  border-radius: 8px;
7825  padding: 1rem;
7826  text-align: center;
7827}
7828
7829.recommendation-card h5 {
7830  font-size: 1rem;
7831  margin-bottom: 0.75rem;
7832  color: #1e293b;
7833}
7834
7835.recommendation-card p {
7836  font-size: 0.875rem;
7837  color: #374151;
7838  line-height: 1.5;
7839}
7840
7841/* Tips */
7842.tips-list {
7843  list-style: none;
7844  padding: 0;
7845  margin: 1rem 0 0 0;
7846}
7847
7848.tips-list li {
7849  display: flex;
7850  align-items: flex-start;
7851  gap: 0.75rem;
7852  margin-bottom: 0.75rem;
7853  font-size: 0.9rem;
7854  line-height: 1.5;
7855  color: #374151;
7856}
7857
7858.tips-list li:before {
7859  content: "💡";
7860  font-size: 1rem;
7861  margin-top: 0.1rem;
7862}
7863
7864/* Modal Footer */
7865.modal-footer {
7866  display: flex;
7867  justify-content: space-between;
7868  align-items: center;
7869  padding: 1.5rem 2rem;
7870  border-top: 1px solid #e2e8f0;
7871  background: #f8fafc;
7872}
7873
7874.btn-outline-secondary {
7875  border: 1px solid #64748b;
7876  background: transparent;
7877  color: #64748b;
7878  padding: 0.75rem 1.5rem;
7879  border-radius: 6px;
7880  font-weight: 600;
7881  display: flex;
7882  align-items: center;
7883  gap: 0.5rem;
7884  cursor: pointer;
7885  transition: all 0.2s ease;
7886}
7887
7888.btn-outline-secondary:hover {
7889  background: #64748b;
7890  color: white;
7891}
7892
7893/* Responsive for chip button */
7894@media (max-width: 880px) {
7895  .pd-compare-section {
7896    margin: 1rem 0;
7897    display: block;
7898  }
7899
7900  .pd-compare-btn {
7901    width: 100%;
7902    font-size: 0.85rem;
7903    padding: 0.65rem 1rem;
7904    border-radius: 10px;
7905    justify-content: center;
7906    min-height: 44px;
7907  }
7908
7909  .pd-compare-btn .compare-icon {
7910    width: 16px;
7911    height: 16px;
7912  }
7913
7914  .pd-compare-info {
7915    margin-top: 0.5rem;
7916    display: block;
7917    text-align: center;
7918  }
7919
7920  .compare-note {
7921    font-size: 0.7rem;
7922    display: block;
7923  }
7924}
7925
7926/* Responsive Design */
7927@media (max-width: 768px) {
7928  .compare-sidebar {
7929    width: 100%;
7930    right: -100%;
7931    /* Fix for mobile viewport height changes */
7932    height: 100vh;
7933    height: 100dvh; /* Dynamic viewport height for modern browsers */
7934  }
7935
7936  .compare-actions {
7937    /* Ensure actions are always visible */
7938    position: sticky;
7939    bottom: 0;
7940    background: white;
7941    border-top: 2px solid #e2e8f0;
7942    box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
7943    z-index: 10;
7944  }
7945
7946  .compare-actions .btn {
7947    /* Larger touch targets for mobile */
7948    min-height: 48px;
7949    font-size: 0.9rem;
7950    font-weight: 700;
7951  }
7952
7953  .modal-container {
7954    width: 95%;
7955    max-height: 95vh;
7956    max-height: 95dvh; /* Dynamic viewport height */
7957  }
7958
7959  .modal-header,
7960  .modal-content,
7961  .modal-footer {
7962    padding: 1rem;
7963  }
7964
7965  .recommendations-grid {
7966    grid-template-columns: 1fr;
7967  }
7968
7969  .category-products {
7970    grid-template-columns: 1fr;
7971  }
7972
7973  .modal-footer {
7974    flex-direction: column;
7975    gap: 0.75rem;
7976  }
7977
7978  .modal-footer .btn {
7979    width: 100%;
7980  }
7981
7982  /* Ensure compare button is visible on mobile */
7983  .pd-compare-section {
7984    display: block !important;
7985    visibility: visible !important;
7986    opacity: 1 !important;
7987    margin: 1rem 0;
7988    padding: 0;
7989    position: relative !important;
7990    z-index: 1 !important;
7991    max-width: 100%;
7992    overflow: hidden;
7993  }
7994
7995  .pd-compare-btn {
7996    display: flex !important;
7997    visibility: visible !important;
7998    opacity: 1 !important;
7999    width: 100%;
8000    padding: 0.75rem 1rem;
8001    font-size: 0.9rem;
8002    min-height: 48px; /* Minimum touch target for mobile */
8003    border: 2px solid #e2e8f0 !important;
8004    background: #fff !important;
8005    position: relative !important;
8006    z-index: 2 !important;
8007    box-sizing: border-box;
8008    white-space: normal !important; /* Allow text wrapping on mobile */
8009    flex-wrap: wrap;
8010    gap: 0.5rem;
8011  }
8012
8013  .pd-compare-btn .compare-text {
8014    display: inline !important;
8015    visibility: visible !important;
8016    font-weight: 600;
8017    color: #475569 !important;
8018  }
8019
8020  .pd-compare-btn .compare-remove {
8021    display: none !important; /* Hidden by default */
8022  }
8023
8024  .pd-compare-btn.added .compare-remove {
8025    display: flex !important; /* Only show when added */
8026    flex-wrap: wrap;
8027    align-items: center;
8028    gap: 0.4rem;
8029    white-space: normal !important;
8030    word-break: break-word;
8031  }
8032
8033  .pd-compare-btn .compare-icon {
8034    display: inline-block !important;
8035    width: 18px;
8036    height: 18px;
8037    flex-shrink: 0;
8038  }
8039
8040  .pd-compare-info {
8041    margin-top: 0.5rem;
8042  }
8043
8044  .compare-note {
8045    font-size: 0.85rem !important;
8046    display: block !important;
8047    visibility: visible !important;
8048    color: #64748b !important;
8049  }
8050}
8051
8052/* iOS Safari specific fixes */
8053@media (max-width: 480px) {
8054  #compareBtn {
8055    width:46px;
8056    height:46px;
8057  }
8058
8059  .pd-compare-section {
8060    -webkit-transform: translateZ(0);
8061    transform: translateZ(0);
8062  }
8063
8064  .pd-compare-btn {
8065    -webkit-appearance: none !important;
8066    appearance: none !important;
8067    -webkit-tap-highlight-color: transparent;
8068    touch-action: manipulation;
8069  }
8070
8071  /* Fix sidebar height for iOS address bar */
8072  .compare-sidebar {
8073    /* Use fixed positioning that accounts for iOS address bar */
8074    height: 100vh;
8075    height: -webkit-fill-available; /* iOS Safari compatibility */
8076    min-height: 100vh;
8077    min-height: -webkit-fill-available;
8078  }
8079
8080  /* Ensure compare actions stay at bottom */
8081  .compare-actions {
8082    position: sticky;
8083    bottom: 0;
8084    background: white;
8085    border-top: 2px solid #e2e8f0;
8086    box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.15);
8087    padding: 1.25rem 1rem;
8088    z-index: 20;
8089  }
8090
8091  .compare-actions .btn {
8092    min-height: 52px; /* Larger for easier tapping */
8093    font-size: 1rem;
8094    font-weight: 700;
8095    border-radius: 8px;
8096  }
8097
8098  /* Adjust content padding to account for sticky actions */
8099  .compare-content {
8100    padding-bottom: 6rem; /* Extra space for sticky actions */
8101  }
8102}
8103
8104/* High-resolution mobile devices (iPhone 12 Pro, etc.) */
8105@media (-webkit-min-device-pixel-ratio: 2) and (max-width: 430px) {
8106  .pd-compare-section {
8107    display: block !important;
8108  }
8109
8110  .pd-compare-btn {
8111    display: flex !important;
8112    min-height: 44px;
8113    font-size: 16px; /* Prevent zoom on iOS */
8114  }
8115
8116  /* Enhanced fixes for iPhone 12 Pro and similar devices */
8117  .compare-sidebar {
8118    /* Ensure full height coverage */
8119    height: 100vh;
8120    height: 100dvh; /* Dynamic viewport height */
8121    height: -webkit-fill-available; /* iOS fallback */
8122    max-height: 100vh;
8123    max-height: 100dvh;
8124    max-height: -webkit-fill-available;
8125    overflow: hidden; /* Prevent scroll issues */
8126  }
8127
8128  .compare-content {
8129    /* Flexible content area */
8130    flex: 1;
8131    overflow-y: auto;
8132    -webkit-overflow-scrolling: touch;
8133    padding: 1rem 1rem 8rem 1rem; /* Extra bottom padding */
8134  }
8135
8136  .compare-actions {
8137    /* Force actions to stay visible */
8138    position: fixed;
8139    bottom: 0;
8140    left: 0;
8141    right: 0;
8142    width: 100%;
8143    background: white;
8144    border-top: 2px solid #e2e8f0;
8145    box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.2);
8146    padding: 1rem;
8147    z-index: 9999;
8148    display: flex !important;
8149    gap: 0.75rem;
8150  }
8151
8152  .compare-actions .btn {
8153    flex: 1;
8154    min-height: 30px; /* Extra large for high-DPI screens */
8155    font-size: .8rem;
8156    font-weight: 600;
8157    border-radius: 12px;
8158    display: flex !important;
8159    align-items: center;
8160    justify-content: center;
8161    gap: 0.5rem;
8162  }
8163
8164  /* Ensure AI analyze button is always visible and prominent */
8165  #compareAnalyze {
8166    background: #3b82f6 !important;
8167    border-color: #3b82f6 !important;
8168    color: white !important;
8169    box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
8170  }
8171
8172  #compareAnalyze:not(:disabled) {
8173    background: #2563eb !important;
8174    border-color: #2563eb !important;
8175    transform: scale(1.02);
8176  }
8177
8178  #compareAnalyze:disabled {
8179    opacity: 0.6 !important;
8180    background: #9ca3af !important;
8181    border-color: #9ca3af !important;
8182  }
8183}
8184
8185/* AI Disclaimer Styles */
8186.ai-disclaimer {
8187  display: flex;
8188  align-items: flex-start;
8189  gap: 0.75rem;
8190  padding: 1rem;
8191  background: #fef3c7;
8192  border: 1px solid #fbbf24;
8193  border-radius: 8px;
8194  margin: 0;
8195}
8196
8197.disclaimer-icon {
8198  font-size: 1.25rem;
8199  flex-shrink: 0;
8200  margin-top: 0.125rem;
8201}
8202
8203.disclaimer-text {
8204  font-size: 0.85rem;
8205  line-height: 1.4;
8206  color: #92400e;
8207}
8208
8209.disclaimer-text strong {
8210  color: #78350f;
8211  font-weight: 600;
8212}
8213
8214/* Compare Disclaimer in Sidebar */
8215.compare-disclaimer {
8216  margin-top: 1rem;
8217  padding: 1rem;
8218  background: #f0f9ff;
8219  border: 1px solid #0ea5e9;
8220  border-radius: 8px;
8221}
8222
8223.compare-disclaimer h6 {
8224  display: flex;
8225  align-items: center;
8226  gap: 0.5rem;
8227  font-size: 0.875rem;
8228  font-weight: 600;
8229  color: #0c4a6e;
8230  margin-bottom: 0.5rem;
8231}
8232
8233.compare-disclaimer p {
8234  font-size: 0.8rem;
8235  line-height: 1.4;
8236  color: #075985;
8237  margin-bottom: 0.5rem;
8238}
8239
8240.compare-disclaimer p:last-child {
8241  margin-bottom: 0;
8242}
8243
8244.compare-disclaimer ul {
8245  list-style: none;
8246  padding: 0;
8247  margin: 0.5rem 0 0 0;
8248}
8249
8250.compare-disclaimer li {
8251  display: flex;
8252  align-items: flex-start;
8253  gap: 0.5rem;
8254  margin-bottom: 0.25rem;
8255  font-size: 0.75rem;
8256  line-height: 1.3;
8257  color: #075985;
8258}
8259
8260.compare-disclaimer li:before {
8261  content: "•";
8262  color: #0ea5e9;
8263  font-weight: bold;
8264  margin-top: 0.1rem;
8265  flex-shrink: 0;
8266}
8267
8268/* Mobile Browser Address Bar Fixes */
8269@supports (height: 100dvh) {
8270  /* Modern browsers with dynamic viewport support */
8271  @media (max-width: 768px) {
8272    .compare-sidebar {
8273      height: 100dvh !important;
8274      max-height: 100dvh !important;
8275    }
8276
8277    .modal-container {
8278      max-height: 95dvh !important;
8279    }
8280  }
8281}
8282
8283@supports (height: -webkit-fill-available) {
8284  /* iOS Safari specific support */
8285  @media (max-width: 768px) {
8286    .compare-sidebar {
8287      height: -webkit-fill-available !important;
8288      max-height: -webkit-fill-available !important;
8289    }
8290  }
8291}
8292
8293/* Fallback for older browsers */
8294@media (max-width: 768px) and (orientation: portrait) {
8295  .compare-sidebar {
8296    /* Fixed height that works across different mobile browsers */
8297    min-height: 600px;
8298    height: calc(100vh - env(keyboard-inset-height, 0px));
8299    height: calc(100vh - env(safe-area-inset-bottom, 0px));
8300  }
8301
8302  /* Ensure actions are always accessible */
8303  .compare-actions {
8304    position: sticky !important;
8305    bottom: env(safe-area-inset-bottom, 0px) !important;
8306    margin-bottom: 0 !important;
8307    padding-bottom: calc(1rem + env(safe-area-inset-bottom, 0px)) !important;
8308  }
8309}
8310
8311/* PWA and fullscreen app support */
8312@media (display-mode: standalone) {
8313  .compare-sidebar {
8314    height: 100vh !important;
8315    height: 100dvh !important;
8316  }
8317}
8318
8319/* Utilities */
8320.btn-sm {
8321  padding: 0.5rem 0.75rem;
8322  font-size: 0.8rem;
8323}
8324
8325[hidden] {
8326  display: none !important;
8327}
8328
8329/* ===== UX ENHANCEMENTS ===== */
8330
8331/* Compare Button Highlight Animation */
8332.pulse-highlight {
8333  animation: pulse-glow 2s ease-in-out 3;
8334  box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7);
8335}
8336
8337@keyframes pulse-glow {
8338  0% {
8339    box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7);
8340  }
8341  50% {
8342    box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
8343  }
8344  100% {
8345    box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
8346  }
8347}
8348
8349/* Flying Product Animation */
8350.flying-product {
8351  position: fixed;
8352  z-index: 9999;
8353  pointer-events: none;
8354}
8355
8356.flying-product img {
8357  border-radius: 8px;
8358  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
8359}
8360
8361/* Sticky Compare Bar - REMOVED (using only fly-to-navbar animation) */
8362
8363/* Inline Compare Preview - REMOVED (keeping it simple and clean) */
8364</style>
8365  <style data-inlined="product-card-actions.css">
8366/**
8367 * Product Card Quick Actions - 3 Dot Menu + Bottom Sheet
8368 * Tokopedia-inspired design
8369 */
8370
8371/* ===== 3 Dot Button on Product Card ===== */
8372.prod-card {
8373    position: relative;
8374}
8375
8376.prod-card-actions-btn {
8377    position: absolute;
8378    top: 8px;
8379    right: 8px;
8380    width: 32px;
8381    height: 32px;
8382    border-radius: 50%;
8383    background: rgba(255, 255, 255, 0.95);
8384    border: 1px solid rgba(0, 0, 0, 0.08);
8385    display: inline-flex;
8386    align-items: center;
8387    justify-content: center;
8388    cursor: pointer;
8389    z-index: 3;
8390    transition: all 0.2s ease;
8391    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
8392    -webkit-tap-highlight-color: transparent;
8393}
8394
8395.prod-card-actions-btn:hover {
8396    background: #fff;
8397    border-color: rgba(0, 0, 0, 0.15);
8398    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
8399    transform: scale(1.05);
8400}
8401
8402.prod-card-actions-btn:active {
8403    transform: scale(0.95);
8404}
8405
8406.prod-card-actions-btn svg {
8407    width: 20px;
8408    height: 20px;
8409    color: #64748b;
8410    display: block;
8411}
8412
8413/* Desktop: Show on hover */
8414@media (min-width: 641px) {
8415    .prod-card-actions-btn {
8416        opacity: 0;
8417        pointer-events: none;
8418    }
8419
8420    .prod-card:hover .prod-card-actions-btn {
8421        opacity: 1;
8422        pointer-events: auto;
8423    }
8424}
8425
8426/* Mobile: Always visible but smaller */
8427@media (max-width: 640px) {
8428    .prod-card-actions-btn {
8429        width: 28px;
8430        height: 28px;
8431        top: 6px;
8432        right: 6px;
8433        opacity: 95%;
8434    }
8435
8436    .prod-card-actions-btn svg {
8437        width: 18px;
8438        height: 18px;
8439    }
8440}
8441
8442/* ===== Bottom Sheet Overlay ===== */
8443.pca-overlay {
8444    position: fixed;
8445    inset: 0;
8446    background: rgba(15, 23, 42, 0.6);
8447    backdrop-filter: blur(4px);
8448    z-index: 99998;
8449    opacity: 0;
8450    visibility: hidden;
8451    transition: all 0.3s ease;
8452}
8453
8454.pca-overlay.active {
8455    opacity: 1;
8456    visibility: visible;
8457}
8458
8459/* ===== Bottom Sheet Container ===== */
8460.pca-bottom-sheet {
8461    position: fixed;
8462    bottom: 0;
8463    left: 0;
8464    right: 0;
8465    background: #fff;
8466    border-radius: 20px 20px 0 0;
8467    box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.15);
8468    z-index: 99999;
8469    max-width: 600px;
8470    margin: 0 auto;
8471    transform: translateY(100%);
8472    transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
8473}
8474
8475.pca-bottom-sheet.active {
8476    transform: translateY(0);
8477}
8478
8479/* Drag Handle */
8480.pca-sheet-handle {
8481    width: 40px;
8482    height: 4px;
8483    background: #cbd5e1;
8484    border-radius: 999px;
8485    margin: 12px auto 8px;
8486}
8487
8488/* Product Preview */
8489.pca-sheet-product {
8490    display: flex;
8491    gap: 12px;
8492    padding: 12px 20px;
8493    border-bottom: 1px solid #f1f5f9;
8494}
8495
8496.pca-product-image {
8497    width: 60px;
8498    height: 60px;
8499    border-radius: 8px;
8500    object-fit: cover;
8501    flex-shrink: 0;
8502    border: 1px solid #e2e8f0;
8503}
8504
8505.pca-product-info {
8506    flex: 1;
8507    min-width: 0;
8508}
8509
8510.pca-product-name {
8511    font-size: 14px;
8512    font-weight: 600;
8513    color: #0f172a;
8514    margin: 0 0 4px 0;
8515    display: -webkit-box;
8516    -webkit-line-clamp: 2;
8517    -webkit-box-orient: vertical;
8518    overflow: hidden;
8519    line-height: 1.3;
8520}
8521
8522.pca-product-price {
8523    font-size: 15px;
8524    font-weight: 700;
8525    color: #0ea5e9;
8526    margin: 0;
8527}
8528
8529.pca-product-stock {
8530    font-size: 12px;
8531    color: #64748b;
8532    margin: 2px 0 0 0;
8533}
8534
8535.pca-product-stock.in {
8536    color: #16a34a;
8537}
8538
8539.pca-product-stock.low {
8540    color: #ea580c;
8541}
8542
8543.pca-product-stock.out {
8544    color: #dc2626;
8545}
8546
8547/* Action Menu */
8548.pca-sheet-actions {
8549    padding: 8px 0 20px;
8550}
8551
8552.pca-action-item {
8553    display: flex;
8554    align-items: center;
8555    gap: 16px;
8556    padding: 16px 20px;
8557    background: transparent;
8558    border: 0;
8559    width: 100%;
8560    cursor: pointer;
8561    transition: background-color 0.15s ease;
8562    -webkit-tap-highlight-color: transparent;
8563}
8564
8565.pca-action-item:hover {
8566    background: #f8fafc;
8567}
8568
8569.pca-action-item:active {
8570    background: #f1f5f9;
8571}
8572
8573.pca-action-item:disabled {
8574    opacity: 0.4;
8575    cursor: not-allowed;
8576}
8577
8578.pca-action-item:disabled:hover {
8579    background: transparent;
8580}
8581
8582.pca-action-icon {
8583    width: 24px;
8584    height: 24px;
8585    color: #475569;
8586    flex-shrink: 0;
8587}
8588
8589.pca-action-content {
8590    flex: 1;
8591    text-align: left;
8592}
8593
8594.pca-action-label {
8595    font-size: 15px;
8596    font-weight: 600;
8597    color: #0f172a;
8598    margin: 0 0 2px 0;
8599}
8600
8601.pca-action-desc {
8602    font-size: 12px;
8603    color: #64748b;
8604    margin: 0;
8605}
8606
8607.pca-action-badge {
8608    display: inline-flex;
8609    align-items: center;
8610    justify-content: center;
8611    min-width: 20px;
8612    height: 20px;
8613    padding: 0 6px;
8614    background: #0ea5e9;
8615    color: #fff;
8616    font-size: 11px;
8617    font-weight: 700;
8618    border-radius: 999px;
8619    margin-left: auto;
8620}
8621
8622.pca-action-badge.compare {
8623    background: #8b5cf6;
8624}
8625
8626/* Action Item Colors */
8627.pca-action-item.cart .pca-action-icon {
8628    color: #0ea5e9;
8629}
8630
8631.pca-action-item.compare .pca-action-icon {
8632    color: #8b5cf6;
8633}
8634
8635.pca-action-item.compare.active {
8636    background: rgba(139, 92, 246, 0.05);
8637}
8638
8639.pca-action-item.compare.active .pca-action-label {
8640    color: #8b5cf6;
8641}
8642
8643.pca-action-item.compare.active .pca-action-icon {
8644    color: #8b5cf6;
8645}
8646
8647.pca-action-item.share .pca-action-icon {
8648    color: #64748b;
8649}
8650
8651/* Loading State */
8652.pca-action-item.loading {
8653    pointer-events: none;
8654}
8655
8656.pca-action-item.loading .pca-action-icon {
8657    animation: spin 1s linear infinite;
8658}
8659
8660@keyframes spin {
8661    from { transform: rotate(0deg); }
8662    to { transform: rotate(360deg); }
8663}
8664
8665/* Success State Animation */
8666.pca-action-item.success .pca-action-icon {
8667    animation: checkmark 0.5s ease;
8668}
8669
8670@keyframes checkmark {
8671    0% { transform: scale(0); }
8672    50% { transform: scale(1.2); }
8673    100% { transform: scale(1); }
8674}
8675
8676/* ===== Safe Area for Mobile Devices ===== */
8677@supports (padding-bottom: env(safe-area-inset-bottom)) {
8678    .pca-sheet-actions {
8679        padding-bottom: calc(20px + env(safe-area-inset-bottom));
8680    }
8681}
8682
8683/* ===== Responsive Tweaks ===== */
8684@media (max-width: 640px) {
8685    .pca-bottom-sheet {
8686        border-radius: 16px 16px 0 0;
8687    }
8688
8689    .pca-sheet-product {
8690        padding: 10px 16px;
8691    }
8692
8693    .pca-product-image {
8694        width: 50px;
8695        height: 50px;
8696    }
8697
8698    .pca-product-name {
8699        font-size: 13px;
8700    }
8701
8702    .pca-product-price {
8703        font-size: 14px;
8704    }
8705
8706    .pca-action-item {
8707        padding: 14px 16px;
8708        gap: 12px;
8709    }
8710
8711    .pca-action-icon {
8712        width: 22px;
8713        height: 22px;
8714    }
8715
8716    .pca-action-label {
8717        font-size: 14px;
8718    }
8719}
8720
8721/* ===== Animation for Flying Product ===== */
8722.flying-product {
8723    position: fixed;
8724    pointer-events: none;
8725    z-index: 99999;
8726    animation: fadeOut 0.8s ease forwards;
8727}
8728
8729@keyframes fadeOut {
8730    to {
8731        opacity: 0;
8732        transform: scale(0.5);
8733    }
8734}
8735
8736/* ===== Desktop Dropdown (Alternative) ===== */
8737@media (min-width: 1024px) {
8738    /* Optional: Could add desktop dropdown menu here */
8739    /* For now, using bottom sheet for consistency */
8740}
8741
8742/* ===== Print Styles ===== */
8743@media print {
8744    .prod-card-actions-btn,
8745    .pca-overlay,
8746    .pca-bottom-sheet {
8747        display: none !important;
8748    }
8749}
8750
8751</style>
8752  <style data-inlined="modal.css">
8753/* Modern Modal Dialog for Cart Operations */
8754.modal-overlay {
8755  position: fixed;
8756  top: 0;
8757  left: 0;
8758  right: 0;
8759  bottom: 0;
8760  background: rgba(0, 0, 0, 0.5);
8761  backdrop-filter: blur(4px);
8762  display: flex;
8763  align-items: center;
8764  justify-content: center;
8765  z-index: 9999;
8766  opacity: 0;
8767  visibility: hidden;
8768  transition: all 0.2s ease;
8769}
8770
8771.modal-overlay.show {
8772  opacity: 1;
8773  visibility: visible;
8774}
8775
8776.modal-dialog {
8777  background: white;
8778  border-radius: 16px;
8779  box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15);
8780  max-width: 400px;
8781  width: 90%;
8782  max-height: 90vh;
8783  overflow-y: auto;
8784  transform: scale(0.95);
8785  transition: transform 0.2s ease;
8786}
8787
8788.modal-overlay.show .modal-dialog {
8789  transform: scale(1);
8790}
8791
8792.modal-header {
8793  padding: 24px 24px 16px;
8794  border-bottom: 1px solid #f1f5f9;
8795}
8796
8797.modal-title {
8798  font-size: 20px;
8799  font-weight: 700;
8800  color: #0f172a;
8801  margin: 0;
8802  display: flex;
8803  align-items: center;
8804  gap: 12px;
8805}
8806
8807.modal-body {
8808  padding: 16px 24px 24px;
8809}
8810
8811.modal-message {
8812  font-size: 16px;
8813  line-height: 1.5;
8814  color: #475569;
8815  margin: 0;
8816}
8817
8818.modal-actions {
8819  display: flex;
8820  gap: 12px;
8821  margin-top: 24px;
8822}
8823
8824.modal-btn {
8825  flex: 1;
8826  padding: 12px 16px;
8827  border: none;
8828  border-radius: 8px;
8829  font-size: 14px;
8830  font-weight: 600;
8831  cursor: pointer;
8832  transition: all 0.2s ease;
8833  text-align: center;
8834}
8835
8836.modal-btn-secondary {
8837  background: #f8fafc;
8838  color: #64748b;
8839  border: 1px solid #e2e8f0;
8840}
8841
8842.modal-btn-secondary:hover {
8843  background: #f1f5f9;
8844  border-color: #cbd5e1;
8845}
8846
8847.modal-btn-danger {
8848  background: #ef4444;
8849  color: white;
8850}
8851
8852.modal-btn-danger:hover {
8853  background: #dc2626;
8854}
8855
8856.modal-btn-primary {
8857  background: #3b82f6;
8858  color: white;
8859}
8860
8861.modal-btn-primary:hover {
8862  background: #2563eb;
8863}
8864
8865.modal-icon {
8866  font-size: 24px;
8867  width: 32px;
8868  height: 32px;
8869  display: flex;
8870  align-items: center;
8871  justify-content: center;
8872  border-radius: 8px;
8873}
8874
8875.modal-icon.danger {
8876  background: #fef2f2;
8877  color: #ef4444;
8878}
8879
8880.modal-icon.warning {
8881  background: #fffbeb;
8882  color: #f59e0b;
8883}
8884
8885.modal-icon.success {
8886  background: #f0fdf4;
8887  color: #22c55e;
8888}
8889
8890.modal-icon.info {
8891  background: #eff6ff;
8892  color: #3b82f6;
8893}
8894
8895/* Mobile responsive */
8896@media (max-width: 640px) {
8897  .modal-dialog {
8898    margin: 16px;
8899    max-width: none;
8900    width: auto;
8901  }
8902
8903  .modal-header,
8904  .modal-body {
8905    padding-left: 20px;
8906    padding-right: 20px;
8907  }
8908
8909  .modal-actions {
8910    flex-direction: column;
8911  }
8912
8913  .modal-btn {
8914    flex: none;
8915  }
8916}
8917</style>
8918
8919        <style data-inlined="reviews-points.css">
8920/**
8921 * Reviews & Points System - Styles
8922 * Product reviews, rating display, and point redemption UI
8923 */
8924
8925/* ===================================
8926   PRODUCT REVIEWS
8927   =================================== */
8928
8929.product-tabs {
8930  display: flex;
8931  border-bottom: 2px solid #e5e7eb;
8932  margin: 24px 0 16px;
8933  gap: 8px;
8934}
8935
8936.tab-btn {
8937  padding: 12px 20px;
8938  border: none;
8939  background: none;
8940  cursor: pointer;
8941  font-size: 15px;
8942  font-weight: 500;
8943  color: #6b7280;
8944  border-bottom: 3px solid transparent;
8945  transition: all 0.2s ease;
8946  position: relative;
8947  top: 2px;
8948}
8949
8950.tab-btn:hover {
8951  color: #111827;
8952}
8953
8954.tab-btn.active {
8955  color: #2563eb;
8956  border-bottom-color: #2563eb;
8957}
8958
8959.tab-content {
8960  padding: 20px 0;
8961}
8962
8963/* Review Statistics */
8964.review-stats {
8965  display: grid;
8966  grid-template-columns: 200px 1fr;
8967  gap: 32px;
8968  padding: 24px;
8969  background: #f9fafb;
8970  border-radius: 12px;
8971  margin-bottom: 24px;
8972}
8973
8974.rating-summary {
8975  text-align: center;
8976}
8977
8978.avg-rating {
8979  display: block;
8980  font-size: 48px;
8981  font-weight: 700;
8982  color: #111827;
8983  line-height: 1;
8984}
8985
8986.rating-summary .stars {
8987  font-size: 24px;
8988  color: #fbbf24;
8989  margin: 8px 0;
8990}
8991
8992.total-reviews {
8993  display: block;
8994  font-size: 14px;
8995  color: #6b7280;
8996  margin-top: 4px;
8997}
8998
8999.rating-breakdown {
9000  display: flex;
9001  flex-direction: column;
9002  gap: 8px;
9003}
9004
9005.rating-bar {
9006  display: flex;
9007  align-items: center;
9008  gap: 12px;
9009}
9010
9011.rating-label {
9012  font-size: 14px;
9013  color: #374151;
9014  width: 32px;
9015  text-align: right;
9016}
9017
9018.bar-container {
9019  flex: 1;
9020  height: 8px;
9021  background: #e5e7eb;
9022  border-radius: 4px;
9023  overflow: hidden;
9024}
9025
9026.bar-fill {
9027  height: 100%;
9028  background: linear-gradient(90deg, #fbbf24 0%, #f59e0b 100%);
9029  transition: width 0.3s ease;
9030}
9031
9032.rating-count {
9033  font-size: 14px;
9034  color: #6b7280;
9035  width: 40px;
9036  text-align: right;
9037}
9038
9039/* Review Filters */
9040.review-filters {
9041  display: flex;
9042  justify-content: space-between;
9043  align-items: center;
9044  margin-bottom: 20px;
9045}
9046
9047.review-sort {
9048  padding: 8px 12px;
9049  border: 1px solid #d1d5db;
9050  border-radius: 8px;
9051  font-size: 14px;
9052  background: white;
9053}
9054
9055.write-review-btn {
9056  padding: 10px 20px;
9057  background: #2563eb;
9058  color: white;
9059  border: none;
9060  border-radius: 8px;
9061  font-weight: 500;
9062  cursor: pointer;
9063  transition: background 0.2s;
9064}
9065
9066.write-review-btn:hover {
9067  background: #1d4ed8;
9068}
9069
9070/* Review List */
9071.reviews-list {
9072  display: flex;
9073  flex-direction: column;
9074  gap: 16px;
9075}
9076
9077.review-item {
9078  padding: 20px;
9079  background: white;
9080  border: 1px solid #e5e7eb;
9081  border-radius: 12px;
9082  transition: box-shadow 0.2s;
9083}
9084
9085.review-item:hover {
9086  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
9087}
9088
9089.review-header {
9090  display: flex;
9091  justify-content: space-between;
9092  align-items: flex-start;
9093  margin-bottom: 8px;
9094}
9095
9096.review-author {
9097  display: flex;
9098  align-items: center;
9099  gap: 8px;
9100}
9101
9102.review-author strong {
9103  font-size: 15px;
9104  color: #111827;
9105}
9106
9107.verified-badge {
9108  display: inline-flex;
9109  align-items: center;
9110  gap: 4px;
9111  font-size: 12px;
9112  color: #059669;
9113  background: #d1fae5;
9114  padding: 2px 8px;
9115  border-radius: 4px;
9116  font-weight: 500;
9117}
9118
9119.anonymous-badge {
9120  display: inline-flex;
9121  align-items: center;
9122  font-size: 12px;
9123  color: #6b7280;
9124  background: #f3f4f6;
9125  padding: 2px 6px;
9126  border-radius: 4px;
9127  margin-left: 4px;
9128  cursor: help;
9129}
9130
9131.review-rating .stars {
9132  font-size: 16px;
9133  color: #fbbf24;
9134}
9135
9136.review-date {
9137  font-size: 13px;
9138  color: #9ca3af;
9139  margin-bottom: 12px;
9140}
9141
9142.review-text {
9143  font-size: 15px;
9144  line-height: 1.6;
9145  color: #374151;
9146  margin-bottom: 12px;
9147}
9148
9149.review-images {
9150  display: flex;
9151  gap: 8px;
9152  margin-bottom: 12px;
9153  flex-wrap: wrap;
9154}
9155
9156.review-img {
9157  width: 80px;
9158  height: 80px;
9159  object-fit: cover;
9160  border-radius: 8px;
9161  cursor: pointer;
9162  transition: transform 0.2s;
9163}
9164
9165.review-img:hover {
9166  transform: scale(1.05);
9167}
9168
9169.review-actions {
9170  display: flex;
9171  gap: 12px;
9172  padding-top: 12px;
9173  border-top: 1px solid #f3f4f6;
9174}
9175
9176.btn-helpful {
9177  display: flex;
9178  align-items: center;
9179  gap: 6px;
9180  padding: 6px 12px;
9181  background: #f9fafb;
9182  border: 1px solid #e5e7eb;
9183  border-radius: 6px;
9184  font-size: 13px;
9185  color: #6b7280;
9186  cursor: pointer;
9187  transition: all 0.2s;
9188}
9189
9190.btn-helpful:hover {
9191  background: #f3f4f6;
9192  color: #111827;
9193  border-color: #d1d5db;
9194}
9195
9196.btn-helpful.voted {
9197  background: #dbeafe;
9198  color: #1d4ed8;
9199  border-color: #93c5fd;
9200}
9201
9202.helpful-icon {
9203  font-size: 14px;
9204}
9205
9206.no-reviews {
9207  text-align: center;
9208  padding: 60px 20px;
9209  color: #9ca3af;
9210  font-size: 15px;
9211}
9212
9213.load-more-btn {
9214  display: block;
9215  margin: 24px auto 0;
9216  padding: 12px 32px;
9217  background: white;
9218  border: 1px solid #d1d5db;
9219  border-radius: 8px;
9220  font-size: 14px;
9221  font-weight: 500;
9222  color: #374151;
9223  cursor: pointer;
9224  transition: all 0.2s;
9225}
9226
9227.load-more-btn:hover {
9228  background: #f9fafb;
9229  border-color: #9ca3af;
9230}
9231
9232/* ===================================
9233   POINT BALANCE WIDGET (LEGACY - kept for backward compatibility)
9234   =================================== */
9235
9236.point-balance-card {
9237  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
9238  color: white;
9239  padding: 24px;
9240  border-radius: 16px;
9241  margin-bottom: 24px;
9242  box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3);
9243}
9244
9245.point-header {
9246  display: flex;
9247  justify-content: space-between;
9248  align-items: center;
9249  margin-bottom: 12px;
9250}
9251
9252.point-header h3 {
9253  font-size: 18px;
9254  font-weight: 600;
9255  margin: 0;
9256  display: flex;
9257  align-items: center;
9258  gap: 8px;
9259}
9260
9261.point-header .balance {
9262  font-size: 32px;
9263  font-weight: 700;
9264}
9265
9266.point-value {
9267  font-size: 14px;
9268  opacity: 0.9;
9269  margin: 8px 0 16px;
9270}
9271
9272.point-stats {
9273  display: flex;
9274  gap: 20px;
9275  font-size: 13px;
9276  opacity: 0.85;
9277  margin-bottom: 16px;
9278}
9279
9280.btn-view-history {
9281  display: inline-block;
9282  padding: 10px 20px;
9283  background: rgba(255, 255, 255, 0.2);
9284  color: white;
9285  text-decoration: none;
9286  border-radius: 8px;
9287  font-size: 14px;
9288  font-weight: 500;
9289  transition: background 0.2s;
9290}
9291
9292.btn-view-history:hover {
9293  background: rgba(255, 255, 255, 0.3);
9294}
9295
9296/* ===================================
9297   CHECKOUT POINT REDEMPTION
9298   =================================== */
9299
9300.checkout-points {
9301  padding: 16px;
9302  background: #fef3c7;
9303  border: 2px solid #fbbf24;
9304  border-radius: 12px;
9305  margin-bottom: 16px;
9306}
9307
9308.points-header {
9309  display: flex;
9310  align-items: center;
9311  gap: 10px;
9312  margin-bottom: 12px;
9313}
9314
9315.points-header input[type="checkbox"] {
9316  width: 18px;
9317  height: 18px;
9318  cursor: pointer;
9319}
9320
9321.points-header label {
9322  font-size: 15px;
9323  color: #78350f;
9324  cursor: pointer;
9325  margin: 0;
9326}
9327
9328#points-slider {
9329  padding: 16px;
9330  background: white;
9331  border-radius: 8px;
9332  margin-top: 12px;
9333}
9334
9335#points-slider label {
9336  display: block;
9337  font-size: 14px;
9338  color: #374151;
9339  margin-bottom: 8px;
9340  font-weight: 500;
9341}
9342
9343#points-amount {
9344  width: 100%;
9345  margin-bottom: 8px;
9346}
9347
9348#points-display {
9349  display: flex;
9350  justify-content: space-between;
9351  font-size: 14px;
9352  color: #111827;
9353  margin-top: 8px;
9354}
9355
9356#points-display .discount-amount {
9357  color: #059669;
9358  font-weight: 600;
9359}
9360
9361.price-row.point-discount {
9362  color: #059669;
9363  font-weight: 500;
9364}
9365
9366/* ===================================
9367   REVIEWABLE ORDERS
9368   =================================== */
9369
9370.reviewable-orders {
9371  background: white;
9372  border: 1px solid #e5e7eb;
9373  border-radius: 12px;
9374  padding: 20px;
9375  margin-bottom: 24px;
9376}
9377
9378.reviewable-orders h3 {
9379  font-size: 18px;
9380  margin-bottom: 16px;
9381  color: #111827;
9382}
9383
9384.order-card {
9385  display: flex;
9386  gap: 16px;
9387  padding: 16px;
9388  background: #f9fafb;
9389  border-radius: 8px;
9390  margin-bottom: 12px;
9391}
9392
9393.order-card:last-child {
9394  margin-bottom: 0;
9395}
9396
9397.order-card img {
9398  width: 80px;
9399  height: 80px;
9400  object-fit: cover;
9401  border-radius: 8px;
9402}
9403
9404.order-info {
9405  flex: 1;
9406}
9407
9408.order-info h4 {
9409  font-size: 15px;
9410  margin: 0 0 4px;
9411  color: #111827;
9412}
9413
9414.order-info p {
9415  font-size: 13px;
9416  color: #6b7280;
9417  margin: 0 0 12px;
9418}
9419
9420.btn-review {
9421  padding: 8px 16px;
9422  background: #059669;
9423  color: white;
9424  border: none;
9425  border-radius: 6px;
9426  font-size: 14px;
9427  font-weight: 500;
9428  cursor: pointer;
9429  transition: background 0.2s;
9430}
9431
9432.btn-review:hover {
9433  background: #047857;
9434}
9435
9436/* ===================================
9437   TOAST NOTIFICATIONS
9438   =================================== */
9439
9440.review-toast {
9441  position: fixed;
9442  bottom: 24px;
9443  right: 24px;
9444  padding: 14px 20px;
9445  background: #111827;
9446  color: white;
9447  border-radius: 8px;
9448  font-size: 14px;
9449  line-height: 1.4;
9450  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
9451  transform: translateY(100px);
9452  opacity: 0;
9453  transition: all 0.3s ease;
9454  z-index: 10000;
9455  max-width: 400px;
9456  word-wrap: break-word;
9457}
9458
9459.review-toast.show {
9460  transform: translateY(0);
9461  opacity: 1;
9462}
9463
9464/* Mobile: Position above bottom-nav */
9465@media (max-width: 768px) {
9466  .review-toast {
9467    bottom: 80px; /* Above bottom-nav (50px height + padding + safe margin) */
9468    right: 16px;
9469    left: 16px;
9470    max-width: calc(100% - 32px);
9471  }
9472}
9473
9474.review-toast.toast-success {
9475  background: #059669;
9476}
9477
9478.review-toast.toast-error {
9479  background: #dc2626;
9480}
9481
9482.review-toast.toast-info {
9483  background: #2563eb;
9484}
9485
9486/* ===================================
9487   RESPONSIVE
9488   =================================== */
9489
9490@media (max-width: 768px) {
9491  .point-balance-card {
9492    border-radius: 0;
9493  }
9494
9495  .review-stats {
9496    grid-template-columns: 70px 1fr;
9497    gap: 20px;
9498    padding: 16px;
9499  }
9500
9501  .rating-summary {
9502    text-align: center;
9503  }
9504
9505  .rating-summary .avg-rating {
9506    font-size: 32px;
9507  }
9508
9509  .rating-summary .stars {
9510    font-size: 18px;
9511    margin: 4px 0;
9512  }
9513
9514  .total-reviews {
9515    font-size: 11px;
9516  }
9517
9518  /* Rating breakdown - more compact */
9519  .rating-breakdown {
9520    gap: 2px;
9521  }
9522
9523  .rating-breakdown .rating-bar {
9524    gap: 4px;
9525  }
9526
9527  .rating-breakdown .rating-label {
9528    font-size: 11px;
9529    width: 16px;
9530    min-width: 16px;
9531    text-align: left;
9532  }
9533
9534  .rating-breakdown .bar-container {
9535    height: 6px;
9536  }
9537
9538  .rating-breakdown .rating-count {
9539    font-size: 11px;
9540    width: 16px;
9541    min-width: 16px;
9542  }
9543
9544  .review-header {
9545    flex-direction: column;
9546    gap: 8px;
9547    align-items: flex-start;
9548  }
9549
9550  .review-rating {
9551    order: -1;
9552    margin-bottom: 4px;
9553  }
9554
9555  .review-rating .stars {
9556    font-size: 14px;
9557  }
9558
9559  .review-filters {
9560    flex-direction: column;
9561    gap: 12px;
9562    align-items: stretch;
9563  }
9564
9565  .review-sort {
9566    width: 100%;
9567  }
9568
9569  .point-header .balance {
9570    font-size: 24px;
9571  }
9572
9573  .point-stats {
9574    flex-direction: column;
9575    gap: 8px;
9576  }
9577
9578  .order-card {
9579    flex-direction: column;
9580  }
9581
9582  .order-card img {
9583    width: 100%;
9584    height: 200px;
9585  }
9586}
9587
9588/* ===================================
9589   LOADING & ERROR STATES
9590   =================================== */
9591
9592.review-loading {
9593  text-align: center;
9594  padding: 40px;
9595  color: #9ca3af;
9596  font-size: 14px;
9597}
9598
9599.review-error {
9600  text-align: center;
9601  padding: 40px;
9602  color: #dc2626;
9603  font-size: 14px;
9604  background: #fee2e2;
9605  border-radius: 8px;
9606}
9607
9608.review-empty {
9609  text-align: center;
9610  padding: 10px;
9611  color: #6b7280;
9612  font-size: 14px;
9613}
9614
9615/* ===================================
9616   REVIEWABLE ORDERS LIST
9617   =================================== */
9618
9619.reviewable-orders-list {
9620  display: flex;
9621  flex-direction: column;
9622  gap: 16px;
9623}
9624
9625.reviewable-order-item {
9626  display: grid;
9627  grid-template-columns: 80px 1fr auto;
9628  gap: 16px;
9629  padding: 16px;
9630  background: #fff;
9631  border: 1px solid #e5e7eb;
9632  border-radius: 12px;
9633  align-items: center;
9634  transition: box-shadow 0.2s;
9635}
9636
9637.reviewable-order-item:hover {
9638  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
9639}
9640
9641.reviewable-order-item .roi-image {
9642  width: 80px;
9643  height: 80px;
9644  object-fit: cover;
9645  border-radius: 8px;
9646  border: 1px solid #e5e7eb;
9647}
9648
9649.reviewable-order-item .roi-info {
9650  display: flex;
9651  flex-direction: column;
9652  gap: 4px;
9653}
9654
9655.reviewable-order-item .roi-info h4 {
9656  margin: 0;
9657  font-size: 15px;
9658  font-weight: 600;
9659  color: #111827;
9660  line-height: 1.4;
9661}
9662
9663.reviewable-order-item .roi-meta {
9664  margin: 0;
9665  font-size: 13px;
9666  color: #6b7280;
9667}
9668
9669.reviewable-order-item .btn-write-review {
9670  white-space: nowrap;
9671  padding: 10px 20px;
9672  font-size: 14px;
9673}
9674
9675@media (max-width: 640px) {
9676  .reviewable-order-item {
9677    grid-template-columns: 60px 1fr;
9678    gap: 12px;
9679  }
9680
9681  .reviewable-order-item .roi-image {
9682    width: 60px;
9683    height: 60px;
9684  }
9685
9686  .reviewable-order-item .btn-write-review {
9687    grid-column: 1 / -1;
9688    width: 100%;
9689  }
9690}
9691
9692/* ===================================
9693   REVIEW SUBMISSION MODAL
9694   =================================== */
9695
9696/* Modal Overlay Background */
9697#review-modal-overlay {
9698  position: fixed;
9699  top: 0;
9700  left: 0;
9701  right: 0;
9702  bottom: 0;
9703  background: rgba(0, 0, 0, 0.6) !important;
9704  backdrop-filter: blur(8px);
9705  display: flex;
9706  align-items: center;
9707  justify-content: center;
9708  z-index: 99999 !important;
9709  padding: 20px;
9710}
9711
9712.review-modal {
9713  max-width: 600px !important;
9714  width: 90% !important;
9715  background: white;
9716  border-radius: 16px !important;
9717  box-shadow: 0 25px 50px rgba(0, 0, 0, 0.25);
9718  max-height: 90vh;
9719  display: flex;
9720  flex-direction: column;
9721  overflow: hidden !important;
9722}
9723
9724#review-modal-overlay .modal-content,
9725.review-modal .modal-content {
9726  padding: 0 !important;
9727  flex: initial !important;
9728  overflow-y: visible !important;
9729}
9730
9731.review-modal .modal-header {
9732  display: flex;
9733  justify-content: space-between;
9734  align-items: center;
9735  padding: 20px 24px;
9736  border-bottom: 1px solid #e5e7eb;
9737}
9738
9739.review-modal .modal-title {
9740  margin: 0;
9741  font-size: 20px;
9742  font-weight: 700;
9743  color: #111827;
9744}
9745
9746.review-modal .modal-close {
9747  background: none;
9748  border: none;
9749  cursor: pointer;
9750  padding: 4px;
9751  color: #6b7280;
9752  transition: color 0.2s;
9753  display: flex;
9754  align-items: center;
9755  justify-content: center;
9756}
9757
9758.review-modal .modal-close:hover {
9759  color: #111827;
9760}
9761
9762.review-modal .modal-body {
9763  padding: 24px;
9764  max-height: calc(90vh - 140px);
9765  overflow-y: auto;
9766}
9767
9768/* Custom Scrollbar for Modal */
9769.review-modal .modal-body::-webkit-scrollbar {
9770  width: 8px;
9771}
9772
9773.review-modal .modal-body::-webkit-scrollbar-track {
9774  background: #f1f5f9;
9775  border-radius: 4px;
9776}
9777
9778.review-modal .modal-body::-webkit-scrollbar-thumb {
9779  background: linear-gradient(180deg, #3b82f6 0%, #2563eb 100%);
9780  border-radius: 4px;
9781}
9782
9783.review-modal .modal-body::-webkit-scrollbar-thumb:hover {
9784  background: linear-gradient(180deg, #2563eb 0%, #1d4ed8 100%);
9785}
9786
9787/* Firefox Scrollbar */
9788.review-modal .modal-body {
9789  scrollbar-width: thin;
9790  scrollbar-color: #3b82f6 #f1f5f9;
9791}
9792
9793.review-modal .modal-footer {
9794  display: flex;
9795  gap: 12px;
9796  margin-top: 24px;
9797  padding-top: 20px;
9798  border-top: 1px solid #f1f5f9;
9799}
9800
9801.review-product-info {
9802  display: flex;
9803  gap: 16px;
9804  padding: 16px;
9805  background: #f9fafb;
9806  border-radius: 8px;
9807  margin-bottom: 24px;
9808}
9809
9810.review-product-thumb {
9811  width: 80px;
9812  height: 80px;
9813  object-fit: cover;
9814  border-radius: 6px;
9815  border: 1px solid #e5e7eb;
9816}
9817
9818.review-product-details h4 {
9819  margin: 0 0 8px 0;
9820  font-size: 16px;
9821  font-weight: 600;
9822  color: #111827;
9823}
9824
9825.review-product-details p {
9826  margin: 0;
9827  font-size: 13px;
9828  color: #6b7280;
9829}
9830
9831/* Star Rating Input */
9832.star-rating-input {
9833  display: inline-flex;
9834  gap: 8px;
9835  margin-right: 12px;
9836  vertical-align: middle;
9837}
9838
9839.star-btn {
9840  background: none;
9841  border: none;
9842  font-size: 32px;
9843  color: #d1d5db;
9844  cursor: pointer;
9845  transition: all 0.2s;
9846  padding: 0;
9847  line-height: 1;
9848}
9849
9850.star-btn:hover,
9851.star-btn.active {
9852  color: #fbbf24;
9853  transform: scale(1.1);
9854}
9855
9856.rating-label {
9857  display: inline-block;
9858  font-size: 15px;
9859  color: #6b7280;
9860  margin: 0;
9861  vertical-align: middle;
9862  min-width: 150px;
9863}
9864
9865/* Form Elements */
9866.review-form .form-group {
9867  margin-bottom: 20px;
9868}
9869
9870.review-form .form-label {
9871  display: block;
9872  font-size: 14px;
9873  font-weight: 600;
9874  color: #374151;
9875  margin-bottom: 8px;
9876}
9877
9878.review-form .form-control {
9879  width: 100%;
9880  padding: 12px;
9881  border: 1px solid #d1d5db;
9882  border-radius: 8px;
9883  font-size: 14px;
9884  font-family: inherit;
9885  transition: border-color 0.2s;
9886}
9887
9888.review-form .form-control:focus {
9889  outline: none;
9890  border-color: #2563eb;
9891  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
9892}
9893
9894.review-form .form-hint {
9895  font-size: 13px;
9896  color: #6b7280;
9897  margin-top: 8px;
9898}
9899
9900/* Image Upload */
9901.btn-upload-trigger {
9902  display: inline-flex;
9903  align-items: center;
9904  gap: 8px;
9905  padding: 10px 16px;
9906  background: #f9fafb;
9907  border: 2px dashed #d1d5db;
9908  border-radius: 8px;
9909  color: #374151;
9910  cursor: pointer;
9911  font-size: 14px;
9912  font-weight: 500;
9913  transition: all 0.2s;
9914}
9915
9916.btn-upload-trigger:hover {
9917  background: #f3f4f6;
9918  border-color: #2563eb;
9919  color: #2563eb;
9920}
9921
9922.image-preview-grid {
9923  display: grid;
9924  grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
9925  gap: 12px;
9926  margin-top: 16px;
9927}
9928
9929.image-preview-item {
9930  position: relative;
9931  aspect-ratio: 1;
9932  border-radius: 8px;
9933  overflow: hidden;
9934  border: 1px solid #e5e7eb;
9935}
9936
9937.image-preview-item img {
9938  width: 100%;
9939  height: 100%;
9940  object-fit: cover;
9941}
9942
9943.remove-image {
9944  position: absolute;
9945  top: 4px;
9946  right: 4px;
9947  background: rgba(0, 0, 0, 0.7);
9948  border: none;
9949  color: white;
9950  width: 24px;
9951  height: 24px;
9952  border-radius: 50%;
9953  cursor: pointer;
9954  display: flex;
9955  align-items: center;
9956  justify-content: center;
9957  transition: background 0.2s;
9958}
9959
9960.remove-image:hover {
9961  background: rgba(0, 0, 0, 0.9);
9962}
9963
9964/* Point Reward Info */
9965.point-reward-info {
9966  background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
9967  border: 2px solid #fbbf24;
9968  border-radius: 12px;
9969  padding: 16px;
9970  margin: 20px 0;
9971}
9972
9973.pri-header {
9974  display: flex;
9975  align-items: center;
9976  gap: 8px;
9977  font-size: 16px;
9978  font-weight: 700;
9979  color: #92400e;
9980  margin-bottom: 12px;
9981}
9982
9983.pri-list {
9984  list-style: none;
9985  padding: 0;
9986  margin: 0 0 12px 0;
9987}
9988
9989.pri-list li {
9990  padding: 6px 0;
9991  font-size: 14px;
9992  color: #78350f;
9993  transition: opacity 0.2s;
9994}
9995
9996.pri-total {
9997  padding-top: 12px;
9998  border-top: 2px solid rgba(251, 191, 36, 0.3);
9999  font-size: 16px;
10000  color: #92400e;
10001  font-weight: 600;
10002}
10003
10004.form-error {
10005  background: #fee2e2;
10006  border: 1px solid #f87171;
10007  color: #991b1b;
10008  padding: 12px;
10009  border-radius: 8px;
10010  font-size: 14px;
10011  margin-top: 16px;
10012}
10013
10014/* Buttons */
10015.review-form .btn {
10016  padding: 12px 24px;
10017  border: none;
10018  border-radius: 8px;
10019  font-size: 15px;
10020  font-weight: 600;
10021  cursor: pointer;
10022  transition: all 0.2s;
10023}
10024
10025.review-form .btn-primary {
10026  background: #2563eb;
10027  color: white;
10028}
10029
10030.review-form .btn-primary:hover {
10031  background: #1d4ed8;
10032}
10033
10034.review-form .btn-primary:disabled {
10035  background: #94a3b8;
10036  cursor: not-allowed;
10037}
10038
10039.review-form .btn-secondary {
10040  background: #f1f5f9;
10041  color: #64748b;
10042}
10043
10044.review-form .btn-secondary:hover {
10045  background: #e2e8f0;
10046  color: #475569;
10047}
10048
10049@media (max-width: 768px) {
10050    #review-modal-overlay { padding: 0; }
10051    .review-modal { max-width: 100%; width: 100% !important; }
10052}
10053
10054/* ===================================
10055   POINT SUMMARY GRID (Profile Page)
10056   =================================== */
10057
10058.point-summary-grid {
10059  display: grid;
10060  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
10061  gap: 12px;
10062  margin-bottom: 32px;
10063}
10064
10065.point-summary-card {
10066  background: linear-gradient(135deg, #f9fafb 0%, #ffffff 100%);
10067  border: 1px solid #e5e7eb;
10068  border-radius: 12px;
10069  padding: 16px 20px;
10070  display: flex;
10071  align-items: center;
10072  justify-content: space-between;
10073  gap: 12px;
10074  transition: all 0.2s;
10075}
10076
10077.point-summary-card:hover {
10078  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
10079  transform: translateY(-2px);
10080}
10081
10082.point-summary-card .psc-left {
10083  display: flex;
10084  flex-direction: column;
10085  gap: 2px;
10086}
10087
10088.point-summary-card .psc-icon {
10089  font-size: 24px;
10090  margin-bottom: 4px;
10091}
10092
10093.point-summary-card .psc-label {
10094  font-size: 13px;
10095  color: #6b7280;
10096  font-weight: 500;
10097  text-transform: uppercase;
10098  letter-spacing: 0.5px;
10099}
10100
10101.point-summary-card .psc-value {
10102  font-size: 26px;
10103  font-weight: 700;
10104  color: #111827;
10105  flex-shrink: 0;
10106}
10107
10108/* Point History Section */
10109.point-history-section {
10110  margin-top: 32px;
10111}
10112
10113.point-history-section h3 {
10114  font-size: 18px;
10115  font-weight: 600;
10116  color: #111827;
10117  margin-bottom: 16px;
10118}
10119
10120.point-history-list {
10121  display: flex;
10122  flex-direction: column;
10123  gap: 12px;
10124}
10125
10126.point-history-item {
10127  display: grid;
10128  grid-template-columns: 40px 1fr auto;
10129  gap: 16px;
10130  padding: 16px;
10131  background: white;
10132  border: 1px solid #e5e7eb;
10133  border-radius: 10px;
10134  align-items: center;
10135  transition: all 0.2s;
10136}
10137
10138.point-history-item:hover {
10139  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
10140}
10141
10142.point-history-item.credit {
10143  border-left: 3px solid #10b981;
10144}
10145
10146.point-history-item.debit {
10147  border-left: 3px solid #f59e0b;
10148}
10149
10150.point-history-item .phi-icon {
10151  width: 40px;
10152  height: 40px;
10153  display: flex;
10154  align-items: center;
10155  justify-content: center;
10156  font-size: 20px;
10157  border-radius: 50%;
10158  background: #f9fafb;
10159}
10160
10161.point-history-item.credit .phi-icon {
10162  background: #d1fae5;
10163}
10164
10165.point-history-item.debit .phi-icon {
10166  background: #fef3c7;
10167}
10168
10169.point-history-item .phi-details {
10170  display: flex;
10171  flex-direction: column;
10172  gap: 4px;
10173}
10174
10175.point-history-item .phi-desc {
10176  font-size: 15px;
10177  font-weight: 500;
10178  color: #111827;
10179}
10180
10181.point-history-item .phi-date {
10182  font-size: 13px;
10183  color: #9ca3af;
10184}
10185
10186.point-history-item .phi-amount {
10187  font-size: 18px;
10188  font-weight: 700;
10189}
10190
10191.point-history-item .phi-amount.credit {
10192  color: #10b981;
10193}
10194
10195.point-history-item .phi-amount.debit {
10196  color: #f59e0b;
10197}
10198
10199@media (max-width: 640px) {
10200  .point-summary-grid {
10201    grid-template-columns: 1fr;
10202  }
10203
10204  .point-summary-card .psc-value {
10205    font-size: 22px;
10206  }
10207
10208  .point-history-item {
10209    grid-template-columns: 36px 1fr;
10210    gap: 12px;
10211  }
10212
10213  .point-history-item .phi-amount {
10214    grid-column: 2;
10215    text-align: right;
10216    margin-top: 8px;
10217  }
10218}
10219
10220/* ── Pending points: compact widget indicator ── */
10221.pbc-pending {
10222  font-size: 12px;
10223  opacity: 0.85;
10224  margin-top: 4px;
10225  color: #fde68a;
10226}
10227
10228/* ── Pending points: summary card ── */
10229.point-summary-card.psc-pending-card {
10230  background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);
10231  border-color: #fcd34d;
10232}
10233
10234.point-summary-card.psc-pending-card .psc-value {
10235  color: #b45309;
10236}
10237
10238.point-summary-card .psc-sub {
10239  font-size: 11px;
10240  color: #9ca3af;
10241  margin-top: 4px;
10242}
10243
10244/* ── Pending points: history item ── */
10245.point-history-item.phi-pending {
10246  opacity: 0.75;
10247  border-left-color: #fcd34d;
10248  background: #fffbeb;
10249}
10250
10251.point-history-item.phi-pending .phi-icon {
10252  background: #fef3c7;
10253}
10254
10255.phi-pending-badge {
10256  display: inline-block;
10257  font-size: 11px;
10258  font-weight: 600;
10259  padding: 2px 7px;
10260  border-radius: 10px;
10261  background: #fef3c7;
10262  color: #92400e;
10263  border: 1px solid #fcd34d;
10264  margin-left: 8px;
10265  vertical-align: middle;
10266}
10267
10268/* ── Reversed points: history item ── */
10269.point-history-item.phi-reversed {
10270  opacity: 0.55;
10271  border-left-color: #d1d5db;
10272  background: #f9fafb;
10273}
10274
10275.point-history-item.phi-reversed .phi-icon {
10276  background: #f3f4f6;
10277}
10278
10279.point-history-item.phi-reversed .phi-amount {
10280  text-decoration: line-through;
10281  color: #9ca3af !important;
10282}
10283
10284.phi-reversed-badge {
10285  display: inline-block;
10286  font-size: 11px;
10287  font-weight: 600;
10288  padding: 2px 7px;
10289  border-radius: 10px;
10290  background: #fee2e2;
10291  color: #991b1b;
10292  border: 1px solid #fca5a5;
10293  margin-left: 8px;
10294  vertical-align: middle;
10295}
10296
10297</style>
10298
10299</head>
10300<body>
10301<header class="topbar">
10302  <div class="container topbar-inner">
10303    <div class="topbar-left">
10304      Menghadirkan PIONTOGEL - Situs Alami Mendapatkan Cuan Dengan Permainan Resmi
10305    </div>
10306    <div class="topbar-right">
10307      <a href="https://calcourtsfitness.com/">PIONTOGEL</a>
10308      <a href="https://calcourtsfitness.com/">TOGEL ONLINE</a>
10309      <a href="https://calcourtsfitness.com/">TEBAK ANGKA</a>
10310      <a href="https://calcourtsfitness.com/">BANDAR TOGEL ONLINE</a>
10311    </div>
10312  </div>
10313</header>
10314<nav class="navbar">
10315  <nav class="navbar-logo" style="width: 100%;">
10316    <div class="container nav-inner" style="justify-items: center; justify-content: center;">
10317        <a href="https://calcourtsfitness.com/" class="logo-mobile" title="PIONTOGEL">
10318          <img src="https://a1.asdqwe.vip/assets/logo-piontogel.gif" alt="PIONTOGEL" height="40">
10319        </a>
10320    </div>
10321</nav>
10322  <div class="container nav-inner">
10323    <a href="https://calcourtsfitness.com/" class="logo" title="PIONTOGEL">
10324      <img src="https://a1.asdqwe.vip/assets/logo-piontogel.gif" alt="PIONTOGEL" height="40">
10325    </a>
10326
10327
10328    <form class="search" method="get" action="#">
10329      <input type="hidden" name="page" value="produk">
10330
10331      <!-- Search Icon (Desktop) -->
10332      <button type="submit" class="search-icon-btn" id="searchBtn" aria-label="Cari">
10333        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
10334          <circle cx="11" cy="11" r="8"/>
10335          <path d="m21 21-4.35-4.35"/>
10336        </svg>
10337      </button>
10338
10339      <input type="text" name="q" id="searchInput" placeholder="Ketik diGoogle PIONTOGEL?"
10340             value="" autocomplete="off">
10341
10342      <div class="search-suggest" id="searchSuggest" hidden></div>
10343    </form>
10344
10345    <div class="nav-actions">
10346      <!--<button class="icon-btn" id="notifBtn" title="Notifikasi">🔔</button>-->
10347      <button class="icon-btn cart-indicator" id="cartBtn" title="Keranjang">
10348        🛒 <span id="cartCount" class="badge" data-role="cart-badge" aria-live="polite">0</span>
10349      </button>
10350
10351      <button class="icon-btn compare-indicator" id="compareBtn" title="Perbandingan">
10352        ⚖️ <span id="compareCount" class="badge" data-role="compare-badge" aria-live="polite">0</span>
10353      </button>
10354
10355      <!-- Dynamic login/logout button -->
10356            <button class="login-btn" id="loginBtn" aria-label="Masuk">
10357        <span class="login-text">Masuk</span>
10358        <span class="login-ico" aria-hidden="true">😊</span>
10359      </button>
10360          </div>
10361  </div>
10362
10363  <div class="container" style="display: flex; align-items: center;">
10364
10365
10366      <div class="mega-cats-wrapper">
10367        <div class="mega-cats container" id="megaCatsScroll" style="padding-left: 5px !important;">
10368                      <a class="" href="https://calcourtsfitness.com/">
10369              PIONTOGEL           </a>
10370                      <a class="" href="https://calcourtsfitness.com/">
10371              PIONTOGEL LOGIN            </a>
10372                      <a class="" href="https://calcourtsfitness.com/">
10373              BANDAR TOGEL            </a>
10374                      <a class="" href="https://calcourtsfitness.com/">
10375              TEBAK ANGKA           </a>
10376                      <a class="" href="https://calcourtsfitness.com/">
10377              PIONTOGEL LINK            </a>
10378                      <a class="" href="https://calcourtsfitness.com/">
10379              BANDAR TOGEL ONLINE            </a>
10380                  </div>
10381        <div class="mega-cats-fade" id="megaCatsFade"></div>
10382      </div>
10383  </div>
10384</nav>
10385
10386<!-- Panel Mega Menu - Outside navbar for full-screen -->
10387<div class="mm2-panel" data-mm2="panel" role="dialog" aria-label="Kategori">
10388  <div class="mm2-view is-active" data-mm2="top"></div>
10389  <div class="mm2-view" data-mm2="detail"></div>
10390</div>
10391
10392<!-- Backdrop - Outside navbar for full-screen -->
10393<div class="mm2-backdrop" data-mm2="backdrop"></div>
10394
10395<!-- Mobile Bottom Navigation -->
10396<nav class="bottom-nav">
10397  <div class="bottom-nav-inner">
10398    <!-- Home -->
10399    <a href="/" class="bottom-nav-item" id="bottomHomeBtn" data-page="home">
10400      <div class="bottom-nav-item-icon">
10401        <!-- Outlined icon (default) -->
10402        <svg class="icon-outlined" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
10403          <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
10404          <polyline points="9 22 9 12 15 12 15 22"/>
10405        </svg>
10406        <!-- Filled icon (active) -->
10407        <svg class="icon-filled" viewBox="0 0 24 24" fill="currentColor">
10408          <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"/>
10409          <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"/>
10410        </svg>
10411      </div>
10412      <span class="bottom-nav-item-label">Home</span>
10413    </a>
10414
10415    <!-- Kategori -->
10416    <button class="bottom-nav-item" id="bottomCategoryBtn" data-mm2="trigger" aria-label="Kategori">
10417      <div class="bottom-nav-item-icon">
10418        <!-- Outlined icon (default) -->
10419        <svg class="icon-outlined" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
10420          <line x1="3" y1="12" x2="21" y2="12"/>
10421          <line x1="3" y1="6" x2="21" y2="6"/>
10422          <line x1="3" y1="18" x2="21" y2="18"/>
10423        </svg>
10424        <!-- Filled icon (active) -->
10425        <svg class="icon-filled" viewBox="0 0 24 24" fill="currentColor">
10426          <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"/>
10427        </svg>
10428      </div>
10429      <span class="bottom-nav-item-label">Kategori</span>
10430    </button>
10431
10432    <!-- Keranjang -->
10433    <button class="bottom-nav-item" id="bottomCartBtn">
10434      <div class="bottom-nav-item-icon">
10435        <!-- Outlined icon (default) -->
10436        <svg class="icon-outlined" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
10437          <circle cx="9" cy="21" r="1"/>
10438          <circle cx="20" cy="21" r="1"/>
10439          <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/>
10440        </svg>
10441        <!-- Filled icon (active) -->
10442        <svg class="icon-filled" viewBox="0 0 24 24" fill="currentColor">
10443          <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"/>
10444        </svg>
10445        <span class="bottom-nav-item-badge" id="bottomCartCount">0</span>
10446      </div>
10447      <span class="bottom-nav-item-label">Keranjang</span>
10448    </button>
10449
10450    <!-- Compare -->
10451    <button class="bottom-nav-item" id="bottomCompareBtn">
10452      <div class="bottom-nav-item-icon">
10453        <!-- Outlined icon (default) -->
10454        <svg class="icon-outlined" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
10455          <polyline points="17 11 21 7 17 3"/>
10456          <path d="M21 7H9"/>
10457          <polyline points="7 21 3 17 7 13"/>
10458          <path d="M15 17H3"/>
10459        </svg>
10460        <!-- Filled icon (active) -->
10461        <svg class="icon-filled" viewBox="0 0 24 24" fill="currentColor">
10462          <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"/>
10463        </svg>
10464        <span class="bottom-nav-item-badge" id="bottomCompareCount" style="display:none;">0</span>
10465      </div>
10466      <span class="bottom-nav-item-label">Compare</span>
10467    </button>
10468
10469    <!-- Chat - Always show, check login in JavaScript -->
10470    <button class="bottom-nav-item" id="bottomChatBtn" data-page="chat">
10471      <div class="bottom-nav-item-icon">
10472        <!-- Outlined icon (default) -->
10473        <svg class="icon-outlined" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" opacity="0.85">
10474          <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
10475        </svg>
10476        <!-- Filled icon (active) -->
10477        <svg class="icon-filled" viewBox="0 0 24 24" fill="currentColor">
10478          <path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/>
10479        </svg>
10480        <span class="bottom-nav-item-badge" id="bottomChatCount" style="display:none;">0</span>
10481      </div>
10482      <span class="bottom-nav-item-label">Chat</span>
10483    </button>
10484
10485    <!-- Akun -->
10486        <button class="bottom-nav-item" id="bottomLoginBtn">
10487      <div class="bottom-nav-item-icon">
10488        <!-- Outlined icon (default) -->
10489        <svg class="icon-outlined" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
10490          <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
10491          <circle cx="12" cy="7" r="4"/>
10492        </svg>
10493        <!-- Filled icon (active) -->
10494        <svg class="icon-filled" viewBox="0 0 24 24" fill="currentColor">
10495          <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"/>
10496        </svg>
10497      </div>
10498      <span class="bottom-nav-item-label">Login</span>
10499    </button>
10500      </div>
10501</nav>
10502
10503<!-- Mobile User Menu Bottom Sheet -->
10504
10505<script nonce="se3iyzEHMc9FQEB4wSyd0w==">
10506(function(){
10507  const btn = document.getElementById('megaMoreBtn');
10508  const dd  = document.getElementById('megaMoreDropdown');
10509  if(!btn || !dd) return;
10510
10511  function openDD(){
10512    dd.hidden = false;
10513    btn.setAttribute('aria-expanded','true');
10514    document.addEventListener('click', outside, {capture:true});
10515    document.addEventListener('keydown', esc);
10516  }
10517  function closeDD(){
10518    if(dd.hidden) return;
10519    dd.hidden = true;
10520    btn.setAttribute('aria-expanded','false');
10521    document.removeEventListener('click', outside, {capture:true});
10522    document.removeEventListener('keydown', esc);
10523  }
10524  function toggle(){ dd.hidden ? openDD() : closeDD(); }
10525  function outside(e){
10526    if(!dd.contains(e.target) && e.target !== btn){
10527      closeDD();
10528    }
10529  }
10530  function esc(e){
10531    if(e.key === 'Escape'){
10532      closeDD();
10533      btn.focus();
10534    }
10535  }
10536  btn.addEventListener('click', toggle);
10537})();
10538
10539// ========== HORIZONTAL SCROLL FADE INDICATOR ==========
10540(function(){
10541  const megaCats = document.getElementById('megaCatsScroll');
10542  const fade = document.getElementById('megaCatsFade');
10543  if (!megaCats || !fade) return;
10544
10545  function updateFade() {
10546    const scrollLeft = megaCats.scrollLeft;
10547    const scrollWidth = megaCats.scrollWidth;
10548    const clientWidth = megaCats.clientWidth;
10549    const maxScroll = scrollWidth - clientWidth;
10550
10551    // Hide fade when scrolled to end (within 10px tolerance)
10552    if (maxScroll - scrollLeft <= 10) {
10553      fade.style.opacity = '0';
10554    } else {
10555      fade.style.opacity = '1';
10556    }
10557  }
10558
10559  // Update on scroll
10560  megaCats.addEventListener('scroll', updateFade, { passive: true });
10561
10562  // Update on resize
10563  window.addEventListener('resize', updateFade, { passive: true });
10564
10565  // Initial check
10566  setTimeout(updateFade, 100);
10567})();
10568
10569document.addEventListener('DOMContentLoaded', function () {
10570  var input = document.getElementById('searchInput');
10571  if (!input) return;
10572  input.addEventListener('keydown', function (e) {
10573    if (e.key === 'Enter') {
10574      e.preventDefault();
10575      var form = input.form;
10576      if (!form) return;
10577      if (typeof form.requestSubmit === 'function') form.requestSubmit();
10578      else form.submit();
10579    }
10580  });
10581});
10582
10583
10584(function(){
10585  const cartBtn = document.getElementById('cartBtn');
10586  if (cartBtn) {
10587    cartBtn.addEventListener('click', function(e) {
10588      e.preventDefault();
10589
10590      const checkLogin = () => {
10591        return window.isUserLoggedIn ? window.isUserLoggedIn() : false;
10592      };
10593
10594      if (checkLogin()) {
10595        window.location.href = '/keranjang';
10596      } else {
10597        showCartLoginModal();
10598      }
10599    });
10600  }
10601
10602  const loginBtn = document.getElementById('loginBtn');
10603  if (loginBtn) {
10604    loginBtn.addEventListener('click', function(e) {
10605      e.preventDefault();
10606      window.location.href = 'https://calcourtsfitness.com/';
10607    });
10608  }
10609
10610  const userMenuBtn = document.getElementById('userMenuBtn');
10611  const userDropdown = document.getElementById('userDropdown');
10612
10613  if (userMenuBtn && userDropdown) {
10614    function openDropdown() {
10615      userDropdown.hidden = false;
10616      userMenuBtn.setAttribute('aria-expanded', 'true');
10617      document.addEventListener('click', closeOnOutside, { capture: true });
10618      document.addEventListener('keydown', closeOnEscape);
10619    }
10620
10621    function closeDropdown() {
10622      if (userDropdown.hidden) return;
10623      userDropdown.hidden = true;
10624      userMenuBtn.setAttribute('aria-expanded', 'false');
10625      document.removeEventListener('click', closeOnOutside, { capture: true });
10626      document.removeEventListener('keydown', closeOnEscape);
10627    }
10628
10629    function closeOnOutside(e) {
10630      if (!userDropdown.contains(e.target) && e.target !== userMenuBtn) {
10631        closeDropdown();
10632      }
10633    }
10634
10635    function closeOnEscape(e) {
10636      if (e.key === 'Escape') {
10637        closeDropdown();
10638        userMenuBtn.focus();
10639      }
10640    }
10641
10642    userMenuBtn.addEventListener('click', function(e) {
10643      e.preventDefault();
10644      userDropdown.hidden ? openDropdown() : closeDropdown();
10645    });
10646
10647    // Close dropdown when clicking profile links
10648    userDropdown.querySelectorAll('[data-close-dropdown]').forEach(link => {
10649      link.addEventListener('click', function() {
10650        closeDropdown();
10651      });
10652    });
10653  }
10654
10655  // Bottom nav profile button - toggle mobile bottom sheet
10656  const bottomProfileBtn = document.getElementById('bottomProfileBtn');
10657  const mobileUserSheet = document.getElementById('mobileUserSheet');
10658  const mobileUserSheetBackdrop = document.getElementById('mobileUserSheetBackdrop');
10659
10660  if (bottomProfileBtn && mobileUserSheet) {
10661    function openMobileSheet() {
10662      mobileUserSheet.hidden = false;
10663      document.body.style.overflow = 'hidden';
10664      // Trigger animation
10665      requestAnimationFrame(() => {
10666        mobileUserSheet.classList.add('active');
10667      });
10668    }
10669
10670    function closeMobileSheet() {
10671      mobileUserSheet.classList.remove('active');
10672      document.body.style.overflow = '';
10673      setTimeout(() => {
10674        mobileUserSheet.hidden = true;
10675      }, 300);
10676    }
10677
10678    bottomProfileBtn.addEventListener('click', function(e) {
10679      e.preventDefault();
10680      openMobileSheet();
10681    });
10682
10683    if (mobileUserSheetBackdrop) {
10684      mobileUserSheetBackdrop.addEventListener('click', closeMobileSheet);
10685    }
10686
10687    // Close on escape key
10688    document.addEventListener('keydown', function(e) {
10689      if (e.key === 'Escape' && !mobileUserSheet.hidden) {
10690        closeMobileSheet();
10691      }
10692    });
10693
10694    // Close sheet when clicking profile links
10695    mobileUserSheet.querySelectorAll('[data-close-sheet]').forEach(link => {
10696      link.addEventListener('click', function() {
10697        closeMobileSheet();
10698      });
10699    });
10700  }
10701
10702  // Handle Google avatar error in bottom-nav (fallback to initials)
10703  const bottomNavAvatar = document.querySelector('.bottom-nav-avatar-img');
10704  if (bottomNavAvatar) {
10705    bottomNavAvatar.addEventListener('error', function() {
10706      const initials = this.getAttribute('data-fallback-initials') || 'U';
10707      const initialsDiv = document.createElement('div');
10708      initialsDiv.className = 'bottom-nav-avatar-initials';
10709      initialsDiv.textContent = initials;
10710      this.parentNode.replaceChild(initialsDiv, this);
10711    });
10712  }
10713
10714  // Mobile logout button
10715  const mobileLogoutBtn = document.getElementById('mobileLogoutBtn');
10716  if (mobileLogoutBtn) {
10717    mobileLogoutBtn.addEventListener('click', async function(e) {
10718      e.preventDefault();
10719
10720      // Check push notification before logout
10721      if (window.ChatNotification && typeof window.ChatNotification.confirmLogoutWithPushNotification === 'function') {
10722        const confirmLogout = await window.ChatNotification.confirmLogoutWithPushNotification();
10723        if (!confirmLogout) {
10724          return; // User cancelled logout
10725        }
10726      }
10727
10728      const getCsrfToken = () => document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
10729
10730      try {
10731        const response = await fetch('/auth/logout', {
10732          method: 'POST',
10733          credentials: 'same-origin',
10734          headers: {
10735            'Content-Type': 'application/json',
10736            'X-CSRF-Token': getCsrfToken()
10737          },
10738          body: JSON.stringify({
10739            csrf_token: getCsrfToken()
10740          })
10741        });
10742
10743        if (response.ok) {
10744          if (window.guestCart && typeof window.guestCart.clear === 'function') {
10745            window.guestCart.clear();
10746          }
10747          window.location.href = '/login?logged_out=1';
10748        } else {
10749          console.error('Logout failed');
10750          window.location.href = '/logout';
10751        }
10752      } catch (error) {
10753        console.error('Logout error:', error);
10754        window.location.href = '/logout';
10755      }
10756    });
10757  }
10758
10759  const logoutBtn = document.getElementById('logoutBtn');
10760  if (logoutBtn) {
10761    logoutBtn.addEventListener('click', async function(e) {
10762      e.preventDefault();
10763
10764      // Check push notification before logout
10765      if (window.ChatNotification && typeof window.ChatNotification.confirmLogoutWithPushNotification === 'function') {
10766        const confirmLogout = await window.ChatNotification.confirmLogoutWithPushNotification();
10767        if (!confirmLogout) {
10768          return; // User cancelled logout
10769        }
10770      }
10771
10772      const getCsrfToken = () => document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
10773
10774      try {
10775        const response = await fetch('/auth/logout', {
10776          method: 'POST',
10777          credentials: 'same-origin',
10778          headers: {
10779            'Content-Type': 'application/json',
10780            'X-CSRF-Token': getCsrfToken()
10781          },
10782          body: JSON.stringify({
10783            csrf_token: getCsrfToken()
10784          })
10785        });
10786
10787        if (response.ok) {
10788          if (window.guestCart && typeof window.guestCart.clear === 'function') {
10789            window.guestCart.clear();
10790          }
10791
10792          window.location.href = '/login?logged_out=1';
10793        } else {
10794          console.error('Logout failed');
10795          window.location.href = '/logout';
10796        }
10797      } catch (error) {
10798        console.error('Logout error:', error);
10799        window.location.href = '/logout';
10800      }
10801    });
10802  }
10803
10804  const wishlistBtn = document.getElementById('wishlistBtn');
10805  const notifBtn = document.getElementById('notifBtn');
10806
10807  if (wishlistBtn) {
10808    wishlistBtn.addEventListener('click', function(e) {
10809      e.preventDefault();
10810      alert('Fitur wishlist akan segera tersedia!');
10811    });
10812  }
10813
10814  if (notifBtn) {
10815    notifBtn.addEventListener('click', function(e) {
10816      e.preventDefault();
10817      alert('Fitur notifikasi akan segera tersedia!');
10818    });
10819  }
10820})();
10821
10822// ========== MOBILE BOTTOM NAVIGATION ==========
10823(function initBottomNav() {
10824  // Sync cart badge with bottom nav
10825  function syncBottomCartBadge() {
10826    const mainBadge = document.getElementById('cartCount');
10827    const bottomBadge = document.getElementById('bottomCartCount');
10828    if (mainBadge && bottomBadge) {
10829      const count = mainBadge.textContent;
10830      bottomBadge.textContent = count;
10831      bottomBadge.style.display = parseInt(count) > 0 ? 'flex' : 'none';
10832    }
10833  }
10834
10835  // Sync compare badge with bottom nav
10836  function syncBottomCompareBadge() {
10837    const mainBadge = document.getElementById('compareCount');
10838    const bottomBadge = document.getElementById('bottomCompareCount');
10839    if (mainBadge && bottomBadge) {
10840      const count = mainBadge.textContent;
10841      bottomBadge.textContent = count;
10842      bottomBadge.style.display = parseInt(count) > 0 ? 'flex' : 'none';
10843    }
10844  }
10845
10846  // Initial sync
10847  syncBottomCartBadge();
10848  syncBottomCompareBadge();
10849
10850  // Observe badge changes
10851  const observer = new MutationObserver(function() {
10852    syncBottomCartBadge();
10853    syncBottomCompareBadge();
10854  });
10855
10856  const cartBadge = document.getElementById('cartCount');
10857  const compareBadge = document.getElementById('compareCount');
10858
10859  if (cartBadge) observer.observe(cartBadge, { childList: true, characterData: true, subtree: true });
10860  if (compareBadge) observer.observe(compareBadge, { childList: true, characterData: true, subtree: true });
10861
10862  // Bottom Cart Button
10863  const bottomCartBtn = document.getElementById('bottomCartBtn');
10864  if (bottomCartBtn) {
10865    bottomCartBtn.addEventListener('click', function(e) {
10866      e.preventDefault();
10867
10868      const checkLogin = () => {
10869        return window.isUserLoggedIn ? window.isUserLoggedIn() : false;
10870      };
10871
10872      if (checkLogin()) {
10873        window.location.href = '/keranjang';
10874      } else {
10875        showCartLoginModal();
10876      }
10877    });
10878  }
10879
10880  // Bottom Compare Button
10881  const bottomCompareBtn = document.getElementById('bottomCompareBtn');
10882  if (bottomCompareBtn) {
10883    bottomCompareBtn.addEventListener('click', function(e) {
10884      e.preventDefault();
10885
10886      // Close chat widget if open
10887      if (window.plazaChat && window.plazaChat.isWidgetOpen) {
10888        window.plazaChat.closeWidget();
10889      }
10890
10891      const mainCompareBtn = document.getElementById('compareBtn');
10892      if (mainCompareBtn) {
10893        mainCompareBtn.click();
10894      }
10895    });
10896  }
10897
10898  // Bottom Category Button - triggers mega menu
10899  const bottomCategoryBtn = document.getElementById('bottomCategoryBtn');
10900  if (bottomCategoryBtn) {
10901    bottomCategoryBtn.addEventListener('click', function(e) {
10902      e.preventDefault();
10903      // Trigger desktop mega menu
10904      const desktopTrigger = document.querySelector('.mm2-trigger:not(#bottomCategoryBtn)');
10905      if (desktopTrigger) {
10906        desktopTrigger.click();
10907      }
10908    });
10909  }
10910
10911  // Bottom Login Button
10912  const bottomLoginBtn = document.getElementById('bottomLoginBtn');
10913  if (bottomLoginBtn) {
10914    bottomLoginBtn.addEventListener('click', function(e) {
10915      e.preventDefault();
10916      window.location.href = '/login';
10917    });
10918  }
10919
10920  // Bottom Chat Button
10921  const bottomChatBtn = document.getElementById('bottomChatBtn');
10922  if (bottomChatBtn) {
10923    bottomChatBtn.addEventListener('click', function(e) {
10924      e.preventDefault();
10925
10926      // Trigger chat widget (try multiple times if needed)
10927      const triggerChat = () => {
10928        const chatToggle = document.getElementById('chat-toggle-btn');
10929        if (chatToggle) {
10930          chatToggle.click();
10931          return true;
10932        }
10933        return false;
10934      };
10935
10936      // Immediate attempt
10937      if (!triggerChat()) {
10938        // Retry after 100ms if not found
10939        setTimeout(() => {
10940          if (!triggerChat()) {
10941            // Last resort: manually show popup
10942            const popup = document.getElementById('chat-popup');
10943            if (popup) {
10944              popup.style.display = 'block';
10945            }
10946          }
10947        }, 100);
10948      }
10949    });
10950  }
10951
10952  // Sync chat unread badge
10953  function syncBottomChatBadge() {
10954    const chatBadge = document.getElementById('chat-unread-badge');
10955    const bottomChatBadge = document.getElementById('bottomChatCount');
10956    if (chatBadge && bottomChatBadge) {
10957      const count = chatBadge.textContent;
10958      bottomChatBadge.textContent = count;
10959      bottomChatBadge.style.display = chatBadge.style.display;
10960    }
10961  }
10962
10963  // Observe chat badge changes
10964  const chatBadge = document.getElementById('chat-unread-badge');
10965  if (chatBadge) {
10966    observer.observe(chatBadge, { attributes: true, childList: true, characterData: true, subtree: true });
10967    syncBottomChatBadge();
10968  }
10969
10970  // Active state management based on current page
10971  function updateActiveState() {
10972    const currentPath = window.location.pathname;
10973    const bottomNavItems = document.querySelectorAll('.bottom-nav-item');
10974
10975    bottomNavItems.forEach(item => {
10976      item.classList.remove('active');
10977
10978      const itemId = item.id;
10979      const page = item.getAttribute('data-page');
10980      const href = item.getAttribute('href');
10981
10982      // Check by ID for specific pages
10983      if (itemId === 'bottomHomeBtn' && currentPath === '/') {
10984        item.classList.add('active');
10985      } else if (itemId === 'bottomCartBtn' && (currentPath === '/keranjang' || currentPath === '/cart')) {
10986        item.classList.add('active');
10987      } else if (itemId === 'bottomProfileBtn' && currentPath.startsWith('/profile')) {
10988        item.classList.add('active');
10989      } else if (itemId === 'bottomCategoryBtn' && currentPath.startsWith('/kategori')) {
10990        item.classList.add('active');
10991      } else if (itemId === 'bottomCompareBtn' && currentPath === '/compare') {
10992        item.classList.add('active');
10993      } else if (itemId === 'bottomChatBtn' && currentPath === '/chat') {
10994        item.classList.add('active');
10995      }
10996      // Fallback: check by data-page attribute
10997      else if (page === 'home' && currentPath === '/') {
10998        item.classList.add('active');
10999      } else if (page === 'profile' && currentPath.startsWith('/profile')) {
11000        item.classList.add('active');
11001      }
11002      // Fallback: check by href
11003      else if (href && currentPath === href) {
11004        item.classList.add('active');
11005      }
11006    });
11007  }
11008
11009  // Update active state on load
11010  updateActiveState();
11011
11012  // Bottom nav always visible - auto-hide disabled for better UX
11013  // const bottomNav = document.querySelector('.bottom-nav');
11014  // if (bottomNav && window.innerWidth <= 640) {
11015  //   // Auto-hide feature disabled
11016  // }
11017})();
11018// Cart Login Modal Handler (Global scope)
11019function showCartLoginModal() {
11020  const modal = document.getElementById('cartLoginModal');
11021  if (!modal) return;
11022
11023  modal.classList.add('show');
11024
11025  const confirmBtn = document.getElementById('cartLoginConfirm');
11026  const registerBtn = document.getElementById('cartRegisterBtn');
11027  const cancelBtn = document.getElementById('cartLoginCancel');
11028
11029  const handleLogin = () => {
11030    window.location.href = '/login?return=' + encodeURIComponent('/keranjang');
11031  };
11032
11033  const handleRegister = () => {
11034    window.location.href = '/register?return=' + encodeURIComponent('/keranjang');
11035  };
11036
11037  const handleCancel = () => {
11038    modal.classList.remove('show');
11039    confirmBtn.removeEventListener('click', handleLogin);
11040    registerBtn.removeEventListener('click', handleRegister);
11041    cancelBtn.removeEventListener('click', handleCancel);
11042  };
11043
11044  confirmBtn.removeEventListener('click', handleLogin);
11045  registerBtn.removeEventListener('click', handleRegister);
11046  cancelBtn.removeEventListener('click', handleCancel);
11047
11048  confirmBtn.addEventListener('click', handleLogin);
11049  registerBtn.addEventListener('click', handleRegister);
11050  cancelBtn.addEventListener('click', handleCancel);
11051
11052  modal.addEventListener('click', (e) => {
11053    if (e.target === modal) {
11054      handleCancel();
11055    }
11056  });
11057}
11058</script>
vendor: 2 bytes, line 11058
11058
11059<script data-inlined="cart-badge.js">
11060(function(){
11061  function selBadge(){
11062    return document.querySelector('[data-role="cart-badge"]') || document.getElementById('cartCount');
11063  }
11064  function setBadge(n){
11065    const el = selBadge();
11066    if (!el) return;
11067    const count = Math.max(0, parseInt(n||0,10) || 0);
11068    el.textContent = String(count);
11069    // Sembunyikan jika 0? Jika ingin selalu terlihat, hapus 2 baris di bawah
11070    if (count <= 0) { el.classList.add('is-empty'); }
11071    else { el.classList.remove('is-empty'); }
11072  }
11073  async function refresh(){
11074    try{
11075      // Primary: Use server-side endpoint (handles both logged and guest users)
11076      const res = await fetch('/api/cart/get-simple.php', { credentials:'same-origin' });
11077      if (res.ok) {
11078        const data = await res.json();
11079        if (data && data.ok) {
11080          setBadge(data.items_count || 0);
11081          return;
11082        }
11083      }
11084
11085      // Fallback hanya untuk guest user: check localStorage
11086      const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true' ||
11087                        document.body.classList.contains('logged-in') ||
11088                        window.userLoggedIn === true;
11089
11090      if (!isLoggedIn && window.guestCart && typeof window.guestCart.getItemsCount === 'function') {
11091        setBadge(window.guestCart.getItemsCount());
11092      }
11093    }catch(e){
11094      // Final fallback hanya untuk guest
11095      const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true';
11096      if (!isLoggedIn) {
11097        try {
11098          const guestCartData = JSON.parse(localStorage.getItem('plazait_guest_cart') || '{"items":{}}');
11099          const items = guestCartData.items || {};
11100          const count = Object.values(items).reduce((total, item) => total + (item.qty || 0), 0);
11101          setBadge(count);
11102        } catch(e2) {
11103          setBadge(0);
11104        }
11105      } else {
11106        setBadge(0);
11107      }
11108    }
11109  }
11110
11111  // Ekspos untuk dipanggil dari skrip lain (opsional)
11112  window.CartBadge = { set: setBadge, refresh };
11113  window.updateCartCounter = refresh; // Alias for compatibility
11114
11115  // Update saat halaman load
11116  if (document.readyState === 'loading') {
11117    document.addEventListener('DOMContentLoaded', refresh);
11118  } else {
11119    refresh();
11120  }
11121
11122  // Dengarkan event global dari add/remove/update
11123  document.addEventListener('cart:updated', function(ev){
11124    const c = ev && ev.detail && typeof ev.detail.items_count === 'number'
11125      ? ev.detail.items_count : null;
11126    if (c !== null) setBadge(c);
11127    else refresh();
11128  });
11129
11130  // Dengarkan event khusus cart remove untuk update badge
11131  document.addEventListener('cart:removed', function(ev){
11132    // Langsung refresh untuk memastikan data terbaru
11133    refresh();
11134  });
11135
11136  // Jika addToCart sudah ada sebelum file ini, coba wrap agar badge ter-update.
11137  const prevAdd = window.addToCart;
11138  if (typeof prevAdd === 'function'){
11139    window.addToCart = async function(pid, qty, vid, meta){
11140      const res = await prevAdd(pid, qty, vid, meta);
11141      try{
11142        const cnt = res && res.cart ? res.cart.items_count : null;
11143        if (cnt !== null) setBadge(cnt);
11144        // tetap siarkan event agar listener lain ikut sync
11145        document.dispatchEvent(new CustomEvent('cart:updated', { detail: res.cart || {} }));
11146      }catch(_){}
11147      return res;
11148    };
11149  }
11150
11151  // Helper function to check if user is logged in
11152  function isUserLoggedIn() {
11153    const metaTag = document.querySelector('meta[name="user-logged-in"]');
11154    return metaTag ? metaTag.getAttribute('content') === 'true' : false;
11155  }
11156
11157  // Make login check globally accessible
11158  window.isUserLoggedIn = isUserLoggedIn;
11159})();
11160</script>
11160
11161
11162<!-- Product Compare Sidebar -->
11163<div class="compare-sidebar" id="compareSidebar">
11164  <div class="compare-header">
11165    <h4 class="compare-title">
11166      <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
11167        <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"/>
11168      </svg>
11169      Bandingkan Produk
11170      <span class="compare-count" id="compareCountSidebar">(0)</span>
11171    </h4>
11172    <button class="compare-close" id="compareClose" aria-label="Tutup">
11173      <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
11174        <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"/>
11175      </svg>
11176    </button>
11177  </div>
11178
11179  <div class="compare-content">
11180    <div class="compare-empty" id="compareEmpty">
11181      <div class="empty-icon">
11182        <svg width="48" height="48" viewBox="0 0 24 24" fill="currentColor" opacity="0.3">
11183          <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"/>
11184        </svg>
11185      </div>
11186      <h5>Belum ada produk dipilih</h5>
11187      <p>Pilih produk yang ingin dibandingkan dengan klik tombol "Bandingkan Produk" di halaman produk.</p>
11188    </div>
11189
11190    <div class="compare-list" id="compareList">
11191      <!-- Products will be populated here -->
11192    </div>
11193  </div>
11194
11195  <div class="compare-actions" id="compareActions">
11196    <button class="btn btn-outline-danger btn-sm" id="compareClearAll">
11197      <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
11198        <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"/>
11199      </svg>
11200      Hapus Semua
11201    </button>
11202    <button class="btn btn-primary btn-sm" id="compareAnalyze" disabled>
11203      <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
11204        <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"/>
11205      </svg>
11206      Analisis AI
11207    </button>
11208  </div>
11209
11210  <!-- AI Disclaimer in Sidebar -->
11211  <div class="compare-disclaimer">
11212    <div class="ai-disclaimer">
11213      <div class="disclaimer-text">
11214        <strong>💡 Catatan:</strong> Analisis ini dihasilkan secara otomatis dan dapat memiliki keterbatasan.
11215        Gunakan sebagai referensi tambahan dengan tetap mempertimbangkan kebutuhan Anda.
11216        Untuk informasi lebih lanjut, silakan hubungi tim PIONTOGEL.
11217      </div>
11218    </div>
11219  </div>
11220</div><style data-inlined="pd-lightbox.css">
11221/* PlazaIT Product Lightbox (vanilla, no CDN) — v4 */
11222.pd-lb {
11223  position: fixed; inset: 0; z-index: 99999;
11224  background: rgba(0,0,0,.95);
11225  display: none;
11226  color: #fff;
11227  /* Transition untuk swipe-down feedback */
11228  transition: transform .18s ease, opacity .18s ease;
11229}
11230.pd-lb.show { display: block; }
11231
11232/* Canvas di bawah (z:1), Controls di atas (z:2) */
11233.pd-lb-canvas {
11234  position: absolute; inset: 56px 0 56px 0;
11235  display: flex; align-items: center; justify-content: center;
11236  overflow: hidden;
11237  touch-action: none;
11238  z-index: 1;
11239}
11240
11241.pd-lb-img {
11242  max-width: 96vw; max-height: 86vh;
11243  will-change: transform;
11244  user-select: none;
11245  pointer-events: none;
11246  transform-origin: 50% 50%;
11247}
11248
11249.pd-lb-close, .pd-lb-prev, .pd-lb-next, .pd-lb-counter {
11250  position: absolute;
11251  z-index: 2;
11252}
11253
11254.pd-lb-close, .pd-lb-prev, .pd-lb-next {
11255  background: rgba(17,24,39,.7);
11256  color: #fff; border: 0; cursor: pointer;
11257  width: 40px; height: 40px; border-radius: 999px;
11258  display: inline-flex; align-items: center; justify-content: center;
11259  transition: background-color .15s ease, transform .05s ease;
11260  -webkit-tap-highlight-color: transparent;
11261}
11262.pd-lb-close:hover, .pd-lb-prev:hover, .pd-lb-next:hover { background: rgba(31,41,55,.85); }
11263.pd-lb-close:active, .pd-lb-prev:active, .pd-lb-next:active { transform: translateY(.5px); }
11264
11265.pd-lb-close { top: max(12px, env(safe-area-inset-top, 12px)); right: max(12px, env(safe-area-inset-right, 12px)); }
11266.pd-lb-prev  { top: 50%; left:  10px; transform: translateY(-50%); }
11267.pd-lb-next  { top: 50%; right: 10px; transform: translateY(-50%); }
11268
11269.pd-lb-counter {
11270  top: max(16px, env(safe-area-inset-top, 16px)); left: 50%;
11271  transform: translateX(-50%);
11272  font-weight: 700; font-size: 13px; letter-spacing: .4px;
11273  background: rgba(17,24,39,.65);
11274  padding: 6px 10px; border-radius: 999px; border: 1px solid rgba(255,255,255,.1);
11275}
11276
11277@media (max-width: 640px){
11278  .pd-lb-img { max-width: 98vw; max-height: 80vh; }
11279  .pd-lb-prev, .pd-lb-next { width: 36px; height: 36px; }
11280  .pd-lb-close { width: 38px; height: 38px; }
11281}
11282</style>
11283<style>.paylater-promo{display:inline-flex;align-items:center;gap:8px;background:linear-gradient(135deg,#f0fdf4 0,#dcfce7 100%);border:1px solid #86efac;border-radius:8px;padding:8px 12px;font-size:13px;line-height:1.4;margin:8px 0}.paylater-promo-sm{padding:6px 10px;font-size:11px;gap:6px}.paylater-promo-md{padding:8px 12px;font-size:13px;gap:8px}.paylater-promo-lg{padding:10px 16px;font-size:14px;gap:10px}.paylater-icon{font-size:1.2em;flex-shrink:0}.paylater-text{display:flex;flex-direction:column;gap:2px}.paylater-text strong{color:#15803d;font-weight:600}.paylater-text small{color:#16a34a;font-size:.85em;opacity:.9}.paylater-desktop-only{display:none}.paylater-mobile-only{display:flex}@media (min-width:641px){.paylater-desktop-only{display:flex}.paylater-mobile-only{display:none}}@media (max-width:640px){.paylater-promo{font-size:12px;padding:7px 10px}.paylater-promo-lg{font-size:13px;padding:8px 12px}}@media print{.paylater-promo{display:none!important}}.paylater-compact{display:inline-block;font-size:11px;font-weight:600;color:#059669;background:#d1fae5;padding:3px 8px;border-radius:4px;margin:6px 0 4px;line-height:1.3}@media (max-width:640px){.paylater-compact{font-size:10px;padding:2px 6px;margin:5px 0 3px}}@media print{.paylater-compact{display:none!important}}</style><style nonce="se3iyzEHMc9FQEB4wSyd0w==">.pd-voucher-minimal{margin:10px 0;display:flex;flex-direction:column;gap:6px}.pdv-min-item{display:flex;align-items:center;gap:8px;padding:0;height:32px;transition:all .2s ease}.pdv-min-hidden{display:none}.pdv-min-disabled{opacity:.5}.pdv-badge-btn{display:inline-flex;align-items:center;gap:8px;background:linear-gradient(135deg,#667eea 0,#764ba2 100%);border:none;border-radius:8px;padding:3px;cursor:pointer;transition:all .2s ease;width:auto}.pdv-badge-btn:hover:not(:disabled){transform:translateY(-1px);box-shadow:0 4px 12px rgba(102,126,234,.3)}.pdv-badge-btn:disabled{opacity:.6;cursor:not-allowed}.pdv-badge-icon{color:#fff;flex-shrink:0}.pdv-badge-text{font-size:14px;font-weight:700;color:#fff;white-space:nowrap}.pdv-badge-code{font-family:'Courier New',monospace;font-size:13px;font-weight:700;color:#fff;background:rgba(255,255,255,.2);padding:4px 8px;border-radius:4px;border:none;white-space:nowrap}.pdv-min-copy,.pdv-min-more{padding:4px 12px;font-size:12px;font-weight:600;border-radius:5px;border:1px solid #e2e8f0;background:#fff;color:#667eea;cursor:pointer;transition:all .2s ease;white-space:nowrap}.pdv-min-copy:hover:not(:disabled),.pdv-min-more:hover{background:#667eea;color:#fff;border-color:#667eea}.pdv-min-copy:disabled{opacity:.4;cursor:not-allowed}.pdv-min-copy.copied{background:#22c55e;color:#fff;border-color:#22c55e}.pdv-min-more{margin-left:auto}.modal-close{position:absolute;top:20px;right:20px;background:none;border:none;cursor:pointer;color:#64748b;padding:4px;transition:color .2s ease}.modal-close:hover{color:#1e293b}.modal-body{margin-bottom:8px}.voucher-modal-content{text-align:center}.voucher-modal-badge{display:inline-flex;flex-direction:row;align-items:center;gap:6px;background:linear-gradient(135deg,#667eea 0,#764ba2 100%);border-radius:10px;padding:8px 16px;margin-bottom:12px}.vmb-value{font-size:18px;font-weight:700;color:#fff;line-height:1}.vmb-label{font-size:18px;font-weight:700;color:#fff;text-transform:uppercase;letter-spacing:.5px}.voucher-modal-code{font-family:'Courier New',monospace;font-size:14px;font-weight:700;color:#667eea;background:rgba(102,126,234,.1);padding:6px 12px;border-radius:6px;border:2px dashed rgba(102,126,234,.3);display:inline-block;margin-bottom:10px}.voucher-modal-name{font-size:14px;font-weight:600;color:#1e293b;margin-bottom:16px}.voucher-modal-details{text-align:left;background:#f8fafc;border-radius:8px;padding:12px;margin-bottom:16px}.vmd-item{display:flex;align-items:flex-start;gap:8px;padding:6px 0;font-size:13px;color:#64748b;border-bottom:1px solid #e2e8f0}.vmd-item:last-child{border-bottom:none}.vmd-item svg{color:#667eea;flex-shrink:0;margin-top:2px}.vmd-item strong{color:#1e293b}.modal-actions{padding:20px}.modal-btn-primary{background:linear-gradient(135deg,#667eea 0,#764ba2 100%);color:#fff}.modal-btn-primary:hover{opacity:.9;transform:translateY(-1px)}@media (max-width:640px){.pdv-min-item{gap:6px}.pdv-badge-btn{padding:3px;gap:6px}.pdv-badge-text{font-size:13px}.pdv-badge-code{font-size:12px;padding:3px 6px}.pdv-min-copy,.pdv-min-more{padding:3px 10px;font-size:11px}.modal-dialog{max-width:95%;margin:20px}.vmb-value{font-size:16px}.vmb-label{font-size:16px}.voucher-modal-code{font-size:13px;padding:5px 10px}}</style><style nonce="se3iyzEHMc9FQEB4wSyd0w==">.promo-endyear-mini-banner{display:flex;align-items:center;gap:12px;padding:12px 16px;background:rgba(255,255,255,.8);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:2px solid rgba(242,140,26,.2);border-radius:12px;margin:16px 0;box-shadow:0 4px 16px rgba(0,0,0,.08);transition:all .3s ease}.promo-endyear-mini-banner:hover{transform:translateY(-2px);box-shadow:0 6px 20px rgba(0,0,0,.12);border-color:rgba(242,140,26,.4)}.pey-icon{font-size:32px;line-height:1;flex-shrink:0}.pey-content{flex:1;min-width:0}.pey-content strong{display:block;font-size:14px;font-weight:700;color:#F28C1A;margin-bottom:2px}
11283.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>
11284<style nonce="se3iyzEHMc9FQEB4wSyd0w==">
11285.pd-title-row{position:relative;margin-bottom:12px;padding-right:52px}.pd-title{margin:0!important}.pd-share-btn{position:absolute;top:0;right:0;width:40px;height:40px;display:inline-flex;align-items:center;justify-content:center;border-radius:10px;border:1px solid #e2e8f0;background:#fff;color:#64748b;cursor:pointer;transition:all .2s ease;-webkit-tap-highlight-color:transparent}.pd-share-btn:hover{background:#f8fafc;border-color:#cbd5e1;color:#0f172a;transform:translateY(-1px)}.pd-share-btn:active{transform:translateY(0)}.pd-share-btn svg{display:block}
11286
11287.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)}}
11288
11289.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}
11290
11291.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}
11292
11293.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)}
11294
11295.pd-share-icon{width:40px;height:40px;border-radius:10px;display:inline-flex;align-items:center;justify-content:center;position:relative}.pd-share-icon svg{width:24px;height:24px;display:block}.pd-share-icon-whatsapp{background:linear-gradient(135deg,#25D366 0%,#128C7E 100%);color:#fff}.pd-share-icon-facebook{background:linear-gradient(135deg,#1877F2 0%,#0c63d4 100%);color:#fff}.pd-share-icon-twitter{background:linear-gradient(135deg,#1DA1F2 0%,#0d8bd9 100%);color:#fff}.pd-share-icon-telegram{background:linear-gradient(135deg,#0088cc 0%,#0077b3 100%);color:#fff}.pd-share-icon-email{background:linear-gradient(135deg,#EA4335 0%,#c5331f 100%);color:#fff}.pd-share-icon-copy{background:linear-gradient(135deg,#f1db63 0%,#e5d146 100%);color:#fff}.pd-share-icon-pdf{background:linear-gradient(135deg,#ef4444 0%,#dc2626 100%);color:#fff}.pd-share-name{font-size:12px;font-weight:600;color:#475569;text-align:center}
11296
11297.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)}
11297.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}
11298
11299@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}}
11300
11301.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}
11302
11303@media(max-width:640px){.pd-share-btn{display:none!important}}
11304
11305.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}
11306
11307.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}
11308
11309.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}
11310
11311.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}
11312
11313
11314.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}
11315
11316.sp-wa{
11317  grid-column:3; justify-self:end;
11318  width:32px; height:32px; min-width:32px; min-height:32px;
11319  box-sizing:border-box;
11320  padding:0; line-height:0; text-decoration:none; outline:0;
11321  display:inline-flex; align-items:center; justify-content:center;
11322  border-radius:999px; border:1px solid rgba(37,211,102,.60);  /* 1px untuk hasil “crisp” */
11323  background:rgba(37,211,102,.08); color:#25D366;
11324  transition:background-color .15s ease, border-color .15s ease, transform .05s ease;
11325  -webkit-tap-highlight-color:transparent;
11326}
11327.sp-wa:hover{background:rgba(37,211,102,.16);border-color:rgba(37,211,102,.75)}
11328.sp-wa:active{transform:translateY(.5px) scale(.98)}
11329
11330.sp-wa svg{width:18px;height:18px;display:block;vertical-align:middle}
11331.stock-popover a{text-decoration:none}
11332
11333#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}}
11334
11335.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}}
11336
11337.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}}
11338
11339.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}
11340.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)}}
11341.pwp-deals-grid{display:flex;flex-direction:column;gap:6px}.pwp-deal-card{background:#fff;border-radius:6px;overflow:hidden;border:1px solid #fbbf24;display:flex;align-items:center;padding:8px;gap:10px}.pwp-deal-card:hover{background:#fef3c7}.pwp-deal-thumb{flex-shrink:0;width:50px;height:50px;background:#f8fafc;overflow:hidden;border-radius:4px}.pwp-deal-thumb img{width:100%;height:100%;object-fit:cover}.pwp-deal-badge{background:#dc2626;color:#fff;padding:2px 5px;border-radius:3px;font-size:9px;font-weight:700;margin-left:auto}.pwp-deal-info{flex:1;display:flex;flex-direction:column;gap:4px}.pwp-deal-name{font-size:12px;font-weight:600;color:#0f172a;margin:0;line-height:1.2}.pwp-deal-prices{display:flex;align-items:center;gap:6px;flex-wrap:wrap}.pwp-deal-price{font-size:14px;font-weight:800;color:#dc2626;white-space:nowrap}.pwp-deal-original{font-size:11px;color:#64748b;text-decoration:line-through;white-space:nowrap}.pwp-cart-note{margin-top:8px;padding:6px 10px;background:#e0f2fe;border-left:2px solid#0ea5e9;font-size:11px;color:#0c4a6e;border-radius:4px}
11342@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}}
11343
11344.preorder-badge{display:inline-flex;align-items:center;gap:5px;padding:6px 12px;background:linear-gradient(135deg,#8b5cf6 0%,#7c3aed 100%);color:#fff;border-radius:8px;font-size:13px;font-weight:700;border:0;box-shadow:0 2px 8px rgba(139,92,246,.25)}.preorder-badge svg{width:16px;height:16px;flex-shrink:0}.preorder-info{margin:12px 0;padding:14px;background:linear-gradient(135deg,#f5f3ff 0%,#ede9fe 100%);border:1.5px solid #c4b5fd;border-radius:12px}.preorder-info-header{display:flex;align-items:center;gap:8px;margin-bottom:10px}.preorder-info-icon{width:20px;height:20px;color:#7c3aed;flex-shrink:0}.preorder-info-title{font-size:14px;font-weight:700;color:#5b21b6;margin:0}.preorder-info-items{display:flex;flex-direction:column;gap:8px}.preorder-info-row{display:flex;align-items:center;justify-content:space-between;font-size:13px}.preorder-info-label{color:#6b21a8;font-weight:600}.preorder-info-value{color:#5b21b6;font-weight:700}.preorder-info-value.highlight{color:#dc2626;font-size:14px}.preorder-info-note{margin-top:10px;padding:8px 10px;background:#fef3c7;border-left:2px solid #f59e0b;font-size:12px;color:#78350f;border-radius:6px;line-height:1.4}@media(max-width:640px){.preorder-badge{font-size:12px;padding:5px 10px}.preorder-info{margin:10px 0;padding:12px}.preorder-info-title{font-size:13px}.preorder-info-row{font-size:12px}.preorder-info-note{font-size:11px;padding:6px 8px}}
11345</style>
11346
11347<div class="pd-sticky-header" id="pd-sticky-header"><div class="pd-sticky-header-container"><div class="pd-sticky-thumb"><img src="https://a1.asdqwe.vip/banner/piontogel-img1" alt="PIONTOGEL" id="pd-sticky-thumb-img"></div><div class="pd-sticky-info"><h2 class="pd-sticky-title" id="pd-sticky-title">
11347Menghadirkan PIONTOGEL - Situs Alami Mendapatkan Cuan Dengan Permainan Resmi</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>
11348
11349<main class="product-single product-single--v3"><nav class="pd-breadcrumb" aria-label="breadcrumb"><a href="https://calcourtsfitness.com/">PIONTOGEL</a><span>-</span><a href="https://calcourtsfitness.com/">TOGEL ONLINE</a><span>-</span><a href="https://calcourtsfitness.com/">BANDAR TOGEL</a><span>-</span><a href="https://calcourtsfitness.com/">TEBAK ANGKA </a><span>-</span><a href="https://calcourtsfitness.com/">BANDAR TOGEL ONLINE</a><span>-</span><span class="current">Menghadirkan PIONTOGEL - Situs Alami Mendapatkan Cuan Dengan Permainan Resmi
11350
11351</span></nav>
11352
11353<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://a1.asdqwe.vip/banner/piontogel-img1" alt="TOTO TOGEL" data-slug="" loading="eager">
11354</figure>
11355
11356<div class="pd-mobile-slider-wrapper">
11357<div class="pd-mobile-slider" id="pd-mobile-slider" aria-label="Galeri gambar (geser)"><div class="pd-mslide"><img src="https://a1.asdqwe.vip/banner/piontogel-img1" alt="TOGEL ONLINE" loading="eager"></div><div class="pd-mslide"><img src="https://a1.asdqwe.vip/banner/piontogel-img1" alt="TOGEL ONLINE" loading="lazy"></div><div class="pd-mslide"><img src="https://a1.asdqwe.vip/banner/piontogel-img1" alt="TOGEL ONLINE" loading="lazy"></div><div class="pd-mslide"><img src="https://a1.asdqwe.vip/banner/piontogel-img1" alt="TOGEL ONLINE" loading="lazy"></div><div class="pd-mslide"><img src="https://a1.asdqwe.vip/banner/piontogel-img1" alt="TOGEL ONLINE" loading="lazy"></div><div class="pd-mslide"><img src="https://a1.asdqwe.vip/banner/piontogel-img1" alt="TOGEL ONLINE" loading="lazy"></div></div><div class="pd-gallery-counter" id="pd-gallery-counter">1/6</div></div>
11358<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://a1.asdqwe.vip/banner/piontogel-img1" data-alt="TOGEL ONLINE" data-slug="" aria-label="Gambar 1"><img src="https://a1.asdqwe.vip/banner/piontogel-img1" alt="TOGEL ONLINE" loading="lazy"></button><button type="button" class="pd-thumb-btn " data-full="https://a1.asdqwe.vip/banner/piontogel-img1" data-alt
11358="TOGEL ONLINE" data-slug="https://a1.asdqwe.vip/banner/piontogel-img1" aria-label="Gambar 2"><img src="https://a1.asdqwe.vip/banner/piontogel-img1" alt="TOGEL ONLINE" loading="lazy"></button><button type="button" class="pd-thumb-btn " data-full="https://a1.asdqwe.vip/banner/piontogel-img1" data-alt="TOGEL ONLINE" 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://a1.asdqwe.vip/banner/piontogel-img1" alt="TOGEL ONLINE" loading="lazy"></button><button type="button" class="pd-thumb-btn " data-full="https://a1.asdqwe.vip/banner/piontogel-img1" data-alt="TOGEL ONLINE" 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://a1.asdqwe.vip/banner/piontogel-img1" alt="TOGEL ONLINE" loading="lazy"></button><button type="button" class="pd-thumb-btn " data-full="https://a1.asdqwe.vip/banner/piontogel-img1" data-alt="TOGEL ONLINE" 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://a1.asdqwe.vip/banner/piontogel-img1" alt="TOGEL ONLINE" loading="lazy"></button><button type="button" class="pd-thumb-btn " data-full="https://a1.asdqwe.vip/banner/piontogel-img1" data-alt="TOGEL ONLINE" 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://a1.asdqwe.vip/banner/piontogel-img1" alt="TOGEL ONLINE" 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">
11359        <h1 class="pd-title" id="pd-title">Menghadirkan PIONTOGEL - Situs Alami Mendapatkan Cuan Dengan Permainan 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>
11360<div id="pd-price-anchor">
11361</div>
11362<div class="pd-price-qr-container">
11363        <div class="pd-price-block" id="pd-price-block">
11364                <div class="pd-price-row">
11365                        <span class="pd-price-current" id="pd-price-current" >Rp 10.000</span>
11366                        <span class="pd-price-original" id="pd-price-original">Rp 50.000</span>
11367                        <span class="pd-discount-badge" id="pd-discount-badge">-90%</span></div>
11368                        <div class="pd-meta-row">
11369                                <span class="sold-inline" id="pd-sold-inline" data-role="sold-chip">Terjual Terjual 5</span>
11370                                <span class="pwp-trigger-badge" id="pwp-trigger-badge" hidden>
11371                                        <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
11372                                                <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>
11373                                                <div class="pd-meta-inline"><span class="stock-badge out" id="pd-stock-badge">PIONTOGEL</span>
11374                                                        <span class="sku">SKU: <span id="pd-sku">BANDAR TOGEL</span></span></div>
11375<div class="paylater-promo paylater-promo-sm paylater-mobile-only">
11376    <span class="paylater-icon">💳</span>
11377    <span class="paylater-text">
11378        <strong>TOGEL</strong>
11379        <small>ONLINE</small>
11380    </span>
11381</div>
11382
11383<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">
11383<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">
11384<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-8asp2-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>
11385
11386                    <li class="sp-item">
11387                      <div class="sp-left">
11388                        <span class="sp-dot" style="--dot:#34d399;"></span>
11389                        <span class="sp-label">Medan Fair</span>
11390                      </div>
11391                      <span class="sp-qty" id="sp-qty-mdf">0</span>
11392                      <a class="sp-wa"
11393                         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"
11394                         target="_blank" rel="noopener" aria-label="WhatsApp Medan Fair">
11395                        <svg class="ico-wa" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
11396                            <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"/>
11397                          </svg>
11398                      </a>
11399                    </li>
11400
11401                    <li class="sp-item">
11402                      <div class="sp-left">
11403                        <span class="sp-dot" style="--dot:#f59e0b;"></span>
11404                        <span class="sp-label">Banda Aceh</span>
11405                      </div>
11406                      <span class="sp-qty" id="sp-qty-bda">0</span>
11407                      <a class="sp-wa"
11408                         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"
11409                         target="_blank" rel="noopener" aria-label="WhatsApp Banda Aceh">
11410                        <svg class="ico-wa" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
11411                            <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"/>
11412                          </svg>
11413                      </a>
11414                    </li>
11415                  </ul>
11416                </div>
11417
11418                            </div>
11419            <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>
11420            </div>
11421                                    <div id="pd-variants-anchor"></div>
11422
11423
11424
11425            <div class="pd-compare-section">
11426                <button type="button" class="pd-compare-btn" id="pd-compare-btn" data-product-id="325">
11427                    <svg class="compare-icon" width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
11428                        <path d="M21,9L17,5V8H10V10H17V13M7,11L3,15L7,19V16H14V14H7V11Z"/>
11429                    </svg>
11430                    <span class="compare-text">Bandingkan Produk</span>
11431                    <span class="compare-remove" hidden>
11432                        <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
11433                            <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"/>
11434                        </svg>
11435                        Hapus dari Perbandingan
11436                    </span>
11437                </button>
11438                <div class="pd-compare-info">
11439                    <small class="compare-note" id="compare-note">Bandingkan hingga 5 produk dengan bantuan AI</small>
11440                </div>
11441            </div>
11442
11443            <div class="pd-short-desc"><p><strong>Informasi PIONTOGEL:</strong></p><ul><li>Minimal Deposit: Rp.10.000</li><li>Jenis Permainan: Togel Online</li><li>Metode Pembayaran: Bank, QRIS, E-Wallet, Pulsa</li><li>Mata Uang: IDR (Indonesian Rupiah)</li><li>Jam Operasional: 24 Jam Nonstop</li><li>Total Rating: 888.888.333</li><li>Rating: ⭐⭐⭐⭐⭐</li><li>Daftar Buruan: <a href="https://calcourtsfitness.nichesite.org/">Klik Disini</a></li></ul></div><div class="PIONTOGEL-container-v1 mini">
11444<div class="PIONTOGEL-container">
11445
11446  <!-- HEADER -->
11447<div class="togel-container-v3 mini">
11448                    <div class="togel-machine-v3">
11449                        <h2 class="togel-title-v3">🎯 TEBAK ANGKA RESMI (SIMULASI) 🎯</h2>
11450
11451                        <div class="togel-controls-v3">
11452                            <label>
11453                                PASARAN
11454                                <select id="marketFilter">
11455                                    <option value="ALL">ALL PASARAN</option>
11456                                </select>
11457                            </label>
11458
11459                            <label>
11460                                TIPE
11461                                <select id="typeFilter">
11462                                    <option value="4D">4D</option>
11463                                    <option value="3D">3D</option>
11464                                    <option value="2D">2D</option>
11465                                </select>
11466                            </label>
11467
11468                            <label>
11469                                MIN CONF
11470                                <input id="minConf" type="number" min="50" max="99" step="1" value="70">
11471                            </label>
11472
11473                            <button id="btnTogelRandom" type="button">
11474                                <span class="icon">🎲</span> GENERATE
11475                            </button>
11476                        </div>
11477
11478                        <div class="togel-main-result-v3" id="togelResult">
11479                            <div class="togel-status-v3" id="togelStatus">— READY —</div>
11480
11481                            <div class="togel-highlight-box">
11482                                <div class="togel-col-left">
11483                                    <div class="k">Pasaran Rekomendasi</div>
11484                                    <div class="v-game" id="rMarket">-</div>
11485
11486                                    <div class="provider-info">
11487                                        <span class="k-small">Tipe:</span>
11488                                        <span class="v-small" id="rType">-</span>
11489                                        <span class="k-small" style="margin-left:10px;">Conf:</span>
11490                                        <span class="v-small" id="rConf">-</span>
11491                                    </div>
11492                                </div>
11493
11494                                <div class="togel-col-right">
11495                                    <div class="k-rtp">Angka Utama</div>
11496                                    <div class="v-rtp-huge" id="rAngka">-</div>
11497                                </div>
11498                            </div>
11499
11500                            <div class="togel-details-v3">
11501                                <div class="togel-detail-item">
11502                                    <div class="k-detail">BBFS</div>
11503                                    <div class="v-detail" id="rBBFS">-</div>
11504                                </div>
11505                                <div class="togel-detail-item">
11506                                    <div class="k-detail">Angka Ikut</div>
11507                                    <div class="v-detail" id="rIkut">-</div>
11508                                </div>
11509                                <div class="togel-detail-item">
11510                                    <div class="k-detail">Shio</div>
11511                                    <div class="v-detail" id="rShio">-</div>
11512                                </div>
11513                            </div>
11514
11515                            <div class="togel-tip-v3" id="rTip">
11516                                Tips: Ini simulasi hiburan. Tetap bijak & disiplin.
11517                            </div>
11518                        </div>
11519
11520                        <div class="togel-alts-v3">
11521                            <div class="togel-alts-title-v3">3 Alternatif Angka</div>
11522                            <div class="togel-alt-list-v3" id="togelAltList"></div>
11523                        </div>
11524
11525                        <div class="togel-note-v3">
11526                            Disclaimer: Hasil di halaman ini random (simulasi), bukan acuan hasil resmi.
11527                        </div>
11528                    </div>
11529                </div>
11530
11531                <style>
11532                    @import url("https://fonts.googleapis.com/css2?family=Orbitron:wght@600;700;800&display=swap");
11533
11534                    /* ===============================
11535   WRAPPER
11536================================ */
11537                    .togel-container-v3 {
11538                        display: flex;
11539                        justify-content: center;
11540                        padding: 16px 10px;
11541                        font-family: "Orbitron", "Poppins", system-ui, sans-serif;
11542                    }
11543
11544                    /* ===============================
11545   MACHINE CORE (RED ELEGANT)
11546================================ */
11547                    .togel-machine-v3 {
11548                        position: relative;
11549                        width: 100%;
11550                        max-width: 520px;
11551                        padding: 16px;
11552                        border-radius: 16px;
11553                        overflow: hidden;
11554                        isolation: isolate;
11555                        background: radial-gradient(circle at 15% 0%, rgba(161 0 0), transparent 48%), radial-gradient(circle at 85% 100%, rgba(180, 0, 0, 0.18), transparent 55%), linear-gradient(123deg, #000000, #000000);
11556                        border: 1px solid rgba(185 0 0);
11557                        box-shadow: inset 0 0 18px rgba(199 0 0), 0 0 18px rgba(199 0 0);
11558                        color: #dbdbdb;
11559                    }
11560
11561                    /* HUD SWEEP */
11562                    .togel-machine-v3::before {
11563                        content: "";
11564                        position: absolute;
11565                        inset: 0;
11566                        background: linear-gradient(120deg, transparent 40%, rgba(255, 0, 0, .10), transparent 65%);
11567                        animation: hudSweep 7s linear infinite;
11568                        pointer-events: none;
11569                        opacity: .8;
11570                    }
11571
11572                    /* ===============================
11573   TITLE
11574================================ */
11575                    .togel-title-v3 {
11576                        text-align: center;
11577                        margin: 0 0 12px;
11578                        font-size: 15px;
11579                        font-weight: 900;
11580                        letter-spacing: 1px;
11581                        text-shadow: 0 0 6px rgba(167 0 0 / 90%);
11582                    }
11583
11584                    /* ===============================
11585   CONTROLS
11586================================ */
11587                    .togel-controls-v3 {
11588                        display: grid;
11589                        grid-template-columns: 1fr 1fr;
11590                        gap: 10px;
11591                        padding: 12px;
11592                        border-radius: 14px;
11593
11594                        background: rgba(0, 0, 0, 0.4);
11595                        border: 1px solid rgba(255, 60, 60, 0.45);
11596                        box-shadow: inset 0 0 14px rgba(205 0 0 / 22%);
11597                    }
11598
11599                    .togel-controls-v3 label {
11600                        display: grid;
11601                        gap: 6px;
11602                        font-size: 10px;
11603                        letter-spacing: .8px;
11604                        text-transform: uppercase;
11605                        color: #cd0000;
11606                    }
11607
11608                    .togel-controls-v3 select,
11609                    .togel-controls-v3 input {
11610                        background: rgba(0 0 0 / 92%);
11611                        color: #c5c5c5;
11612                        border: 1px solid rgba(205 0 0);
11613                        border-radius: 10px;
11614                        padding: 7px 9px;
11615                        font-weight: 700;
11616                        font-size: 12px;
11617                        outline: none;
11618                        box-shadow: inset 0 0 10px rgba(207 3 3);
11619                    }
11620
11621                    /* BUTTON */
11622                    #btnTogelRandom {
11623                        grid-column: span 2;
11624                        display: flex;
11625                        align-items: center;
11626                        justify-content: center;
11627                        gap: 8px;
11628
11629                        background: linear-gradient(145deg, #c30000, #c30000);
11630                        border: none;
11631                        color: #000000;
11632                        font-weight: 900;
11633                        letter-spacing: .8px;
11634                        border-radius: 14px;
11635                        cursor: pointer;
11636                        padding: 10px 12px;
11637                        font-size: 12px;
11638
11639                        box-shadow: 0 0 16px rgba(255, 40, 40, 0.75);
11640                        transition: .2s ease;
11641                    }
11642
11643                    #btnTogelRandom:active {
11644                        transform: scale(.98);
11645                    }
11646
11647                    #btnTogelRandom:disabled {
11648                        opacity: .75;
11649                        cursor: not-allowed;
11650                    }
11651
11652                    /* ===============================
11653   MAIN RESULT
11654================================ */
11655                    .togel-main-result-v3 {
11656                        margin-top: 12px;
11657                        padding: 12px;
11658                        border-radius: 14px;
11659
11660                        background: rgba(30, 0, 0, 0.78);
11661                        border: 1px solid rgba(223 0 0);
11662                        box-shadow: inset 0 0 16px rgba(215 0 0);
11663                    }
11664
11665                    .togel-status-v3 {
11666                        text-align: center;
11667                        font-weight: 800;
11668                        letter-spacing: .8px;
11669                        color: #cd0000;
11670                        margin-bottom: 10px;
11671                        font-size: 11px;
11672                    }
11673
11674                    .togel-highlight-box {
11675                        display: flex;
11676                        justify-content: space-between;
11677                        gap: 12px;
11678                        padding-bottom: 10px;
11679                        margin-bottom: 10px;
11680                        border-bottom: 1px dashed rgba(255, 80, 80, 0.3);
11681                    }
11682
11683                    .k,
11684                    .k-rtp {
11685                        font-size: 10px;
11686                        color: #c7c7c7;
11687                        text-transform: uppercase;
11688                    }
11689
11690                    .v-game {
11691                        font-size: 14px;
11692                        font-weight: 900;
11693                        color: #ffffff;
11694                        margin-top: 6px;
11695                        line-height: 1.2;
11696                    }
11697
11698                    .provider-info {
11699                        margin-top: 8px;
11700                        font-size: 11px;
11701                        color: #d9d9d9;
11702                        opacity: .95;
11703                    }
11704
11705                    .k-small {
11706                        color: #cd0000;
11707                        font-size: 10px;
11708                    }
11709
11710                    .v-small {
11711                        color: #fff0f0;
11712                        font-weight: 900;
11713                    }
11714
11715                    .v-rtp-huge {
11716                        font-size: 2.4em;
11717                        font-weight: 900;
11718                        color: #d70000;
11719                        text-shadow: 0 0 10px rgba(0 0 0);
11720                        line-height: 1;
11721                        letter-spacing: 1px;
11722                    }
11723
11724                    /* DETAILS */
11725                    .togel-details-v3 {
11726                        display: grid;
11727                        grid-template-columns: repeat(3, 1fr);
11728                        gap: 8px;
11729                        background: rgba(0, 0, 0, .30);
11730                        padding: 10px;
11731                        border-radius: 12px;
11732                    }
11733
11734                    .k-detail {
11735                        font-size: 9px;
11736                        color: #cd0000;
11737                        text-transform: uppercase;
11738                    }
11739
11740                    .v-detail {
11741                        font-size: 12px;
11742                        font-weight: 800;
11743                        color: #ffffff;
11744                        margin-top: 4px;
11745                        letter-spacing: .8px;
11746                    }
11747
11748                    .togel-tip-v3 {
11749                        margin-top: 10px;
11750                        font-size: 10px;
11751                        color: #d9d9d9;
11752                        opacity: .9;
11753                        text-align: center;
11754                    }
11755
11756                    /* ALTS */
11757                    .togel-alts-v3 {
11758                        margin-top: 12px;
11759                        padding: 12px;
11760                        border-radius: 14px;
11761                        background: rgba(0, 0, 0, .40);
11762                        border: 1px solid rgba(223 0 0);
11763                    }
11764
11765                    .togel-alts-title-v3 {
11766                        font-weight: 900;
11767                        color: #cd0000;
11768                        margin-bottom: 10px;
11769                        font-size: 12px;
11770                    }
11771
11772                    .togel-alt-list-v3 {
11773                        display: grid;
11774                        grid-template-columns: 1fr;
11775                        gap: 8px;
11776                    }
11777
11778                    .togel-alt-item {
11779                        background: rgba(30, 0, 0, .86);
11780                        border-radius: 12px;
11781                        padding: 10px;
11782                        border-left: 3px solid rgba(167 0 0 / 90%);
11783                    }
11784
11785                    .togel-alt-item .g {
11786                        font-weight: 900;
11787                        color: #c5c5c5;
11788                        font-size: 12px;
11789                    }
11790
11791                    .togel-alt-item .p {
11792                        font-size: 10px;
11793                        color: #cd0000;
11794                        margin-top: 4px;
11795                    }
11796
11797                    .togel-alt-item .r {
11798                        margin-top: 6px;
11799                        font-weight: 900;
11800                        color: #d70000;
11801                        letter-spacing: .8px;
11802                        font-size: 12px;
11803                    }
11804
11805                    .togel-note-v3 {
11806                        margin-top: 10px;
11807                        font-size: 10px;
11808                        color: #d9d9d9;
11809                        opacity: .85;
11810                        text-align: center;
11811                    }
11812
11813                    /* MOBILE */
11814                    @media(max-width:420px) {
11815                        .togel-highlight-box {
11816                            flex-direction: column
11817                        }
11818
11819                        .togel-details-v3 {
11820                            grid-template-columns: 1fr
11821                        }
11822                    }
11823
11824                    /* ANIM */
11825                    @keyframes hudSweep {
11826                        0% {
11827                            transform: translateX(-110%)
11828                        }
11829
11830                        100% {
11831                            transform: translateX(110%)
11832                        }
11833                    }
11834
11835                    @keyframes pulse {
11836                        0% {
11837                            opacity: .55
11838                        }
11839
11840                        50% {
11841                            opacity: 1
11842                        }
11843
11844                        100% {
11845                            opacity: .55
11846                        }
11847                    }
11848                </style>
11849
11850                
11850<script>
11851                    /* ===============================
11852                       DATASET TOGEL (SIMULASI)
11853                    ================================ */
11854                    const TOGEL_MARKETS = [
11855                        { market: "Singapore", code: "SGP", open: "17:45", zone: "WIB" },
11856                        { market: "Hongkong", code: "HK", open: "22:45", zone: "WIB" },
11857                        { market: "Sydney", code: "SDY", open: "13:45", zone: "WIB" },
11858                        { market: "Macau", code: "MC", open: "16:00", zone: "WIB" },
11859                        { market: "Cambodia", code: "KHM", open: "10:45", zone: "WIB" },
11860                        { market: "Japan", code: "JP", open: "11:30", zone: "WIB" }
11861                    ];
11862
11863                    const SHIO = ["Tikus", "Kerbau", "Macan", "Kelinci", "Naga", "Ular", "Kuda", "Kambing", "Monyet", "Ayam", "Anjing", "Babi"];
11864
11865                    const $ = (q) => document.querySelector(q);
11866                    const randInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
11867
11868                    function uniqueMarkets() {
11869                        return TOGEL_MARKETS.map(m => m.market);
11870                    }
11871
11872                    function genDigits(len) {
11873                        let s = "";
11874                        for (let i = 0; i < len; i++) s += String(randInt(0, 9));
11875                        return s;
11876                    }
11877
11878                    function genBBFS() {
11879                        const pool = [...Array(10)].map((_, i) => i);
11880                        const out = [];
11881                        for (let i = 0; i < 7; i++) {
11882                            const idx = randInt(0, pool.length - 1);
11883                            out.push(pool.splice(idx, 1)[0]);
11884                        }
11885                        return out.join("");
11886                    }
11887
11888                    function genIkut() {
11889                        const pool = [...Array(10)].map((_, i) => i);
11890                        const out = [];
11891                        for (let i = 0; i < 4; i++) {
11892                            const idx = randInt(0, pool.length - 1);
11893                            out.push(pool.splice(idx, 1)[0]);
11894                        }
11895                        return out.join(" • ");
11896                    }
11897
11898                    function simulateConfidence() {
11899                        return randInt(60, 96);
11900                    }
11901
11902                    function applyFilters() {
11903                        const m = $("#marketFilter").value;
11904                        const minC = parseFloat($("#minConf").value || "0");
11905
11906                        return TOGEL_MARKETS
11907                            .filter(x => (m === "ALL" || x.market === m))
11908                            .map(x => ({ ...x, conf: simulateConfidence() }))
11909                            .filter(x => x.conf >= minC);
11910                    }
11911
11912                    function pickRandom(arr, n = 1) {
11913                        const copy = [...arr];
11914                        const out = [];
11915                        for (let i = 0; i < n && copy.length; i++) {
11916                            const idx = randInt(0, copy.length - 1);
11917                            out.push(copy.splice(idx, 1)[0]);
11918                        }
11919                        return out;
11920                    }
11921
11922                    function formatAngka(type) {
11923                        if (type === "4D") return genDigits(4);
11924                        if (type === "3D") return genDigits(3);
11925                        return genDigits(2);
11926                    }
11927
11928                    function renderMain(item, type) {
11929                        $("#rMarket").textContent = `${item.market} (${item.code}) • Open ${item.open} ${item.zone}`;
11930                        $("#rType").textContent = type;
11931                        $("#rConf").textContent = `${item.conf}%`;
11932
11933                        $("#rAngka").textContent = formatAngka(type);
11934                        $("#rBBFS").textContent = genBBFS();
11935                        $("#rIkut").textContent = genIkut();
11936                        $("#rShio").textContent = SHIO[randInt(0, SHIO.length - 1)];
11937                        $("#rTip").textContent = "Tips: Ini simulasi hiburan. Tetap bijak & disiplin.";
11938                    }
11939
11940                    function renderAlts(list, type) {
11941                        const wrap = $("#togelAltList");
11942                        wrap.innerHTML = "";
11943                        list.forEach(x => {
11944                            const el = document.createElement("div");
11945                            el.className = "togel-alt-item";
11946                            el.innerHTML = `
11947      <div class="g">${x.market} (${x.code})</div>
11948      <div class="p">Tipe ${type} • Conf <strong>${x.conf}%</strong></div>
11949      <div class="r">Angka: <strong>${formatAngka(type)}</strong></div>
11950    `;
11951                            wrap.appendChild(el);
11952                        });
11953                    }
11954
11955                    function spinButton(btn, on) {
11956                        if (on) {
11957                            btn.disabled = true;
11958                            btn.dataset.text = btn.innerHTML;
11959                            btn.innerHTML = `<span class="icon">🔄</span> PROCESS...`;
11960                        } else {
11961                            btn.disabled = false;
11962                            btn.innerHTML = btn.dataset.text || `<span class="icon">🎲</span> GENERATE`;
11963                        }
11964                    }
11965
11966                    /* ===============================
11967                       INIT
11968                    ================================ */
11969                    (function init() {
11970                        const sel = $("#marketFilter");
11971                        uniqueMarkets().forEach(m => {
11972                            const o = document.createElement("option");
11973                            o.value = m; o.textContent = m;
11974                            sel.appendChild(o);
11975                        });
11976
11977                        $("#btnTogelRandom").addEventListener("click", (e) => {
11978                            const btn = e.currentTarget;
11979                            const type = $("#typeFilter").value;
11980                            const filtered = applyFilters();
11981                            const st = $("#togelStatus");
11982
11983                            if (!filtered.length) {
11984                                st.textContent = "— NO MATCH —";
11985                                st.style.color = "white";
11986                                renderAlts([], type);
11987                                $("#rMarket").textContent = "-";
11988                                $("#rType").textContent = type;
11989                                $("#rConf").textContent = "-";
11990                                $("#rAngka").textContent = "-";
11991                                $("#rBBFS").textContent = "-";
11992                                $("#rIkut").textContent = "-";
11993                                $("#rShio").textContent = "-";
11994                                return;
11995                            }
11996
11997                            spinButton(btn, true);
11998                            st.textContent = "— GENERATING —";
11999                            st.style.animation = "pulse .6s linear infinite";
12000                            st.style.color = "white";
12001
12002                            const t0 = setInterval(() => {
12003                                const temp = pickRandom(filtered, 1)[0];
12004                                $("#rMarket").textContent = `${temp.market} (${temp.code}) • Open ${temp.open} ${temp.zone}`;
12005                                $("#rType").textContent = type;
12006                                $("#rConf").textContent = `${temp.conf}%`;
12007                                $("#rAngka").textContent = formatAngka(type);
12008                                $("#rBBFS").textContent = genBBFS();
12009                                $("#rIkut").textContent = genIkut();
12010                                $("#rShio").textContent = SHIO[randInt(0, SHIO.length - 1)];
12011                            }, 85);
12012
12013                            setTimeout(() => {
12014                                clearInterval(t0);
12015
12016                                const picks = pickRandom(filtered, 4);
12017                                renderMain(picks[0], type);
12018                                renderAlts(picks.slice(1), type);
12019
12020                                st.textContent = "— READY —";
12021                                st.style.animation = "";
12022                                st.style.color = "gold";
12023                                spinButton(btn, false);
12024                            }, 1200);
12025                        });
12026
12027                        renderAlts([], "4D");
12028                    })();
12029                </script>
12029
12030
12031<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: 2 bytes, line 12031
12031
12032<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: 2 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":"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: 2 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":"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: 2 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: 2 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":"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: 2 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":"51df3b17a746455f940c0b561e8c0c0b","r":1,"server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
12037
12038
12039<style>
12040body{background:#ebf2fc; font-family:Poppins,sans-serif; color:white;}
12041.PIONTOGEL-container{
12042max-width: 520px;
12043    margin: auto;
12044    padding: 15px;
12045    border-radius: 12px;
12046    background: #a10808;
12047    border: 2px solid #ca0000;
12048    box-shadow: 0 0 20px #d90000;
12049}
12050
12051.PIONTOGEL-header{
12052  font-weight:900;
12053  text-align:center;
12054  color:#ff4c4c;
12055  text-shadow:0 0 12px #5f90d9;
12056  margin-bottom:12px;
12057}
12058
12059.PIONTOGEL-controls{
12060  display:grid;
12061  grid-template-columns:1fr 1fr;
12062  gap:10px;
12063  margin-bottom:12px;
12064}
12065.PIONTOGEL-controls label{
12066  display:flex; flex-direction:column;
12067  font-size:11px; color:#ff9999;
12068}
12069.PIONTOGEL-controls select,
12070.PIONTOGEL-controls input{
12071  padding:8px;
12072  border-radius:8px;
12073  border:1px solid #ff4c4c;
12074  background:#2b0000;
12075  color:white;
12076}
12077#btnGenerate{
12078  grid-column:span 2;
12079  padding:12px;
12080  background:#5f90d9;
12081  border:none;
12082  color:black;
12083  font-weight:900;
12084  cursor:pointer;
12085  margin-top:5px;
12086  box-shadow:0 0 15px #ff4c4c;
12087}
12088
12089.PIONTOGEL-result{
12090  margin-top:12px;
12091  padding:12px;
12092  border-radius:10px;
12093  background:rgba(255,0,0,0.05);
12094  border:1px solid #ff4c4c;
12095}
12096
12097.PIONTOGEL-main div, .PIONTOGEL-details div{
12098  margin:5px 0;
12099}
12100
12101#rPrediction{
12102  font-size:24px;
12103  font-weight:900;
12104  color:#5f90d9;
12105}
12106
12107.PIONTOGEL-alts{
12108  margin-top:10px;
12109}
12110.PIONTOGEL-alts h4{margin-bottom:6px;}
12111#PIONTOGELAltList div{
12112  background:#2b0000;
12113  padding:6px 10px;
12114  margin:3px 0;
12115  border-radius:8px;
12116  border-left:3px solid #5f90d9;
12117}
12118  .bank-tab.active, .bank-tab:hover {
12119    background: #df0000;
12120    color: #fff;
12121    border-color: #df0000;
12122    box-shadow: 0 3px 10px -4px rgb(240 29 29 / 60%);
12123}
12124
12125.PIONTOGEL-disclaimer{
12126  font-size:10px;
12127  margin-top:8px;
12128  text-align:center;
12129  color:#ff9999;
12130}
12131</style>
12132
12133<script>
12134const leagues = ["Premier League","La Liga","Serie A"];
12135const tips = ["Analisis statistik tim","Performa terakhir","Cek head-to-head"];
12136function rand(arr){return arr[Math.floor(Math.random()*arr.length)];}
12137
12138var _bgBtn=document.getElementById("btnGenerate");if(_bgBtn)_bgBtn.onclick=function(){
12139  const type = document.getElementById("betTypeFilter").value;
12140  const league = rand(leagues);
12141
12142  let pred;
12143  if(type==="1X2") pred=rand(["1","X","2"]);
12144  else if(type==="OVER_UNDER") pred=rand(["Over","Under"]);
12145  else pred=rand(["Home -1","Away +1","Draw"]);
12146
12147  document.getElementById("rLeague").innerText=league;
12148  document.getElementById("rPrediction").innerText=pred;
12149  document.getElementById("rBetType").innerText=type;
12150  document.getElementById("rConf").innerText=Math.floor(Math.random()*40+60)+"%";
12151  document.getElementById("rTip").innerText=rand(tips);
12152  document.getElementById("rOdds").innerText=(Math.random()*3+1).toFixed(2);
12153
12154  // alternatif
12155  const alt=document.getElementById("PIONTOGELAltList"); alt.innerHTML="";
12156  for(let i=0;i<3;i++){let d=document.createElement("div"); d.innerText=rand(["1","X","2"]); alt.appendChild(d);}
12157
12158  document.getElementById("PIONTOGELStatus").innerText="— READY —";
12159}
12160</script>
12160
12161
12162
12163
12164            <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>
12165        </div>
12166        </div>
12167        </section>
12168
12169        <aside class="pd-sidebar">
12170            <div class="pd-side-card pd-buy-card">
12171                <form class="pd-buy-form" id="pd-buy-form" data-pid="325">
12172                    <input type="hidden" name="variant_id" id="pd-variant-id" value="0">
12173                    <div>
12174                        <label for="qty" class="qty-label">Qty</label>
12175                        <input type="number" id="qty" name="qty" value="0" min="1"
12176                               max="0"
12177                               disabled>
12178                    </div>
12179                    <div class="pd-actions">
12180                        <button type="submit" class="btn-add-cart" id="btn-add-cart" disabled>+ Keranjang</button>
12181                        <button type="button" class="btn-buy" id="btn-buy" data-id="278426" disabled >Beli</button>
12182                        <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>
12183                                            </div>
12184                </form>
12185                <div class="paylater-promo paylater-promo-md paylater-desktop-only">
12186    <span class="paylater-icon">💳</span>
12187    <span class="paylater-text">
12188        <strong>Autorized PIONTOGEL</strong>
12189        <small>Situs Resmi PIONTOGEL </small>
12190    </span>
12191</div>            </div>
12192
12193
12194                        <div class="pd-side-card qoala-banner-card">
12195                <div class="qoala-banner">
12196                    <div class="qoala-banner-content">
12197                        <div class="qoala-icon">
12198                            <center><img src="https://a1.asdqwe.vip/assets/icon-piontogel.png" alt="PIONTOGEL" class="qoala-shield-img"></center>
12199                        </div>
12200                        <div class="qoala-text">
12201                            <div class="qoala-title">Login PIONTOGEL</div>
12202                            <div class="qoala-subtitle">Verified</div>
12203                        </div>
12204                    </div>
12205                    <a href="https://calcourtsfitness.nichesite.org/" class="qoala-cta"
12206                       data-ga-event="qoala_banner_click"
12207                       data-product-id="325"
12208                       data-product-name="PIONTOGEL"
12209                       data-category-path="Komputer &amp; Laptop/Laptop/Gaming" target="_blank">Info</a>
12210                </div>
12211<style>
12212.qoala-banner-card{padding:0;overflow:hidden}
12213
12214.qoala-banner{
12215  background:linear-gradient(135deg, #cd0000 0%, #c90000 100%);
12216  padding:12px 16px;
12217  display:flex;
12218  align-items:center;
12219  justify-content:space-between;
12220  color:white;
12221  text-decoration:none;
12222  min-height:60px;
12223}
12224
12225.qoala-banner-content{
12226  display:flex;
12227  align-items:center;
12228  gap:12px;
12229}
12230
12231.qoala-icon{
12232  display:flex;
12233  align-items:center;
12234  justify-content:center;
12235}
12236
12237.qoala-shield-img{
12238  width:36px;
12239  height:36px;
12240  object-fit:contain;
12241  opacity:0.9;
12242}
12243
12244.qoala-title{
12245  font-weight:600;
12246  font-size:14px;
12247  line-height:1.2;
12248}
12249
12250.qoala-subtitle{
12251  font-size:12px;
12252  opacity:0.9;
12253  line-height:1.2;
12254}
12255
12256.qoala-cta{
12257  background:rgba(255,255,255,0.15);
12258  color:white;
12259  padding:6px 12px;
12260  border-radius:6px;
12261  font-size:12px;
12262  font-weight:600;
12263  text-decoration:none;
12264  transition:background 0.2s ease;
12265  border:1px solid rgba(255,80,80,0.4);
12266}
12267
12268.qoala-cta:hover{
12269  background:rgba(255,60,60,0.25);
12270  color:white;
12271}
12272
12273@media(max-width:768px){
12274  .qoala-banner{padding:10px 12px}
12275  .qoala-title{font-size:17px}
12276  .qoala-subtitle{font-size:11px}
12277}
12278</style>
12279            </div>
12280
12281                        <div class="pd-side-card pd-installment-card2"
12282                 id="inst-card"
12283                 data-base-price="20789000"
12284                 data-zero-smart="0">
12285                <div class="inst-head">
12286                    <h3>Promo & Fitur PIONTOGEL</h3>
12287                                    </div>
12288
12289                <div class="inst-bank-tabs" role="tablist" aria-label="Bank">
12290                    <button type="button" class="bank-tab active" data-bank="Lucky Draw" aria-selected="true">Lucky Draw</button>
12291                    <button type="button" class="bank-tab" data-bank="Lomba Panjat Tur
12291nover" aria-selected="false">Lomba Panjat Turnover</button>
12292                    <button type="button" class="bank-tab" data-bank="Prediksi Jitu" aria-selected="false">Prediksi Jitu</button>
12293                </div>
12294
12295                <ul class="inst-month-list" id="inst-month-list" aria-label="Daftar cicilan per bulan"></ul>
12296                <div class="inst-note" id="inst-zero-note" style="display:none;">0% Cicilan khusus bank terpilih*</div>
12297            </div>
12298
12299            <!-- MECHA BLUE FINAL BOSS REVIEW SLIDER -->
12300<div class="tnt-review-mecha">
12301
12302<div class="tnt-header">
12303<h2>REVIEW MEMBER PIONTOGEL</h2>
12304
12305</div>
12306
12307<div class="tnt-slider" id="tntSlider">
12308
12309<div class="tnt-slide active">
12310  <div class="tnt-name">Rian Pratama – Surabaya</div>
12311  <div class="tnt-comment">"Login VIP-nya cepat banget dan semua informasi langsung terlihat jelas. Live draw selalu update, jadi memantau result terbaru terasa makin praktis."</div>
12312</div>
12313
12314<div class="tnt-slide">
12315  <div class="tnt-name">Vinka Aurelia – Semarang</div>
12316  <div class="tnt-comment">"Pasarannya lengkap dan riwayat keluaran tersusun rapi. Saya jadi lebih mudah membandingkan result sekaligus melihat pola angka yang sering muncul."</div>
12317</div>
12318
12319<div class="tnt-slide">
12320  <div class="tnt-name">Violet Evergarde – Makassar</div>
12321  <div class="tnt-comment">"Menurut saya konsepnya cukup beda. Informasi yang ditampilkan nggak terasa terlalu penuh dan masih nyaman buat dijelajahi."</div>
12322</div>
12323
12324
12325
12326</div>
12327</div>
12328
12329</aside>
12330</div>
12331
12332<style>
12333.tnt-review-mecha{
12334  background: radial-gradient(circle at center, #da0000, #350000);
12335  border: 2px solid #aa0000;
12336  border-radius: 14px;
12337  padding: 20px;
12338  color: #dbdbdb;
12339  font-family: 'Orbitron', sans-serif;
12340  position: relative;
12341  overflow: hidden;
12342  box-shadow: 0 0 25px #990000, inset 0 0 20px rgba(255, 0, 0, 0.2);
12343}
12344
12345/* scanline */
12346.tnt-review-mecha::before{
12347  content:"";
12348  position:absolute;
12349  inset:0;
12350  background: repeating-linear-gradient(
12351    to bottom,
12352    rgba(255,0,0,0.08),
12353    rgba(255,0,0,0.08) 1px,
12354    transparent 2px,
12355    transparent 4px
12356  );
12357  pointer-events:none;
12358}.bank-tab.active, .bank-tab:hover
12359
12360
12361.tnt-header h2{
12362  font-size: 18px;
12363  color:#000000;
12364  text-shadow:0 0 10px #d71414, 0 0 20px #d10000;
12365  margin-bottom:5px;
12366}
12367
12368.tnt-header p{
12369  font-size:12px;
12370  opacity:0.85;
12371  margin-bottom:15px;
12372}
12373
12374.tnt-slider{
12375  position:relative;
12376  height:90px;
12377}
12378
12379.tnt-slide{
12380  position:absolute;
12381  width:100%;
12382  opacity:0;
12383  transform:translateX(40px);
12384  transition:all .6s ease;
12385}
12386  .tenor-badge {
12387    background: #df0000;
12388    color: #fff;
12389    font-weight: 700;
12390    min-width: 42px;
12391    text-align: center;
12392    padding: .38rem .4rem;
12393    font-size: .8rem;
12394    border-radius: 10px;
12395    box-shadow: 0 3px 10px -4px rgb(211 3 3);
12396}
12397
12398.tnt-slide.active{
12399  opacity:1;
12400  transform:translateX(0);
12401}
12402
12403.tnt-name{
12404font-size: 14px;
12405    color: #000000;
12406    margin-bottom: 5px;
12407    text-shadow: 0 0 6px #290000;
12408}
12409
12410.tnt-comment{
12411  font-size:13px;
12412  line-height:1.5;
12413}
12414
12415/* glowing border animation */
12416@keyframes pulseGlow{
12417  0%{box-shadow:0 0 10px #d71414;}
12418  50%{box-shadow:0 0 25px #d71414;}
12419  100%{box-shadow:0 0 10px #d71414;}
12420}
12421
12422.tnt-review-mecha{
12423  animation:pulseGlow 2.5s infinite;
12424}
12425</style>
12426
12427<script>
12428let slides = document.querySelectorAll('.tnt-slide');
12429let index = 0;
12430
12431setInterval(()=>{
12432  slides[index].classList.remove('active');
12433  index = (index + 1) % slides.length;
12434  slides[index].classList.add('active');
12435}, 3000);
12436</script>
12436
12437
12438        <div class="artikel-glow-SM">
12439
12440  <div class="artikel-shine"></div>
12441
12442  <h1>Menghadirkan PIONTOGEL - Situs Alami Mendapatkan Cuan Dengan Permainan Resmi</h1>
12443 <p style="text-align: justify;">PIONTOGEL membangun identitas sebagai platform yang mencoba menghadirkan pengalaman dalam satu ruang dengan karakter yang lebih terarah.</p>
12444
12445 <p style="text-align: justify;">PIONTOGEL mengambil pendekatan berbeda dengan menempatkan setiap bagian berdasarkan fungsi dan kebutuhan. Gagasan tersebut membuat ruang digital tidak sekadar dipenuhi berbagai elemen, melainkan mempunyai alur yang dapat dipahami sejak kunjungan pertama.</p>
12446</div>
12447
12448<style>
12449.artikel-glow-SM{
12450      max-width: 820px;
12451    margin: 40px auto;
12452    padding: 36px;
12453    box-sizing: border-box;
12454    position: relative;
12455    overflow: hidden;
12456    border-radius: 30px;
12457    font-family: Arial, sans-serif;
12458    color: #ffffff;
12459    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);
12460    border: 2px solid rgba(255, 255, 255, .55);
12461    box-shadow: 0 0 25px rgb(207 3 3), 0 0 55px rgb(225 0 0), 0 20px 45px rgba(0, 0, 0, .25), inset 0 0 35px rgba(145 0 0);
12462}
12463
12464.artikel-glow-SM:before{
12465  content:"";
12466  position:absolute;
12467  inset:10px;
12468  border-radius:24px;
12469  border:1px solid rgba(255,255,255,.32);
12470  pointer-events:none;
12471}
12472
12473.artikel-shine{
12474  position:absolute;
12475  top:-120px;
12476  left:-180px;
12477  width:280px;
12478  height:650px;
12479  background:linear-gradient(120deg,transparent,rgba(255,255,255,.55),transparent);
12480  transform:rotate(25deg);
12481  animation:artikelShine 4.5s infinite;
12482  pointer-events:none;
12483}
12484
12485@keyframes artikelShine{
12486  0%{left:-220px;}
12487  55%{left:110%;}
12488  100%{left:110%;}
12489}
12490
12491.artikel-glow-SM h1,
12492.artikel-glow-SM h2{
12493  position:relative;
12494  color:#000000;
12495  text-align:center;
12496  line-height:1.35;
12497  text-shadow:
12498    0 0 8px rgba(179 0 0 / 95%),
12499    0 0 18px rgba(197 0 0),
12500    0 2px 8px rgba(149 0 0);
12501}
12502
12503.artikel-glow-SM h1{
12504  font-size:31px;
12505  margin:0 0 20px;
12506}
12507
12508.artikel-glow-SM h2{
12509  font-size:24px;
12510  margin:28px 0 14px;
12511}
12512
12513.artikel-glow-SM p{
12514  position:relative;
12515  margin:0 0 16px;
12516  color:#f8ffff;
12517  font-size:15px;
12518  line-height:1.85;
12519  text-align:justify;
12520  text-shadow:0 1px 4px rgba(0,0,0,.28);
12521}
12522
12523.artikel-glow-SM a{
12524  color:#000000;
12525  font-weight:800;
12526  text-shadow:0 0 8px rgba(153 0 0);
12527}
12528
12529.artikel-glow-SM img{
12530  position:relative;
12531  display:block;
12532  max-width:100%;
12533  margin:16px auto 24px;
12534  border-radius:16px;
12535  border:1px solid rgba(255,255,255,.55);
12536  box-shadow:
12537    0 0 18px rgba(255,255,255,.55),
12538    0 12px 28px rgba(0,0,0,.28);
12539}
12540
12541.artikel-highlight{
12542  position:relative;
12543  margin:24px 0;
12544  padding:22px;
12545  border-radius:22px;
12546  background:rgba(255,255,255,.20);
12547  border:1px solid rgba(255,255,255,.45);
12548  box-shadow:
12549    inset 0 1px 0 rgba(255,255,255,.65),
12550    inset 0 0 22px rgba(255,255,255,.14),
12551    0 10px 25px rgba(0,0,0,.18);
12552  backdrop-filter:blur(8px);
12553}
12554
12555.artikel-highlight:before{
12556  content:"";
12557  position:absolute;
12558  left:0;
12559  right:0;
12560  top:0;
12561  height:45%;
12562  border-radius:22px 22px 0 0;
12563  background:linear-gradient(rgba(255,255,255,.35),rgba(255,255,255,.04));
12564  pointer-events:none;
12565}
12566
12567.artikel-glow-SM ul{
12568  position:relative;
12569  margin:15px 0 0 20px;
12570  padding:0;
12571}
12572
12573.artikel-glow-SM li{
12574  margin-bottom:11px;
12575  padding:10px 12px;
12576  border-radius:12px;
12577  background:rgba(255,255,255,.16);
12578  color:#ffffff;
12579  line-height:1.6;
12580  box-shadow:inset 0 0 10px rgba(255,255,255,.08);
12581}
12582
12583@media(max-width:600px){
12584  .artikel-glow-SM{
12585    margin:25px 12px;
12586    padding:25px;
12587    border-radius:24px;
12588  }
12589
12590  .artikel-glow-SM h1{
12591    font-size:24px;
12592  }
12593
12594  .artikel-glow-SM h2{
12595    font-size:21px;
12596  }
12597}
12598</style>
12599
12600<div class="SM-faq-rainbow">
12601  <div class="SM-shine"></div>
12602
12603  <div class="SM-faq-top">
12604    <span>★ PREMIUM FAQ ★</span>
12605    <h2>PERTANYAAN POPULER PIONTOGEL</h2>
12606
12607  </div>
12608
12609  <div class="SM-rainbow-item">
12610    <button type="button">Apa yang menjadi ciri khas PIONTOGEL?</button>
12611    <div class="SM-rainbow-answer">
12612      <p>PIONTOGEL mengusung konsep ruang digital yang memadukan keberagaman permainan, penyajian informasi, dan pengalaman penggunaan dalam satu platform.</p>
12613    </div>
12614  </div>
12615
12616  <div class="SM-rainbow-item">
12617    <button type="button">Mengapa keberagaman permainan menjadi bagian penting?</button>
12618    <div class="SM-rainbow-answer">
12619      <p>Keberagaman memberikan keleluasaan bagi pengguna untuk mengenali berbagai pilihan yang tersedia sesuai dengan ketertarikannya masing-masing.</p>
12620    </div>
12621  </div>
12622
12623  <div class="SM-rainbow-item">
12624    <button type="button">Apa yang dimaksud dengan perspektif berbeda pada PIONTOGEL?</button>
12625    <div class="SM-rainbow-answer">
12626      <p>Perspektif tersebut merujuk pada cara platform menyusun informasi dan menghadirkan pilihan hiburan secara lebih terarah, bukan sekadar menampilkan banyak elemen sekaligus.</p>
12627    </div>
12628  </div>
12629
12630  <div class="SM-rainbow-item">
12631    <button type="button">Bagaimana cara mengenali permainan yang tersedia?</button>
12632    <div class="SM-rainbow-answer">
12633      <p>Pengguna dapat melihat kategori dan informasi yang disediakan pada platform, kemudian memilih bagian yang ingin dieksplorasi berdasarkan keterangannya.</p>
12634    </div>
12635  </div>
12636  <div class="SM-rainbow-item">
12637    <button type="button">Apakah PIONTOGEL cocok untuk pengguna baru?</button>
12638    <div class="SM-rainbow-answer">
12639      <p>Platform dengan penyajian informasi yang terstruktur dapat membantu pengguna baru mengenali berbagai bagian secara bertahap tanpa harus memahami seluruh fitur sekaligus.</p>
12640    </div>
12641  </div>
12642</div>
12643
12644<style>
12645.SM-faq-rainbow{
12646      max-width: 760px;
12647    margin: 40px auto;
12648    padding: 30px;
12649    box-sizing: border-box;
12650    position: relative;
12651    overflow: hidden;
12652    border-radius: 32px;
12653    font-family: Arial, sans-serif;
12654    background: radial-gradient(circle at 15% 10%, rgba(177 2 2), transparent 18%), radial-gradient(circle at 85% 15%, rgb(207 3 3), transparent 25%), radial-gradient(circle at 20% 90%, rgb(207 3 3), transparent 25%), linear-gradient(135deg, #c70000, #8e0b0b, #960e0e, #c30000);
12655    border: 2px solid rgba(177 2 2);
12656    box-shadow: 0 0 25px rgb(207 3 3), 0 0 55px rgba(223 0 0), inset 0 0 35px rgba(145 0 0);
12657}
12658
12659.SM-shine{
12660  position:absolute;
12661  top:-90px;
12662  left:-120px;
12663  width:260px;
12664  height:520px;
12665  background:linear-gradient(120deg,transparent,rgba(255,255,255,.55),transparent);
12666  transform:rotate(25deg);
12667  animation:SMShine 4s infinite;
12668  pointer-events:none;
12669}
12670
12671@keyframes SMShine{
12672  0%{left:-180px;}
12673  55%{left:110%;}
12674  100%{left:110%;}
12675}
12676
12677.SM-faq-top{
12678  position:relative;
12679  text-align:center;
12680  margin-bottom:24px;
12681}
12682
12683.SM-faq-top span{
12684  display:inline-block;
12685  padding:8px 22px;
12686  border-radius:999px;
12687  background:rgba(185 0 0 / 0%);
12688  color:#dbdbdb;
12689  font-size:12px;
12690  font-weight:900;
12691  letter-spacing:2px;
12692  border:1px solid rgba(215 215 215 / 62%);
12693  box-shadow:inset 0 0 14px rgba(255,255,255,.25);
12694}
12695
12696.SM-faq-top h2{
12697  margin:16px 0 8px;
12698  color:#000000;
12699  font-size:34px;
12700  line-height:1.15;
12701  text-shadow:
12702    0 0 10px rgba(177 0 0),
12703    0 0 22px rgba(173 0 0);
12704}
12705
12706.SM-faq-top p{
12707  margin:0;
12708  color:#f3ffff;
12709  font-size:14px;
12710}
12711
12712.SM-rainbow-item{
12713  position:relative;
12714  overflow:hidden;
12715  margin-bottom:14px;
12716  border-radius:20px;
12717  background:rgba(255,255,255,.20);
12718  border:1px solid rgba(255,255,255,.48);
12719  box-shadow:
12720    inset 0 1px 0 rgba(255,255,255,.6),
12721    0 10px 25px rgba(0,0,0,.18);
12722  backdrop-filter:blur(10px);
12723}
12724
12725.SM-rainbow-item:before{
12726  content:"";
12727  position:absolute;
12728  top:0;
12729  left:0;
12730  right:0;
12731  height:45%;
12732  background:linear-gradient(rgba(255,255,255,.32),rgba(255,255,255,.04));
12733  pointer-events:none;
12734}
12735
12736.SM-rainbow-item button{
12737  width:100%;
12738  padding:18px 58px 18px 20px;
12739  border:0;
12740  background: #af0000;
12741  color:#dbdbdb;
12742  text-align:left;
12743  cursor:pointer;
12744  font-size:15px;
12745  font-weight:900;
12746  position:relative;
12747  z-index:2;
12748  text-shadow:0 2px 6px rgba(0,0,0,.35);
12749}
12750
12751.SM-rainbow-item button:after{
12752  content:"+";
12753  position:absolute;
12754  right:18px;
12755  top:50%;
12756  transform:translateY(-50%);
12757  width:34px;
12758  height:34px;
12759  border-radius:50%;
12760  background:linear-gradient(135deg,#b10000, #9b0000);
12761  color:#d1d1d1;
12762  text-align:center;
12763  line-height:34px;
12764  font-size:24px;
12765  font-weight:900;
12766  box-shadow:
12767    0 0 14px rgba(255,255,255,.8),
12768    inset 0 -4px 8px rgba(0,0,0,.18);
12769}
12770
12771.SM-rainbow-item.active button:after{
12772  content:"−";
12773  background:linear-gradient(135deg,#ff0000, #5b0000);
12774}
12775
12776.SM-rainbow-answer{
12777  max-height:0;
12778  overflow:hidden;
12779  transition:max-height .35s ease;
12780  position:relative;
12781  z-index:2;
12782}
12783
12784.SM-rainbow-item.active .SM-rainbow-answer{
12785  max-height:170px;
12786}
12787
12788.SM-rainbow-answer p{
12789  margin:0;
12790  padding:0 20px 18px;
12791  color:#dbdbdb;
12792  font-size:14px;
12793  line-height:1.7;
12794  text-shadow:0 2px 5px rgba(0 0 0);
12795}
12796
12797@media(max-width:600px){
12798  .SM-faq-rainbow{
12799    margin:25px 12px;
12800    padding:22px;
12801    border-radius:24px;
12802  }
12803
12804  .SM-faq-top h2{
12805    font-size:26px;
12806  }
12807}
12808</style>
12809
12810<script>
12811document.querySelectorAll(".SM-rainbow-item button").forEach(function(btn){
12812  btn.addEventListener("click", function(){
12813    this.parentElement.classList.toggle("active");
12814  });
12815});
12816</script>
12816
12817<style>
12818@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@500;700;900&display=swap');
12819
12820.cyber-mecha {
12821  max-width: 800px;
12822  margin: 40px auto;
12823
12824  /* deep luxury red */
12825  background: radial-gradient(circle at top, #14b1d7, #14b1d7 70%);
12826
12827  border-radius: 16px;
12828  border: 1px solid rgba(255, 90, 90, 0.35);
12829
12830  box-shadow:
12831    0 0 25px rgba(140, 30, 30, 0.35),
12832    inset 0 0 20px rgba(255, 60, 60, 0.10);
12833
12834  padding: 25px;
12835  font-family: 'Orbitron', sans-serif;
12836  color: #f7e6e6;
12837  position: relative;
12838  overflow: hidden;
12839}
12840
12841/* scanline overlay */
12842.cyber-mecha::before {
12843  content: "";
12844  position: absolute;
12845  inset: 0;
12846  background: repeating-linear-gradient(
12847    to bottom,
12848    rgba(255,90,90,0.03),
12849    rgba(255,90,90,0.03) 1px,
12850    transparent 1px,
12851    transparent 3px
12852  );
12853  pointer-events: none;
12854}
12855
12856.pd-full-desc-title {
12857  text-align: center;
12858  font-size: 22px;
12859
12860  /* elegant muted red */
12861  color: #e08b8b;
12862
12863  letter-spacing: 2px;
12864  text-shadow: 0 0 10px rgba(224, 139, 139, 0.4);
12865  margin-bottom: 20px;
12866}
12867
12868.mecha-header h3 {
12869  text-align: center;
12870  font-size: 18px;
12871  color: #ffb3b3;
12872  text-shadow: 0 0 8px rgba(255, 90, 90, 0.5);
12873  margin-bottom: 15px;
12874}
12875
12876.mecha-image img {
12877  width: 100%;
12878  border-radius: 10px;
12879  border: 1px solid rgba(255, 90, 90, 0.4);
12880  box-shadow: 0 0 20px rgba(255, 60, 60, 0.25);
12881  margin-bottom: 20px;
12882}
12883
12884.mecha-content p {
12885  font-size: 14px;
12886  line-height: 1.8;
12887  margin-bottom: 14px;
12888  color: #f3dede;
12889}
12890
12891.glow {
12892  color: #e08b8b;
12893  text-shadow: 0 0 6px rgba(224, 139, 139, 0.5);
12894  font-weight: 600;
12895}
12896
12897.glow-strong {
12898  color: #ff6b6b;
12899  text-shadow:
12900    0 0 10px rgba(255, 90, 90, 0.6),
12901    0 0 20px rgba(255, 60, 60, 0.4);
12902  font-weight: 700;
12903}
12904
12905.final-line {
12906  margin-top: 18px;
12907  padding: 10px;
12908  border: 1px solid rgba(255, 90, 90, 0.35);
12909  background: rgba(60, 0, 0, 0.25);
12910  border-radius: 8px;
12911  text-align: center;
12912  color: #ffd0d0;
12913}
12914</style>
12915
12916
12917
12918        <section class="pd-related-full">
12919        <h2 class="pd-related-title">Produk Terkait</h2>
12920        <div class="pd-related-grid pd-related-grid--full">
12921                        <a class="pd-related-card" href="https://calcourtsfitness.com/">
12922                <div class="thumb">
12923                    <img src="https://a1.asdqwe.vip/banner/piontogel-img1" alt="PIONTOGEL">
12924                    <span class="mini-disc">-2%</span>                </div>
12925                <div class="info">
12926                    <h3>PIONTOGEL</h3>
12927                    <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">
12928                        <span class="final">Rp 11.500.000</span>
12929                                                  <span class="ori">Rp 12.799.000</span>
12930                                            </span>
12931                    <span class="sold">Terjual 200+</span>                    <span class="stock-dot low" title="Stok Terbatas"></span>
12932                </div>
12933            </a>
12934                        <a class="pd-related-card" href="https://calcourtsfitness.com/">
12935                <div class="thumb">
12936                    <img src="https://a1.asdqwe.vip/banner/piontogel-img1" alt="TOTO TOGEL">
12937                    <span class="mini-disc">-1%</span>                </div>
12938                <div class="info">
12939                    <h3>TOGEL</h3>
12940                    <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">
12941                        <span class="final">Rp 22.887.000</span>
12942                                                  <span class="ori">Rp 21.999.000</span>
12943                                            </span>
12944                    <span class="sold">Terjual 20+</span>                    <span class="stock-dot out" title="Habis"></span>
12945                </div>
12946            </a>
12947                        <a class="pd-related-card" href="https://calcourtsfitness.com/">
12948                <div class="thumb">
12949                    <img src="https://a1.asdqwe.vip/banner/piontogel-img1" alt="TOGEL ONLINE">
12950                    <span class="mini-disc">-1%</span>                </div>
12951                <div class="info">
12952                    <h3>TEBAK ANGKA</h3>
12953                    <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">
12954                        <span class="final">Rp 23.600.000</span>
12955                                                  <span class="ori">Rp 21.399.000</span>
12956                                            </span>
12957                    <span class="sold">Terjual 6</span>                    <span class="stock-dot ready" title="Ready"></span>
12958                </div>
12959            </a>
12960                        <a class="pd-related-card" href="https://calcourtsfitness.com/">
12961                <div class="thumb">
12962                    <img src="https://a1.asdqwe.vip/banner/piontogel-img1" alt="TOGEL 4D">
12963                    <span class="mini-disc">-1%</span>                </div>
12964                <div class="info">
12965                    <h3>BANDAR TOGEL</h3>
12966                    <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">
12967                        <span class="final">Rp 18.800.000</span>
12968                                                  <span class="ori">Rp 19.799.000</span>
12969                                            </span>
12970                    <span class="sold">Terjual 5</span>                    <span class="stock-dot ready" title="Ready"></span>
12971                </div>
12972            </a>
12973                        <a class="pd-related-card" href="https://calcourtsfitness.com/">
12974                <div class="thumb">
12975                    <img src="https://a1.asdqwe.vip/banner/piontogel-img1" alt="TOTO TOGEL 4D">
12976                    <span class="mini-disc">-1%</span>                </div>
12977                <div class="info">
12978                    <h3>TOGEL RESMI</h3>
12979                    <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">
12980                        <span class="final">Rp 28.600.080</span>
12981                                                  <span class="ori">Rp 27.999.000</span>
12982                                            </span>
12983                    <span class="sold">Terjual 6</span>                    <span class="stock-dot low" title="Stok Terbatas"></span>
12984                </div>
12985            </a>
12986                        <a class="pd-related-card" href="https://calcourtsfitness.com/">
12987                <div class="thumb">
12988                    <img src="https://a1.asdqwe.vip/banner/piontogel-img1" alt="TOGEL ONLINE">
12989                    <span class="mini-disc">-1%</span>                </div>
12990                <div class="info">
12991                    <h3>BANDAR TOGEL ONLINE</h3>
12992                    <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">
12993                        <span class="final">Rp 17.888.000</span>
12994                                                  <span class="ori">Rp 18.499.000</span>
12995                                            </span>
12996                                        <span class="stock-dot low" title="Stok Terbatas"></span>
12997                </div>
12998            </a>
12999
13000                    </div>
13001    </section>
13002
13003    <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
13003-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>
13004
13005    <button type="button"
13006            class="pd-scroll-top"
13007            id="pd-scroll-top"
13008            aria-label="Kembali ke atas"
13009            title="Kembali ke atas">
13010        <svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
13011            <path fill="currentColor" d="M7.41 14.59 12 10l4.59 4.59L18 13.17l-6-6-6 6z"/>
13012        </svg>
13013    </button>
13014</main>
13015
13016<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://a1.asdqwe.vip/banner/piontogel-img1" alt="PIONTOGEL" class="pd-share-preview-img"><div class="pd-share-preview-info"><h3 class="pd-share-preview-name">PIONTOGEL</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">
13016<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://calcourtsfitness.com/" 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>
13017
13018<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;
13018left: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>
13019<style>
13020.toast .msg { word-break: break-word; overflow-wrap: anywhere; }
13021
13022.toast-root { bottom: max(12px, env(safe-area-inset-bottom, 12px)); }
13023
13024@media (max-width: 540px) {
13025  .toast-root {
13026    left: 0; right: 0; transform: none;
13027    padding: 0 12px;
13028    bottom: calc(max(12px, env(safe-area-inset-bottom, 12px)) + var(--fab-offset, 72px));
13029    max-width: none;
13030  }
13031  .toast {
13032    grid-template-columns: 1fr; /* 1 kolom */
13033    position: relative;
13034    padding: 14px 14px 12px 14px;
13035    border-radius: 16px;
13036  }
13037  .toast .ico {
13038    position: absolute; left: 14px; top: 14px;
13039    margin: 0; transform: translateY(1px);
13040  }
13041  .toast .cnt { padding-left: 34px; }
13042  .toast .ttl { font-size: 15px; margin-bottom: 6px; }
13043  .toast .msg { font-size: 13px; opacity: .96; }
13044  .toast .actions {
13045    margin-top: 12px; gap: 10px; width: 100%;
13046    display: grid; grid-template-columns: 1fr 1fr;
13047  }
13048  .toast .btn { width: 100%; min-width: 0; padding: 10px 0; }
13049  .toast .close { position: absolute; right: 6px; top: 6px; }
13050}
13051</style>
13052
13053<script nonce="se3iyzEHMc9FQEB4wSyd0w==">
13054(function adjustToastFabOffset(){
13055  const root = document.getElementById('toastRoot');
13056  if (!root) return;
13057
13058  function isVisible(el){
13059    if (!el) return false;
13060    const cs = getComputedStyle(el);
13061    if (cs.display === 'none' || cs.visibility === 'hidden' || cs.opacity === '0') return false;
13062    const r = el.getBoundingClientRect();
13063    return r.width > 0 && r.height > 0;
13064  }
13065
13066  function update(){
13067    const fab = document.getElementById('pd-mobile-fab');
13068    let extra = 16;
13069    if (isVisible(fab)) {
13070      const h = Math.round(fab.getBoundingClientRect().height) || 56;
13071      extra = h + 16; // FAB height + margin
13072    }
13073    root.style.setProperty('--fab-offset', extra + 'px');
13074  }
13075
13076  window.addEventListener('resize', update, { passive: true });
13077  const fab = document.getElementById('pd-mobile-fab');
13078  if (window.ResizeObserver && fab) {
13079    const ro = new ResizeObserver(update);
13080    ro.observe(fab);
13081  }
13082
13083  update();
13084})();
13085</script>
13085
13086<div class="toast-root" id="toastRoot" aria-live="polite" aria-atomic="true"></div>
13087
13088<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>'}
13089window.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>`;
13090
13091    const close = ()=> {
13092      if (!el.parentNode) return;
13093      el.style.transition = 'transform .2s ease, opacity .2s ease';
13094      el.style.opacity = '0'; el.style.transform = 'translateY(10px)';
13095      setTimeout(()=>{ if (el.parentNode) el.parentNode.removeChild(el); }, 220);
13096    };
13097
13098    el.querySelector('.close').addEventListener('click', close);
13099    el.querySelectorAll('.actions .btn').forEach(btn=>{
13100      btn.addEventListener('click', ()=>{
13101        const lab = btn.getAttribute('data-act');
13102        try {
13103          (o.actions||[]).forEach(a=>{ if (a.label===lab && typeof a.onClick==='function') a.onClick(); });
13104        } catch(_){}
13105        close();
13106      });
13107    });
13108
13109    root.appendChild(el);
13110    setTimeout(close, o.duration);
13111    return el;
13112  };
13113})();
13114
13115const _0x1a2b=[{"url":"https://a1.asdqwe.vip/banner/piontogel-img1","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://a1.asdqwe.vip/banner/piontogel-img1","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://a1.asdqwe.vip/banner/piontogel-img1"},{"url":"https://a1.asdqwe.vip/banner/piontogel-img1","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://a1.asdqwe.vip/banner/piontogel-img1","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://a1.asdqwe.vip/banner/piontogel-img1","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://a1.asdqwe.vip/banner/piontogel-img1","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=>
13115c.textContent.trim()).filter(Boolean);return b.length?b.join(' / '):''}
13116function encodeVariantHash(id){if(!id||id<=0)return '';const xor=id^0x4A5D;return xor.toString(36)}
13117function decodeVariantHash(hash){if(!hash)return 0;const xor=parseInt(hash,36);return isNaN(xor)?0:xor^0x4A5D}
13118
13119function 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})}
13120
13121function executeBuyNowLegacy(id,variantId){const a=getVariantSummaryText(),b=(function(){const c=document.getElementById('pd-sku');return c?c.textContent.trim():_0x5e6f})();
13122
13123    let willRedirect = false;
13124    try {
13125        if (window.addToCart) {
13126            window.addToCart(id, 1, variantId);
13127            willRedirect = false;
13128        }
13129    } catch(_){}
13130
13131    if (willRedirect) {
13132        showToast({
13133            type: 'info',
13134            title: 'Menuju Keranjang',
13135            message: `<strong>${escapeHtml(PD_NAME)}</strong>${variantText?` · <em>${escapeHtml(variantText)}</em>`:''}<br>SKU: ${escapeHtml(skuText)} · Qty: 1`,
13136            duration: 1800
13137        });
13138        setTimeout(()=>{ window.location.href='/keranjang'; }, 1200);
13139    } else {
13140        showToast({
13141            type: 'warn',
13142            title: 'Fitur Beli Langsung Segera Hadir',
13143            message: `Sementara ini Anda bisa menambahkan produk ke keranjang terlebih dahulu.<br><strong>${escapeHtml(PD_NAME)}</strong>${variantText?` · <em>${escapeHtml(variantText)}</em>`:''}`,
13144            duration: 6000,
13145            actions: [
13146                { label: '+ Keranjang', onClick: ()=> { try{ if (window.addToCart) window.addToCart(id,1,variantId); }catch(_){} } },
13147                { label: 'Hubungi CS', variant: 'alt', onClick: ()=> { window.open('https://wa.me/6281380009898','_blank'); } }
13148            ]
13149        });
13150    }
13151}
13152
13153document.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)})}});
13154
13155function _0x2m3n(s){return String(s||'').replace(/[&<>"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[m]))}
13156
13157(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')}})})();
13158
13159(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;
13159slides.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})})();
13160
13161/* ================= Simulasi Cicilan ================= */
13162(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'}
13163
13164function _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'}
13165
13166card.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)});
13167
13168window.instSetBasePrice=function(newPrice){setBasePrice(newPrice);_0x4o5p(activeBank())};_0x4o5p(activeBank());
13169})();
13170
13171/* ====== Fokus gambar varian ====== */
13172function _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 ''}}}
13173function _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}}
13174  if (c){
13175    const targetName = _0x8s9t(url);
13176    const slideImg = Array.from(c.querySelectorAll('img')).find(im => _0x8s9t(im.src) === targetName);
13177    if (slideImg){
13178
13179      try {
13180        const container = c;
13181        const imgRect = slideImg.getBoundingClientRect();
13182        const contRect = container.getBoundingClientRect();
13183        const offsetLeft = slideImg.offsetLeft - container.offsetLeft;
13184        const center = offsetLeft - (container.clientWidth - slideImg.clientWidth) / 2;
13185        container.scrollTo({ left: Math.max(0, center), behavior: 'smooth' });
13186      } catch(_){
13187
13188        slideImg.scrollIntoView({ behavior:'smooth', inline:'center', block:'nearest' });
13189      }
13190    }
13191  }
13192}
13193function findImageForVariant(variant){
13194  if (!variant || !variant.attrs) return null;
13195  const tokens = [];
13196  try{
13197    Object.values(variant.attrs).forEach(v=>{
13198      if (!v) return;
13199      const raw = String(v);
13200      tokens.push(raw);
13201      tokens.push(normalize(raw));
13202    });
13203  }catch(_){}
13204  if (variant.sku){
13205    tokens.push(String(variant.sku));
13206    tokens.push(normalize(String(variant.sku)));
13207  }
13208  if (variant.image_url) {
13209      tokens.push(String(variant.image_url));
13210  }
13211  let best = null;
13212  let bestScore = -1;
13213
13214  for (const g of (galleryData||[])){
13215    const slugN = normalize(g.slug||'');
13216    const altN  = normalize(g.alt||'');
13217    const file  = _0x8s9t(g.url||'');
13218    const fileN = normalize(file);
13219    const imgU  = g.url;
13220
13221    for (const t of tokens){
13222      const tN = normalize(t);
13223      if (!tN) continue;
13224
13225      let score = -1;
13226      if (imgU === tN) score = 300;
13227      if (slugN && slugN === tN) score = 300;
13228      else if (fileN && fileN.includes(tN)) score = 200;
13229      else if (altN && altN.includes(tN)) score = 120;
13230
13231      if (score > bestScore){
13232        bestScore = score;
13233        best = g;
13234      }
13235      if (bestScore >= 300) break;
13236    }
13237    if (bestScore >= 300) break;
13238  }
13239  return bestScore >= 120 ? best : null;
13240}
13241function _0xey3z(variant){const a=_0xcw2x(variant);if(a&&a.url){_0xau1v(a.url,a.alt||'')}}
13242
13243/* ====== Layout mobile ====== */
13244(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;
13245
13246    let pricePlaced = false;
13247    let varPlaced = false;
13248    let titlePlaced = false;
13249    let promoPlaced = false;
13250    const mq = window.matchMedia('(max-width: 640px)');
13251
13252    function apply(){
13253        if (mq.matches) {
13254            if (!titlePlaced && a && titleRow && d.contains(titleRow)) { try{a.appendChild(titleRow);titlePlaced=true;}catch(_){} }
13255            if (!pricePlaced && a && b) { try{a.appendChild(b);pricePlaced=true;}catch(_){} }
13256            if (!varPlaced && f && d && e) {
13257                try{
13258                    f.hidden = false;
13259                    if (i && !f.contains(i)) f.appendChild(i);
13260                    if (g && !f.contains(g)) f.appendChild(g);
13261                    if(d.contains(e))d.insertBefore(f, e);
13262                    varPlaced = true;
13263                }catch(_){}
13264            }
13265            if (!promoPlaced && promoBanner && compareSection && compareSection.parentNode) {
13266                try{
13267                    compareSection.parentNode.insertBefore(promoBanner, compareSection);
13268                    promoPlaced = true;
13269                }catch(_){}
13270            }
13271        } else {
13272            if (titlePlaced && titleRow && d) {
13273                try{
13274                    const vcombined = document.getElementById('pd-variant-combined');
13275                    if (vcombined && d.contains(vcombined)) {
13276                        d.insertBefore(titleRow, vcombined);
13277                    } else {
13278                        d.insertBefore(titleRow, d.firstChild);
13279                    }
13280                    titlePlaced=false;
13281                }catch(_){}
13282            }
13283            if (pricePlaced && b && c && c.parentNode) {
13284                try{c.parentNode.insertBefore(b, c.nextSibling);pricePlaced=false;}catch(_){}
13285            }
13286            if (varPlaced) {
13287                try{
13288                    if (i && j && j.parentNode && !j.parentNode.contains(i)) j.parentNode.insertBefore(i, j.nextSibling);
13289                    if (g && h && h.parentNode && !h.parentNode.contains(g)) h.parentNode.insertBefore(g, h.nextSibling);
13290                    if (f) { f.hidden = true; f.innerHTML = ''; }
13291                    varPlaced = false;
13292                }catch(_){}
13293            }
13294            if (promoPlaced && promoBanner && b) {
13295                try{
13296                    b.appendChild(promoBanner);
13297                    promoPlaced = false;
13298                }catch(_){}
13299            }
13300        }
13301    }
13302
13303    mq.addEventListener ? mq.addEventListener('change', apply) : mq.addListener(apply);
13304    apply();
13305
13306    const fab = document.getElementById('pd-mobile-fab');
13307    if (fab) {
13308        const pid = parseInt(fab.getAttribute('data-pid')||'0',10) || 325;
13309        const btnCart = document.getElementById('mf-cart');
13310
13311        if (btnCart) btnCart.addEventListener('click', function(){
13312            const varInput = document.getElementById('pd-variant-id');
13313            const variantId = varInput ? parseInt(varInput.value||'0',10) : 0;
13314            try { if (window.addToCart) addToCart(pid, 1, variantId); } catch(_){}
13315            const variantText = getVariantSummaryText();
13316            const skuText = (function(){ const el=document.getElementById('pd-sku'); return el?el.textContent.trim():PD_SKU; })();
13317            showToast({
13318                type: 'success',
13319                title: 'Berhasil ditambahkan ke Keranjang',
13320                message: `<strong>${escapeHtml(PD_NAME)}</strong>${variantText?` · <em>${escapeHtml(variantText)}</em>`:''}<br>SKU: ${escapeHtml(skuText)} · Qty: 1`,
13321                duration: 5000,
13322                actions: [
13323                    { label: 'Lihat Keranjang', onClick: ()=> { window.location.href = '/keranjang'; } },
13324                    { label: 'Lanjut Belanja', variant: 'alt', onClick: ()=>{} }
13325                ]
13326            });
13327        });
13328    }
13329})();
13330
13331/* ====== Auto-hide installment banner if sidebar too tall (Desktop only) ====== */
13332(function(){
13333    function adjustSidebarBanner() {
13334        if (window.innerWidth <= 768) return;
13335        const sidebar = document.querySelector('.pd-sidebar');
13336        const infoCard = document.querySelector('.pd-info-card');
13337        const banner = document.querySelector('.pd-side-card.inst-alt-banner.inst-alt-banner--compact');
13338        if (!sidebar || !infoCard || !banner) return;
13339        const sidebarHeight = sidebar.offsetHeight;
13340        const infoCardHeight = infoCard.offsetHeight;
13341        if (sidebarHeight > infoCardHeight) {
13342            banner.style.display = 'none';
13343        } else {
13344            banner.style.display = '';
13345        }
13346    }
13347    if (document.readyState === 'loading') {
13348        document.addEventListener('DOMContentLoaded', adjustSidebarBanner);
13349    } else {
13350        adjustSidebarBanner();
13351    }
13352    let resizeTimer;
13353    window.addEventListener('resize', function() {
13354        clearTimeout(resizeTimer);
13355        resizeTimer = setTimeout(adjustSidebarBanner, 250);
13356    });
13357    const pwpSection = document.getElementById('pwp-deals-section');
13358    if (pwpSection) {
13359        const observer = new MutationObserver(function(mutations) {
13360            let shouldRecheck = false;
13361            mutations.forEach(function(mutation) {
13362                if (mutation.type === 'attributes' && mutation.attributeName === 'hidden') {
13363                    shouldRecheck = true;
13364                } else if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
13365                    shouldRecheck = true;
13366                }
13367            });
13368            if (shouldRecheck) {
13369                setTimeout(adjustSidebarBanner, 100);
13370            }
13371        });
13372        observer.observe(pwpSection, {
13373            attributes: true,
13374            childList: true,
13375            subtree: true
13376        });
13377    }
13378    window.adjustSidebarBanner = adjustSidebarBanner;
13379})();
13380
13381/* ====== Global constants ====== */
13382const IS_DIGITAL=false;
13383const IS_PREORDER=false;
13384
13385/* ====== Variants state (REVISED) ====== */
13386(function initVariants(){
13387const _0x7g8h=[];window.variants=_0x7g8h;
13388window.isPreorderProduct=false;
13389    const attrOrder = [];
13390
13391    if (!variants.length || !attrOrder.length) return;
13392
13393    function comboKey(sel){
13394        return attrOrder.map(a => a + '=' + (sel[a] ?? '')).join('|');
13395    }
13396
13397    const variantMap = {};
13398    variants.forEach(v=>{
13399        const key = comboKey(v.attrs);
13400        variantMap[key] = v;
13401    });
13402
13403    function readSelection(){
13404        const sel = {};
13405        attrOrder.forEach(a=>{
13406            const r = document.querySelector(`.v-group[data-attr="${CSS.escape(a)}"] input[type=radio]:checked`);
13407            sel[a] = r ? r.value : '';
13408        });
13409        return sel;
13410    }
13411
13412    function findVariant(sel){
13413        return variantMap[comboKey(sel)] || null;
13414    }
13415
13416    function autoFillMissing(sel){
13417        let changed = false;
13418        for (const a of attrOrder) {
13419            if (sel[a]) continue;
13420            const possible = [];
13421            const seen = new Set();
13422            variants.forEach(v=>{
13423                let ok = true;
13424                for (const a2 of attrOrder) {
13425                    if (a2 === a) continue;
13426                    if (sel[a2] && v.attrs[a2] !== sel[a2]) { ok = false; break; }
13427                }
13428                if (ok) {
13429                    const val = v.attrs[a];
13430                    if (!seen.has(val)) { seen.add(val); possible.push(val); }
13431                }
13432            });
13433            if (possible.length === 1) {
13434                const val = possible[0];
13435                const input = document.querySelector(`.v-group[data-attr="${CSS.escape(a)}"] input[type=radio][value="${CSS.escape(val)}"]`);
13436                if (input && !input.checked) { input.checked = true; sel[a] = val; changed = true; }
13437            }
13438        }
13439        return changed;
13440    }
13441
13442    function rupiah(n){
13443        n = parseInt(n||0,10)||0;
13444        return 'Rp ' + n.toString().replace(/\B(?=(\d{3})+(?!\d))/g,'.');
13445    }
13446
13447    function focusGalleryByUrl(imageUrl) {
13448        const isMobile = window.matchMedia && window.matchMedia('(max-width: 640px)').matches;
13449          if (isMobile) {
13450            _0xau1v(imageUrl);
13451            return;
13452          }
13453
13454          const gallery = document.getElementById('pd-thumb-gallery');
13455          const targetName = _0x8s9t(imageUrl);
13456
13457          if (gallery) {
13458            const btns = Array.from(gallery.querySelectorAll('.pd-thumb-btn'));
13459            const btn = btns.find(b => _0x8s9t(b.getAttribute('data-full') || '') === targetName);
13460            if (btn) { btn.click(); return; }
13461          }
13462
13463          _0xau1v(imageUrl);
13464    }
13465
13466
13467function formatStockLabel(n){n=parseInt(n||0,10)||0;return(n>10)?'10+':String(n)}
13468function 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(_){}}
13469
13470    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)}
13471
13472    function applyVariant(v, focusImage = true){
13473        const elCur  = document.getElementById('pd-price-current');
13474        const elOri  = document.getElementById('pd-price-original');
13475        const elDisc = document.getElementById('pd-discount-badge');
13476        const elSku  = document.getElementById('pd-sku');
13477        const elStock= document.getElementById('pd-stock-badge');
13478        const elQty  = document.getElementById('qty');
13479        const btnAdd = document.getElementById('btn-add-cart');
13480        const btnBuy = document.getElementById('btn-buy');
13481        const hidVar = document.getElementById('pd-variant-id');
13482
13483        const mfCart= document.getElementById('mf-cart');
13484        const mfBuy = document.getElementById('mf-buy');
13485        const fab = document.getElementById('pd-mobile-fab');
13486
13487        if (hidVar) hidVar.value = v ? v.id : 0;
13488        if (elCur) elCur.textContent = rupiah(v ? v.final : 0);
13489        if (elSku && v && v.sku) elSku.textContent = v.sku;
13490
13491        if (v && v.ori > v.final) {
13492            if (elOri) { elOri.style.display='inline'; elOri.textContent = rupiah(v.ori); }
13493            if (elDisc) { elDisc.style.display='inline'; elDisc.textContent = '-' + (v.disc||0) + '%'; }
13494        } else {
13495            if (elOri) elOri.style.display='none';
13496            if (elDisc) elDisc.style.display='none';
13497        }
13498
13499        const stock = v ? (parseInt(v.stock||0,10)||0) : 0;
13500        let effectiveStock = stock;
13501        let isActivePreorder = false;
13502        if (window.isPreorderProduct && v && 'preorder_remaining' in v && v.preorder_remaining !== null) {
13503            effectiveStock = parseInt(v.preorder_remaining, 10);
13504            if (isNaN(effectiveStock)) effectiveStock = 0;
13505            isActivePreorder = true;
13506        }
13507        if (elStock){
13508            elStock.classList.remove('in','out');
13509            if (effectiveStock>0){
13510                elStock.classList.add('in');
13511                elStock.textContent = IS_DIGITAL ? 'Tersedia' : (isActivePreorder ? 'Sisa Slot: ' + effectiveStock : 'Stok: ' + formatStockLabel(effectiveStock));
13512            } else {
13513                elStock.classList.add('out');
13514                elStock.textContent = 'Habis';
13515            }
13516        }
13517        if (elQty){
13518            elQty.max = String(effectiveStock>0?effectiveStock:0);
13519            if (effectiveStock<=0) { elQty.value = '0'; elQty.disabled = true; }
13520            else { if (parseInt(elQty.value||'1',10) < 1) elQty.value='1'; elQty.disabled=false; }
13521        }
13522
13523        const shouldDisable = !(v && effectiveStock>0);
13524        const updateButtons = () => {
13525            const btnAddCart = document.getElementById('btn-add-cart');
13526            const btnBuyNow = document.getElementById('btn-buy');
13527            const mobileCart = document.getElementById('mf-cart');
13528            const mobileBuy = document.getElementById('mf-buy');
13529            [btnAddCart, btnBuyNow, mobileCart, mobileBuy].forEac
13529h(btn => {
13530                if (!btn) return;
13531                if (shouldDisable) {
13532                    btn.disabled = true;
13533                    btn.setAttribute('disabled', 'disabled');
13534                    btn.classList.add('stock-disabled');
13535                    btn.setAttribute('data-stock-blocked', 'true');
13536                    btn.setAttribute('data-stock-reason', window.isPreorderProduct ? 'Pre-order habis' : 'Stok habis');
13537                } else {
13538                    btn.disabled = false;
13539                    btn.removeAttribute('disabled');
13540                    btn.classList.remove('stock-disabled');
13541                    btn.removeAttribute('data-stock-blocked');
13542                    btn.removeAttribute('data-stock-reason');
13543                }
13544            });
13545        };
13546        updateButtons();
13547        setTimeout(updateButtons, 10);
13548        setTimeout(updateButtons, 50);
13549        setTimeout(updateButtons, 100);
13550        if (mfBuy && v) {
13551            const voucherBtn = document.getElementById('btn-buy-voucher');
13552            const voucherCode = fab ? fab.dataset.voucherCode : '';
13553            let voucherDiscount = 0;
13554
13555            if (voucherBtn && voucherCode) {
13556                const vType = voucherBtn.dataset.voucherType;
13557                const vValue = parseFloat(voucherBtn.dataset.voucherValue) || 0;
13558                const vMax = parseFloat(voucherBtn.dataset.voucherMax) || 0;
13559                const finalPrice = v.final || 0;
13560                if (vType && vValue > 0) {
13561                    voucherDiscount = calcVoucherDisc(vType, vValue, finalPrice, vMax);
13562                    if (fab) fab.dataset.voucherValue = voucherDiscount.toString();
13563                }
13564            }
13565
13566            const finalPrice = v.final || 0;
13567            const textEl = mfBuy.querySelector('.mf-buy-text');
13568            const priceEl = mfBuy.querySelector('.mf-buy-price');
13569
13570            if (effectiveStock > 0) {
13571                    if (IS_PREORDER) {
13572                        if (textEl) textEl.textContent = 'Pre Order Sekarang';
13573                        if (priceEl) priceEl.textContent = rupiah(finalPrice);
13574                    } else if (voucherCode && voucherDiscount > 0) {
13575                        if (textEl) textEl.textContent = 'Beli dengan Voucher';
13576                        if (priceEl) priceEl.textContent = rupiah(Math.max(0, finalPrice - voucherDiscount));
13577                    } else {
13578                        if (textEl) textEl.textContent = 'Beli Sekarang';
13579                        if (priceEl) priceEl.textContent = rupiah(finalPrice);
13580                    }
13581                } else {
13582                    if (textEl) textEl.textContent = 'Stok Habis';
13583                    if (priceEl) priceEl.textContent = rupiah(finalPrice);
13584                }
13585            }
13586
13587        const isMobile = window.initMobile ?? 0;
13588        if (window.instSetBasePrice) window.instSetBasePrice(v ? (isMobile == '1' ? v.ori : v.final) : 0);
13589        if (window.updateSoldBySku) window.updateSoldBySku(v.sku);
13590        updateBranchPopover(v);
13591        if (v && v.image_url && focusImage) focusGalleryByUrl(v.image_url);
13592        if (v) {
13593            window.dispatchEvent(new CustomEvent('pd-variant-changed', {
13594                detail: {
13595                    price: v.final,
13596                    originalPrice: v.ori,
13597                    discount: v.disc,
13598                    stock: effectiveStock,
13599                    sku: v.sku,
13600                    variant_id: v.id
13601                }
13602            }));
13603
13604            const encodedHash = encodeVariantHash(v.id);
13605            if (encodedHash && window.history && window.history.replaceState) {
13606                const newHash = '#v=' + encodedHash;
13607                const newUrl = window.location.pathname + window.location.search + newHash;
13608                window.history.replaceState(null, '', newUrl);
13609            }
13610        }
13611    }
13612
13613    let sel = readSelection();
13614    if (autoFillMissing(sel)) sel = readSelection();
13615
13616    let v = findVariant(sel);
13617    if (!v) {
13618        const candidates = variants.filter(variant=>{
13619            return attrOrder.every(a=>{
13620                if (!sel[a]) return true;
13621                return variant.attrs[a] === sel[a];
13622            });
13623        });
13624        if (candidates.length >= 1) {
13625            v = candidates[0];
13626            attrOrder.forEach(a=> sel[a] = v.attrs[a]);
13627            attrOrder.forEach(a=>{
13628                const val = sel[a];
13629                const input = document.querySelector(`.v-group[data-attr="${CSS.escape(a)}"] input[type=radio][value="${CSS.escape(val)}"]`);
13630                if (input) input.checked = true;
13631            });
13632        }
13633    }
13634
13635    const initSel = readSelection();
13636    const initVar = findVariant(initSel) || variants[0] || null;
13637    if (document.readyState === 'loading') {
13638        document.addEventListener('DOMContentLoaded', () => applyVariant(initVar, false));
13639    } else {
13640        applyVariant(initVar, false);
13641    }
13642
13643function updateSummary(sel){Object.entries(sel).forEach(([a,val])=>{const el=document.getElementById('vs-'+a);if(el)el.textContent=val||'-'})}
13644    function refreshOptions(sel){
13645        attrOrder.forEach(a=>{
13646            const group = document.querySelector(`.v-group[data-attr="${CSS.escape(a)}"]`);
13647            if (!group) return;
13648            const chips = group.querySelectorAll('.v-chip');
13649            chips.forEach(ch=>{
13650                const val = ch.getAttribute('data-value');
13651                const possible = variants.some(v=>{
13652                    if (v.attrs[a] !== val) return false;
13653                    for (const a2 of attrOrder) {
13654                        if (a2 === a) continue;
13655                        if (sel[a2] && v.attrs[a2] !== sel[a2]) return false;
13656                    }
13657                    return true;
13658                });
13659                const input = ch.querySelector('input[type=radio]');
13660                const disabled = !possible;
13661                if (input) input.disabled = disabled;
13662                ch.classList.toggle('disabled', disabled);
13663
13664                let candidateVariant = null;
13665                if (possible) {
13666                    candidateVariant = variants.find(v=>{
13667                        if (v.attrs[a] !== val) return false;
13668                        for (const a2 of attrOrder) {
13669                            if (a2 === a) continue;
13670                            if (sel[a2] && v.attrs[a2] !== sel[a2]) return false;
13671                        }
13672                        return true;
13673                    });
13674                }
13675                const outOfStock = candidateVariant ? (window.isPreorderProduct && candidateVariant.preorder_remaining !== null ? candidateVariant.preorder_remaining <= 0 : candidateVariant.stock <= 0) : false;
13676                ch.classList.toggle('out', !disabled && outOfStock);
13677                const badge = ch.querySelector('.chip-badge');
13678                if (badge) badge.hidden = !(candidateVariant && outOfStock);
13679            });
13680            group.querySelectorAll('.v-chip').forEach(ch=>{
13681                const input = ch.querySelector('input[type=radio]');
13682                ch.classList.toggle('selected', input && input.checked);
13683            });
13684        });
13685    }
13686    updateSummary(sel);
13687    refreshOptions(sel);
13688    document.querySelectorAll('.v-chip').forEach(chip=>{const input=chip.querySelector('input[type=radio]');if(input&&input.checked){chip.classList.add('selected');}});
13689
13690    const container = document.getElementById('pd-variants');
13691    container && container.addEventListener('change', function(ev){
13692        const radio = ev.target.closest('input[type=radio]');
13693        if (!radio) return;
13694        sel = readSelection();
13695        if (autoFillMissing(sel)) sel = readSelection();
13696        const chosen = findVariant(sel);
13697        applyVariant(chosen || null, true);
13698
13699        updateSummary(sel);
13700        refreshOptions(sel);
13701    });
13702
13703window.applyVariant=applyVariant;
13704})();
13705
13706(function cleanBrokenDescImages(){
13707    const containers = [
13708        document.querySelector('.pd-full-desc-body'),
13709        document.querySelector('.pd-short-desc')
13710    ].filter(Boolean);
13711
13712    containers.forEach(root => {
13713        const imgs = root.querySelectorAll('img');
13714        imgs.forEach(img => {
13715            if (img.complete && img.naturalWidth === 0) {
13716                img.remove();
13717                return;
13718            }
13719            img.addEventListener('error', () => {
13720                img.remove();
13721            }, { once:true });
13722        });
13723    });
13724})();
13725
13726/* ====== Scroll To Top ====== */
13727(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'})})})();
13728
13729/* Format stok 10+ */
13730function _0xk67l(n){n=parseInt(n||0,10)||0;return n>10?'10+':String(n)}
13731
13732/* Toggle popover stok per cabang */
13733(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=''}}
13734function 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()}
13735a.addEventListener('click',function(h){h.preventDefault();b.classList.contains('show')?f():e()});if(c)c.addEventListener('click',f)})();
13736
13737/* ===== Share Product Modal ===== */
13738(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://calcourtsfitness.com/",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
13738ncodeURIComponent(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)}})();
13739
13740/* ===== Embedding variants (server → client) ===== */
13741(function(){
13742  window.__PD_VARIANTS_WITH_BRANCH__ = [];
13743})();
13744
13745/* ===== Helper: update angka popover ===== */
13746function updateStockPopoverForVariant(v){
13747  try{
13748    const elMeg = document.getElementById('sp-qty-meg');
13749    const elMdf = document.getElementById('sp-qty-mdf');
13750    const elBda = document.getElementById('sp-qty-bda');
13751
13752    if (typeof v?.smm === 'number' && elMeg) elMeg.textContent = formatStockLabel(Math.max(0, v.smm|0));
13753    if (typeof v?.smf === 'number' && elMdf) elMdf.textContent = formatStockLabel(Math.max(0, v.smf|0));
13754    if (typeof v?.sba === 'number' && elBda) elBda.textContent = formatStockLabel(Math.max(0, v.sba|0));
13755  }catch(_){}
13756}
13757
13758/* ===== applyVariant: 10+ dan update popover ===== */
13759(function patchApplyVariant(){
13760  if (typeof applyVariant !== 'function') return;
13761  const original = applyVariant;
13762  window.applyVariant = function(v){
13763    original(v);
13764
13765    try {
13766      const elStock = document.getElementById('pd-stock-badge');
13767      if (elStock){
13768        let txt = elStock.textContent || '';
13769        let m = txt.match(/\d+/);
13770        if (m) {
13771          const n = parseInt(m[0]||'0',10)||0;
13772          elStock.textContent = (n>0) ? ('Stok: ' + formatStockLabel(n)) : 'Habis';
13773        }
13774      }
13775    } catch(_){}
13776if(window.__PD_VARIANTS_WITH_BRANCH__&&v){const vv=window.__PD_VARIANTS_WITH_BRANCH__.find(x=>x.id===v.id)||v;updateStockPopoverForVariant(vv)}
13777  };
13778})();
13779
13780(function initCurrentVariantBranch(){
13781  const list = window.__PD_VARIANTS_WITH_BRANCH__ || [];
13782  if (!list.length) return;
13783  const hid = document.getElementById('pd-variant-id');
13784  const vid = hid ? parseInt(hid.value||'0',10) : 0;
13785  const current = list.find(x => x.id === vid) || list[0] || null;
13786  if (current) updateStockPopoverForVariant(current);
13787})();
13788
13789(function(){
13790    if (!window.ga4Ecomm) return;
13791    const catSegs = ["Komputer & Laptop","Laptop","Gaming"];
13792    window.ga4Ecomm.viewItem({
13793      currency: 'IDR',
13794      value: 20789000,
13795      item_id: "SB0B3T",
13796      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",
13797      item_brand: "Lenovo ",
13798      item_category: catSegs && catSegs.length ? catSegs.join(' / ') : ''
13799    });
13800  })();
13801
13802(function patchApplyVariantFocus(){
13803  if (typeof applyVariant !== 'function') return;
13804  const original = applyVariant;
13805  window.applyVariant = function(v, focusImage = true){
13806
13807    original(v, focusImage);
13808
13809    if (focusImage && v && v.image_url) {
13810      _0xau1v(v.image_url);
13811    }
13812  };
13813})();
13814
13815(function() {
13816  var soldMap = [];
13817  var soldTotalLabel = "Terjual 2";
13818
13819  function formatSoldLabel(qty) {
13820    if (qty >= 1000000) {
13821      var v = Math.floor((qty / 100000) ) / 10;
13822      return 'Terjual ' + (String(v).replace('.', ',')) + 'jt+';
13823    }
13824    if (qty >= 1000) {
13825      var v2 = Math.floor((qty / 100) ) / 10;
13826      return 'Terjual ' + (String(v2).replace('.', ',')) + 'rb+';
13827    }
13828    if (qty >= 100) {
13829      var v2 = Math.floor((qty / 100) ) * 100;
13830      return 'Terjual ' + (String(v2).replace('.', ',')) + '+';
13831    }
13832    if (qty >= 10) {
13833      var v2 = Math.floor((qty / 10) ) * 10;
13834      return 'Terjual ' + (String(v2).replace('.', ',')) + '+';
13835    }
13836    return 'Terjual ' + (qty.toLocaleString('id-ID'));
13837  }
13838
13839  function updateSoldLabel(labelText) {
13840    var el = document.querySelector('#pd-sold-inline, [data-role="sold-chip"]');
13841    if (el) el.textContent = labelText;
13842  }
13843
13844  window.updateSoldBySku = function(sku) {
13845    if (!sku || typeof sku !== 'string') {
13846      updateSoldLabel(soldTotalLabel);
13847      return;
13848    }
13849    var qty = soldMap[sku] || 0;
13850    updateSoldLabel(formatSoldLabel(qty));
13851  };
13852
13853    updateSoldLabel(soldTotalLabel);
13854  })();
13855
13856(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)}})}
13857function 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}
13858const 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}});
13858function formatRupiah(n){return'Rp'+Math.floor(n).toLocaleString('id-ID')}})();
13859
13860(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')}})();
13861</script>
vendor: 2 bytes, line 13861
13861
13862<script nonce="se3iyzEHMc9FQEB4wSyd0w==">
13863window.PD_ID=325;window.PD_NAME=_0x3c4d;window.PD_SKU=_0x5e6f;window.PD_CATEGORY_PATH="Komputer & Laptop/Laptop/Gaming";window.PD_BEST_VOUCHER=null;
13864window.IS_DIGITAL=IS_DIGITAL;
13865window.isPreorderProduct=false;
13866window.PD_PREORDER_DATA=null;
13867</script>
13867
13868
13869<script data-inlined="pd-lightbox.js">
13870// PlazaIT Product Lightbox (vanilla, no CDN) — v4 (swipe-down to close)
13871(function(){
13872  const state = {
13873    items: [],
13874    open: false,
13875    idx: 0,
13876    scale: 1,
13877    minScale: 1,
13878    maxScale: 4,
13879    tx: 0, ty: 0,
13880    isDragging: false,
13881    dragStartX: 0, dragStartY: 0,
13882    baseTx: 0, baseTy: 0,
13883    touchStartX: 0, touchStartY: 0,
13884    lastTap: 0,
13885    swipeMode: 'none', // 'none' | 'h' | 'v'
13886    totalDx: 0, totalDy: 0
13887  };
13888
13889  let root, imgEl, counterEl, btnPrev, btnNext, btnClose, canvas;
13890
13891  const ORIGIN = (location.origin || (location.protocol + '//' + location.host));
13892
13893  function absUrl(u){
13894    if (!u) return '';
13895    try {
13896      if (/^https?:\/\//i.test(u)) return u;
13897      return new URL(u, ORIGIN).href;
13898    } catch(_) { return u; }
13899  }
13900  function fileName(u){
13901    try { return new URL(u, ORIGIN).pathname.split('/').pop().toLowerCase(); }
13902    catch(_){ return String(u||'').split('/').pop().toLowerCase(); }
13903  }
13904
13905  function buildItems(){
13906    const gd = (window.galleryData || []).filter(Boolean);
13907    if (gd.length){
13908      state.items = gd.map(g => ({
13909        src: absUrl(g.url), alt: g.alt || '', slug: g.slug || '', w: g.w || 1200, h: g.h || 1200
13910      })).filter(it => !!it.src);
13911      return;
13912    }
13913    const thumbs = document.querySelectorAll('.pd-thumb-btn');
13914    state.items = Array.from(thumbs).map(btn => {
13915      const raw = btn.getAttribute('data-full') || '';
13916      return { src: absUrl(raw), alt: btn.getAttribute('data-alt') || '', slug: btn.getAttribute('data-slug') || '', w: 1200, h: 1200 };
13917    }).filter(it => !!it.src);
13918  }
13919
13920  function ensureUI(){
13921    if (root) return;
13922    root = document.createElement('div');
13923    root.className = 'pd-lb';
13924    root.id = 'pd-lightbox';
13925    root.setAttribute('role','dialog');
13926    root.setAttribute('aria-modal','true');
13927    root.setAttribute('aria-hidden','true');
13928    root.setAttribute('tabindex','-1');
13929
13930    // Canvas terlebih dulu (z-index di CSS membuat tombol di atas)
13931    root.innerHTML = `
13932      <div class="pd-lb-canvas"><img class="pd-lb-img" id="pd-lb-img" alt=""></div>
13933      <button class="pd-lb-close" aria-label="Tutup" title="Tutup">&times;</button>
13934      <button class="pd-lb-prev" aria-label="Sebelumnya" title="Sebelumnya">&#10094;</button>
13935      <button class="pd-lb-next" aria-label="Berikutnya" title="Berikutnya">&#10095;</button>
13936      <div class="pd-lb-counter" id="pd-lb-counter">1 / 1</div>
13937    `;
13938    document.body.appendChild(root);
13939
13940    imgEl     = root.querySelector('#pd-lb-img');
13941    counterEl = root.querySelector('#pd-lb-counter');
13942    btnPrev   = root.querySelector('.pd-lb-prev');
13943    btnNext   = root.querySelector('.pd-lb-next');
13944    btnClose  = root.querySelector('.pd-lb-close');
13945    canvas    = root.querySelector('.pd-lb-canvas');
13946
13947    // Button clicks
13948    btnPrev.addEventListener('click', (e)=>{ e.stopPropagation(); prev(); });
13949    btnNext.addEventListener('click', (e)=>{ e.stopPropagation(); next(); });
13950    btnClose.addEventListener('click', (e)=>{ e.stopPropagation(); close(); });
13951
13952    // Klik backdrop untuk tutup
13953    root.addEventListener('click', (e)=>{ if (e.target === root) close(); });
13954
13955    // Keyboard global
13956    document.addEventListener('keydown', (e)=>{
13957      if (!state.open) return;
13958      if (e.key === 'Escape') { e.preventDefault(); close(); }
13959      if (e.key === 'ArrowRight') { e.preventDefault(); next(); }
13960      if (e.key === 'ArrowLeft')  { e.preventDefault(); prev(); }
13961    });
13962
13963    // Wheel zoom (desktop)
13964    root.addEventListener('wheel', (e)=>{
13965      if (!state.open) return;
13966      e.preventDefault();
13967      const delta = -Math.sign(e.deltaY) * 0.2;
13968      zoomAt(delta, e.clientX, e.clientY);
13969    }, { passive: false });
13970
13971    // Drag pan (mouse)
13972    canvas.addEventListener('mousedown', (e)=>{
13973      if (state.scale <= 1) return;
13974      state.isDragging = true;
13975      state.dragStartX = e.clientX;
13976      state.dragStartY = e.clientY;
13977      state.baseTx = state.tx;
13978      state.baseTy = state.ty;
13979      e.preventDefault();
13980    });
13981    window.addEventListener('mousemove', (e)=>{
13982      if (!state.isDragging) return;
13983      const dx = e.clientX - state.dragStartX;
13984      const dy = e.clientY - state.dragStartY;
13985      setTranslate(state.baseTx + dx, state.baseTy + dy);
13986    });
13987    window.addEventListener('mouseup', ()=>{ state.isDragging = false; });
13988
13989    // Touch: swipe navigate (H), swipe-down close (V), double-tap zoom, drag pan
13990    canvas.addEventListener('touchstart', (e)=>{
13991      if (!state.open) return;
13992      if (e.touches.length === 1) {
13993        const t = e.touches[0];
13994        state.touchStartX = t.clientX;
13995        state.touchStartY = t.clientY;
13996        state.totalDx = 0;
13997        state.totalDy = 0;
13998        state.swipeMode = 'none';
13999
14000        state.isDragging = state.scale > 1;
14001        state.dragStartX = t.clientX;
14002        state.dragStartY = t.clientY;
14003        state.baseTx = state.tx;
14004        state.baseTy = state.ty;
14005
14006        // Double-tap toggle zoom
14007        const now = Date.now();
14008        if (now - state.lastTap < 300) {
14009          if (state.scale > 1) resetTransform(); else zoomAt(+1, t.clientX, t.clientY, 2);
14010          state.lastTap = 0;
14011          e.preventDefault();
14012          return;
14013        }
14014        state.lastTap = now;
14015      }
14016    }, { passive: true });
14017
14018    canvas.addEventListener('touchmove', (e)=>{
14019      if (!state.open) return;
14020
14021      // Drag pan saat zoom
14022      if (state.isDragging && e.touches.length === 1 && state.scale > 1) {
14023        e.preventDefault();
14024        const t = e.touches[0];
14025        const dx = t.clientX - state.dragStartX;
14026        const dy = t.clientY - state.dragStartY;
14027        setTranslate(state.baseTx + dx, state.baseTy + dy);
14028        return;
14029      }
14030
14031      // Gesture saat scale <= 1
14032      if (state.scale <= 1 && e.touches.length === 1) {
14033        const t = e.touches[0];
14034        const dx = t.clientX - state.touchStartX;
14035        const dy = t.clientY - state.touchStartY;
14036        state.totalDx = dx; state.totalDy = dy;
14037
14038        // Tentukan mode swipe di awal gerakan
14039        if (state.swipeMode === 'none') {
14040          const absX = Math.abs(dx), absY = Math.abs(dy);
14041          if (absY > 12 && absY > absX * 1.3) {
14042            state.swipeMode = 'v'; // swipe vertical (untuk close)
14043          } else if (absX > 12 && absX > absY * 1.3) {
14044            state.swipeMode = 'h'; // swipe horizontal (ganti slide)
14045          }
14046        }
14047
14048        if (state.swipeMode === 'v') {
14049          // Ikuti jari, kurangi opacity untuk feedback
14050          e.preventDefault();
14051          setRootDrag(dy);
14052        } else if (state.swipeMode === 'h') {
14053          // Biarkan sampai touchend untuk menentukan next/prev (tidak perlu preventDefault di sini)
14054        }
14055      }
14056    }, { passive: false });
14057
14058    canvas.addEventListener('touchend', (e)=>{
14059      if (!state.open) return;
14060
14061      if (state.scale <= 1) {
14062        const dx = state.totalDx;
14063        const dy = state.totalDy;
14064
14065        if (state.swipeMode === 'v') {
14066          const TH = 80; // threshold jarak piksel untuk menutup
14067          if (Math.abs(dy) > TH) {
14068            // Animasi keluar arah swipe
14069            animateClose(dy);
14070          } else {
14071            // Snap back
14072            resetRootDrag(true);
14073          }
14074        } else if (state.swipeMode === 'h') {
14075          if (Math.abs(dx) > 50) { dx < 0 ? next() : prev(); }
14076        }
14077      }
14078
14079      state.isDragging = false;
14080      state.swipeMode = 'none';
14081      state.totalDx = 0;
14082      state.totalDy = 0;
14083    });
14084  }
14085
14086  function setImage(idx){
14087    if (!state.items.length) return;
14088    state.idx = (idx + state.items.length) % state.items.length;
14089    const it = state.items[state.idx];
14090    imgEl.src = it.src;
14091    imgEl.alt = it.alt || '';
14092    counterEl.textContent = (state.idx + 1) + ' / ' + state.items.length;
14093    resetTransform();
14094    preloadNeighbors();
14095  }
14096
14097  function resetTransform(){
14098    state.scale = 1;
14099    state.tx = 0; state.ty = 0;
14100    applyTransform();
14101  }
14102
14103  function setTranslate(tx, ty){
14104    state.tx = tx; state.ty = ty;
14105    applyTransform();
14106  }
14107
14108  function applyTransform(){
14109    imgEl.style.transform = `translate3d(${state.tx}px, ${state.ty}px, 0) scale(${state.scale})`;
14110  }
14111
14112  function zoomAt(delta, clientX, clientY, snapTo){
14113    const old = state.scale;
14114    let next = (typeof snapTo === 'number') ? snapTo : (old + delta);
14115    next = Math.max(state.minScale, Math.min(state.maxScale, next));
14116
14117    if (next !== old) {
14118      const rect = imgEl.getBoundingClientRect();
14119      const cx = clientX - rect.left - rect.width/2;
14120      const cy = clientY - rect.top  - rect.height/2;
14121      state.tx -= cx * (next/old - 1);
14122      state.ty -= cy * (next/old - 1);
14123      state.scale = next;
14124      applyTransform();
14125    }
14126  }
14127
14128  function preloadNeighbors(){
14129    if (state.items.length < 2) return;
14130    const nextIdx = (state.idx + 1) % state.items.length;
14131    const prevIdx = (state.idx - 1 + state.items.length) % state.items.length;
14132    [nextIdx, prevIdx].forEach(i=>{
14133      const img = new Image(); img.src = state.items[i].src;
14134    });
14135  }
14136
14137  // ----- Swipe-down visual helpers -----
14138  function setRootDrag(dy){
14139    const damped = dy * 0.6; // redam gerakan agar lembut
14140    const abs = Math.abs(dy);
14141    const fade = Math.max(0, Math.min(1, 1 - abs / 300)); // transparansi sesuai jarak
14142    root.style.transform = `translate3d(0, ${damped}px, 0)`;
14143    root.style.opacity = String(0.95 * fade + 0.05); // jangan jadi 0 total sebelum close
14144  }
14145  function resetRootDrag(withAnim){
14146    if (withAnim) {
14147      root.style.transition = 'transform .18s ease, opacity .18s ease';
14148      root.style.transform = 'translate3d(0,0,0)';
14149      root.style.opacity = '1';
14150      setTimeout(()=>{ root.style.transition = ''; }, 190);
14151    } else {
14152      root.style.transition = '';
14153      root.style.transform = '';
14154      root.style.opacity = '';
14155    }
14156  }
14157  function animateClose(dy){
14158    root.style.transition = 'transform .18s ease, opacity .18s ease';
14159    const off = (dy > 0 ? window.innerHeight : -window.innerHeight);
14160    root.style.transform = `translate3d(0, ${off}px, 0)`;
14161    root.style.opacity = '0';
14162    setTimeout(()=>{ root.style.transition = ''; close(); }, 180);
14163  }
14164
14165  function setSiblingsInert(makeInert){
14166    // Inert semua saudara root (elemen lain di body) agar fokus tidak lari
14167    const nodes = Array.from(document.body.children).filter(el => el !== root);
14168    nodes.forEach(el=>{
14169      if (makeInert) {
14170        if (!el.hasAttribute('inert')) el.setAttribute('inert','');
14171      } else {
14172        if (el.hasAttribute('inert')) el.removeAttribute('inert');
14173      }
14174    });
14175  }
14176
14177  function open(idx){
14178    if (!state.items.length) buildItems();
14179    if (!state.items.length) return;
14180    ensureUI();
14181
14182    // Reset efek drag visual
14183    resetRootDrag(false);
14184
14185    document.body.style.overflow = 'hidden';
14186    root.classList.add('show');
14187    root.removeAttribute('aria-hidden'); // hindari aria-hidden saat fokus child
14188    setSiblingsInert(true);
14189
14190    state.open = true;
14191    setImage(typeof idx === 'number' ? idx : 0);
14192
14193    // Fokuskan dialog agar keyboard pasti tertangkap
14194    try { root.focus(); } catch(_){}
14195  }
14196
14197  function close(){
14198    state.open = false;
14199    root.classList.remove('show');
14200    root.setAttribute('aria-hidden','true');
14201    setSiblingsInert(false);
14202    document.body.style.overflow = '';
14203
14204    // Bersihkan transform/opacity
14205    resetRootDrag(false);
14206  }
14207
14208  function next(){ setImage(state.idx + 1); }
14209  function prev(){ setImage(state.idx - 1); }
14210
14211  function findIndexByUrl(url){
14212    const target = fileName(absUrl(url));
14213    const idx = state.items.findIndex(it => fileName(it.src) === target);
14214    return idx >= 0 ? idx : 0;
14215  }
14216
14217  function initPdLightbox(){
14218    buildItems();
14219    ensureUI();
14220
14221    // Klik gambar utama → buka LB di index yang sama
14222    const mainImg = document.getElementById('pd-main-img');
14223    if (mainImg) {
14224      mainImg.style.cursor = 'zoom-in';
14225      mainImg.addEventListener('click', ()=>{
14226        open(findIndexByUrl(mainImg.src));
14227      });
14228    }
14229
14230    // Klik gambar slider mobile → buka LB di gambar yang diketuk
14231    const slider = document.getElementById('pd-mobile-slider');
14232    if (slider) {
14233      slider.addEventListener('click', (e)=>{
14234        const im = e.target && e.target.tagName === 'IMG' ? e.target : e.target.closest('img');
14235        if (!im) return;
14236        open(findIndexByUrl(im.src));
14237      });
14238    }
14239
14240    // (Opsional) double-click pada thumbnail untuk buka langsung
14241    const thumbs = document.querySelectorAll('.pd-thumb-btn');
14242    thumbs.forEach((btn)=>{
14243      let last = 0;
14244      btn.addEventListener('click', ()=>{
14245        const now = Date.now();
14246        if (now - last < 300) {
14247          const src = btn.getAttribute('data-full') || '';
14248          open(findIndexByUrl(absUrl(src)));
14249          last = 0;
14250        } else {
14251          last = now;
14252        }
14253      });
14254    });
14255
14256    // Ekspos API
14257    window.PDLightbox = { open, close, next, prev, findIndexByUrl };
14258  }
14259
14260  if (document.readyState === 'loading') {
14261    document.addEventListener('DOMContentLoaded', initPdLightbox);
14262  } else {
14263    initPdLightbox();
14264  }
14265})();
14266</script>
vendor: 2 bytes, line 14266
14266
14267<script data-inlined="cart.js">
14268(function(){
14269  function $(s,ctx=document){ return ctx.querySelector(s); }
14270  function esc(s){ return String(s||'').replace(/[&<>"']/g, m=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;' }[m])); }
14271
14272  // CSRF token helper
14273  function getCsrfToken() {
14274    const metaTag = document.querySelector('meta[name="csrf-token"]');
14275    return metaTag ? metaTag.getAttribute('content') : '';
14276  }
14277
14278  function guessPdMeta(){
14279    const name = (window.PD_NAME !== undefined) ? String(window.PD_NAME) : ($('#pd-title')?.textContent || '');
14280    const sku  = (function(){ const el=$('#pd-sku'); return el ? el.textContent.trim() : (window.PD_SKU||''); })();
14281    // SECURITY FIX: Remove price from client-side metadata (will be validated server-side)
14282    const imgEl = $('#pd-main-img');
14283    const image_url = imgEl ? (imgEl.getAttribute('src') || '') : '';
14284    return { name, sku, image_url }; // price removed for security
14285  }
14286
14287  async function addToCart(productId, qty=1, variantId=0, metaOpt){
14288    const meta = Object.assign(guessPdMeta(), metaOpt||{});
14289    const payload = {
14290      product_id: parseInt(productId||0,10) || 0,
14291      qty: parseInt(qty||1,10) || 1,
14292      variant_id: parseInt(variantId||0,10) || 0,
14293      meta
14294    };
14295
14296    // Check if user is logged in
14297    const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true' ||
14298                      document.body.classList.contains('logged-in') ||
14299                      window.userLoggedIn === true ||
14300                      document.body.dataset.userLoggedIn === 'true';
14301
14302    // Skip localStorage untuk logged user - mereka pakai database
14303    if (!isLoggedIn && window.guestCart) {
14304      try {
14305        window.guestCart.add(payload.product_id, payload.qty, payload.variant_id, meta);
14306      } catch (e) {
14307        console.warn('Failed to add to localStorage cart:', e);
14308      }
14309    }
14310
14311    const res = await fetch('/api/cart/add.php', {
14312      method: 'POST',
14313      headers: {
14314        'Content-Type':'application/json',
14315        'X-CSRF-Token': getCsrfToken()
14316      },
14317      credentials: 'same-origin',
14318      body: JSON.stringify({
14319        ...payload,
14320        csrf_token: getCsrfToken()
14321      }),
14322    });
14323    const data = await res.json().catch(()=>({ ok:false, error:'Gagal menambahkan ke keranjang' }));
14324    if (!res.ok || !data.ok) {
14325      const msg = data && data.error ? data.error : 'Gagal menambahkan ke keranjang';
14326      throw new Error(msg);
14327    }
14328
14329    // Update badge di navbar dengan data dari response
14330    try {
14331      const el = document.querySelector('[data-role="cart-badge"]') || document.getElementById('cartCount');
14332      if (el && data.cart && typeof data.cart.items_count === 'number') {
14333        el.textContent = String(data.cart.items_count);
14334        el.classList.toggle('is-empty', data.cart.items_count <= 0);
14335      }
14336    } catch(_){}
14337
14338    // Siarkan event global (dipakai cart-badge.js dan halaman lain)
14339    try {
14340      document.dispatchEvent(new CustomEvent('cart:updated', { detail: data.cart || {} }));
14341    } catch(_){}
14342
14343    // Update CartBadge dengan data dari response (lebih reliable)
14344    try {
14345      if (window.CartBadge && window.CartBadge.set) {
14346        window.CartBadge.set(data.cart.items_count||0);
14347      }
14348    } catch(_){}
14349
14350    // Tambahan: refresh cart counter setelah delay kecil untuk memastikan data tersinkronisasi
14351    setTimeout(() => {
14352      if (window.updateCartCounter) {
14353        window.updateCartCounter();
14354      }
14355    }, 100);
14356
14357    return data;
14358  }
14359
14360  // Function to update cart counter from server
14361  async function updateCartCounter(){
14362    try {
14363      const res = await fetch('/api/cart/get.php', { credentials:'same-origin' });
14364      const data = await res.json().catch(()=>({ok:false}));
14365      if (data.ok && typeof data.items_count === 'number') {
14366        const el = document.querySelector('[data-role="cart-badge"]') || document.getElementById('cartCount');
14367        if (el) {
14368          el.textContent = String(data.items_count);
14369          el.classList.toggle('is-empty', data.items_count <= 0);
14370        }
14371        // Dispatch event
14372        try {
14373          document.dispatchEvent(new CustomEvent('cart:updated', { detail: { items_count: data.items_count } }));
14374        } catch(_){}
14375      }
14376    } catch (err) {
14377      console.log('Could not update cart counter:', err);
14378    }
14379  }
14380
14381  // Function to manually merge guest cart (if needed)
14382  async function mergeGuestCart(){
14383    try {
14384      const res = await fetch('/api/cart/merge-guest.php', {
14385        method: 'POST',
14386        headers: {
14387          'Content-Type': 'application/json',
14388          'X-CSRF-Token': getCsrfToken()
14389        },
14390        credentials: 'same-origin',
14391        body: JSON.stringify({ csrf_token: getCsrfToken() })
14392      });
14393      const data = await res.json().catch(()=>({ok:false}));
14394
14395      if (data.ok && data.merged_items > 0) {
14396        console.log(`Guest cart merged: ${data.merged_items} items`);
14397        // Update cart counter after merge
14398        await updateCartCounter();
14399        return data;
14400      }
14401      return data;
14402    } catch (err) {
14403      console.log('Could not merge guest cart:', err);
14404      return { ok: false, error: err.message };
14405    }
14406  }
14407
14408  window.addToCart = addToCart;
14409  window.updateCartCounter = updateCartCounter;
14410  window.mergeGuestCart = mergeGuestCart;
14411
14412  // Optional: auto-bind untuk tombol apapun yang punya data-addtocart
14413  document.addEventListener('click', async (e)=>{
14414    const btn = e.target.closest('[data-addtocart]');
14415    if (!btn) return;
14416    e.preventDefault();
14417    if (btn.disabled) return;
14418
14419    const pid = parseInt(btn.getAttribute('data-pid')||'0',10);
14420    const vid = parseInt(btn.getAttribute('data-vid')||'0',10);
14421    const qty = parseInt(btn.getAttribute('data-qty')||'1',10) || 1;
14422
14423    try {
14424      const data = await addToCart(pid, qty, vid);
14425      if (window.showToast) {
14426        const meta = guessPdMeta();
14427        window.showToast({
14428          type:'success',
14429          title:'Berhasil ditambahkan',
14430          message:`<strong>${esc(meta.name)}</strong>${meta.sku?` · <em>SKU: ${esc(meta.sku)}</em>`:''}<br>Qty: ${qty}`,
14431          duration: 3800,
14432          actions: [
14433            { label:'Lihat Keranjang', onClick: ()=> { window.location.href='/keranjang'; } },
14434            { label:'Lanjut Belanja', variant:'alt', onClick: ()=>{} }
14435          ]
14436        });
14437      }
14438    } catch (err) {
14439      if (window.showToast) {
14440        window.showToast({ type:'warn', title:'Gagal', message: String(err?.message||'Gagal menambahkan'), duration: 4200 });
14441      } else {
14442        console.error(err);
14443      }
14444    }
14445  });
14446})();
14447</script>
vendor: 2 bytes, line 14447
14447
14448<script data-inlined="stock-validator-vanilla.js">
14449/**
14450 * Stock Validation - Vanilla JS Version
14451 *
14452 * This script provides client-side stock validation without jQuery dependency
14453 * - Works with native JavaScript and fetch API
14454 * - Graceful fallback for older browsers
14455 * - Same functionality as jQuery version
14456 */
14457
14458window.StockValidator = (function() {
14459    'use strict';
14460
14461    const config = {
14462        apiEndpoint: '/api/stock/check.php',
14463        modalSelector: '#stock-modal-overlay',  // We create our own modal
14464        loadingClass: 'loading-stock',
14465        disabledClass: 'stock-disabled',
14466        debug: true  // Enable debug logging
14467    };
14468
14469    /**
14470     * Cross-browser event listener helper
14471     */
14472    function addEventListener(element, event, handler) {
14473        if (element.addEventListener) {
14474            element.addEventListener(event, handler);
14475        } else if (element.attachEvent) {
14476            element.attachEvent('on' + event, handler);
14477        }
14478    }
14479
14480    /**
14481     * Get stock information from embedded variant data (instant, no AJAX needed)
14482     */
14483    function getStockFromEmbeddedData(variantId) {
14484        // Check if embedded variant data exists
14485        if (!window.__PD_VARIANTS_WITH_BRANCH__ || !Array.isArray(window.__PD_VARIANTS_WITH_BRANCH__)) {
14486            return null;
14487        }
14488
14489        const variants = window.__PD_VARIANTS_WITH_BRANCH__;
14490
14491        // SPECIAL CASE: Simple product (no variants) - empty array means simple product
14492        if (variants.length === 0) {
14493            // For simple products, we cannot determine stock from embedded data
14494            // Return null to force API validation
14495            return null;
14496        }
14497
14498        // If no variant specified, check base product stock (all variants)
14499        if (!variantId || variantId <= 0) {
14500            // For base product, check if ANY variant has stock
14501            const isPreorder = window.isPreorderProduct || false;
14502            let hasAnyStock = false;
14503            let totalStock = 0;
14504
14505            if (isPreorder) {
14506                // For pre-order, check preorder_remaining
14507                hasAnyStock = variants.some(v => {
14508                    if ('preorder_remaining' in v && v.preorder_remaining !== null) {
14509                        return v.preorder_remaining > 0;
14510                    }
14511                    return v.stock > 0;
14512                });
14513                totalStock = variants.reduce((sum, v) => {
14514                    if ('preorder_remaining' in v && v.preorder_remaining !== null) {
14515                        return sum + (v.preorder_remaining || 0);
14516                    }
14517                    return sum + (v.stock || 0);
14518                }, 0);
14519            } else {
14520                hasAnyStock = variants.some(v => v.stock > 0);
14521                totalStock = variants.reduce((sum, v) => sum + (v.stock || 0), 0);
14522            }
14523
14524            return {
14525                hasStock: hasAnyStock,
14526                stock: totalStock,
14527                sku: 'Base Product',
14528                variant: null
14529            };
14530        }
14531
14532        // Find specific variant
14533        const variant = variants.find(v => v.id === variantId);
14534
14535        if (!variant) {
14536            return {
14537                hasStock: false,
14538                stock: 0,
14539                sku: 'Unknown Variant',
14540                variant: null
14541            };
14542        }
14543
14544        // For pre-order products, check preorder_remaining instead of stock
14545        const isPreorder = window.isPreorderProduct || false;
14546        let hasStock = false;
14547        let availableStock = 0;
14548
14549        if (isPreorder && 'preorder_remaining' in variant && variant.preorder_remaining !== null) {
14550            availableStock = parseInt(variant.preorder_remaining, 10);
14551            if (isNaN(availableStock)) availableStock = 0;
14552            hasStock = availableStock > 0;
14553        } else {
14554            availableStock = variant.stock || 0;
14555            hasStock = availableStock > 0;
14556        }
14557
14558        return {
14559            hasStock: hasStock,
14560            stock: availableStock,
14561            sku: variant.sku,
14562            variant: variant
14563        };
14564    }
14565
14566    /**
14567     * Cross-browser AJAX helper
14568     */
14569    function makeRequest(url, options) {
14570        return new Promise((resolve, reject) => {
14571            // Try fetch first (modern browsers)
14572            if (typeof fetch !== 'undefined') {
14573                const fetchOptions = {
14574                    method: options.method || 'POST',
14575                    headers: {
14576                        'Content-Type': 'application/x-www-form-urlencoded',
14577                    },
14578                    body: options.data ? new URLSearchParams(options.data).toString() : null
14579                };
14580
14581                fetch(url, fetchOptions)
14582                    .then(response => {
14583                        if (!response.ok) {
14584                            throw new Error(`HTTP ${response.status}`);
14585                        }
14586                        return response.json();
14587                    })
14588                    .then(resolve)
14589                    .catch(reject);
14590            } else {
14591                // Fallback to XMLHttpRequest
14592                const xhr = new XMLHttpRequest();
14593                xhr.open(options.method || 'POST', url);
14594                xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
14595
14596                xhr.onload = function() {
14597                    if (xhr.status >= 200 && xhr.status < 300) {
14598                        try {
14599                            resolve(JSON.parse(xhr.responseText));
14600                        } catch (e) {
14601                            reject(new Error('Invalid JSON response'));
14602                        }
14603                    } else {
14604                        reject(new Error(`HTTP ${xhr.status}`));
14605                    }
14606                };
14607
14608                xhr.onerror = function() {
14609                    reject(new Error('Network error'));
14610                };
14611
14612                const data = options.data ? new URLSearchParams(options.data).toString() : null;
14613                xhr.send(data);
14614            }
14615        });
14616    }
14617
14618    /**
14619     * Check stock via AJAX
14620     */
14621    function checkStock(productId, variantId, qty) {
14622        return makeRequest(config.apiEndpoint, {
14623            method: 'POST',
14624            data: {
14625                product_id: productId,
14626                variant_id: variantId || 0,
14627                qty: qty || 1
14628            }
14629        });
14630    }
14631
14632    /**
14633     * Create and show stock alert modal using modal.css structure
14634     */
14635    function showStockModal(stockInfo, action = 'add to cart') {
14636        let title, message, type, iconClass;
14637
14638        // Handle both string and object formats
14639        if (typeof stockInfo === 'string') {
14640            title = 'Stok Tidak Tersedia';
14641            message = stockInfo;
14642            type = 'warning';
14643            iconClass = 'warning';
14644        } else if (stockInfo && typeof stockInfo === 'object') {
14645            if (!stockInfo.is_active) {
14646                title = 'Produk Tidak Tersedia';
14647                message = 'Maaf, produk ini sudah tidak aktif atau telah dihapus dari katalog.';
14648                type = 'danger';
14649                iconClass = 'danger';
14650            } else if (!stockInfo.is_in_stock) {
14651                title = 'Stok Habis';
14652                message = `<strong>${stockInfo.product_name || 'Produk ini'}</strong> sedang habis stok. Silakan pilih varian lain atau tunggu restock.`;
14653                if (stockInfo.sku) {
14654                    message += `<br><small style="color: #64748b;">SKU: ${stockInfo.sku}</small>`;
14655                }
14656                type = 'warning';
14657                iconClass = 'warning';
14658            } else if (!stockInfo.can_add_to_cart) {
14659                title = 'Stok Tidak Mencukupi';
14660                message = `Stok <strong>${stockInfo.product_name || 'produk'}</strong> tidak mencukupi.<br>`;
14661                message += `Tersedia: <strong>${stockInfo.available_stock}</strong>, diminta: <strong>${stockInfo.requested_qty}</strong><br>`;
14662                message += `Silakan kurangi jumlah atau pilih varian lain.`;
14663                type = 'warning';
14664                iconClass = 'warning';
14665            } else {
14666                title = 'Stok Tidak Tersedia';
14667                message = stockInfo.message || 'Produk ini tidak dapat ditambahkan ke keranjang saat ini.';
14668                type = 'warning';
14669                iconClass = 'warning';
14670            }
14671        } else {
14672            title = 'Stok Tidak Tersedia';
14673            message = 'Produk ini tidak dapat ditambahkan ke keranjang saat ini.';
14674            type = 'warning';
14675            iconClass = 'warning';
14676        }
14677
14678        // Create modal HTML using modal.css structure
14679        const modalHTML = `
14680            <div class="modal-overlay" id="stock-modal-overlay">
14681                <div class="modal-dialog">
14682                    <div class="modal-header">
14683                        <div class="modal-icon ${iconClass}">
14684                            ${getIconSVG(iconClass)}
14685                        </div>
14686                        <h3 class="modal-title">${title}</h3>
14687                    </div>
14688                    <div class="modal-body">
14689                        <p class="modal-message">${message}</p>
14690                        <div class="modal-actions">
14691                            <button type="button" class="modal-btn modal-btn-secondary" data-action="close">
14692                                Tutup
14693                            </button>
14694                            <button type="button" class="modal-btn modal-btn-primary" data-action="reload">
14695                                Muat Ulang
14696                            </button>
14697                        </div>
14698                    </div>
14699                </div>
14700            </div>
14701        `;
14702
14703        // Remove existing modal if any
14704        const existingModal = document.getElementById('stock-modal-overlay');
14705        if (existingModal) {
14706            existingModal.remove();
14707        }
14708
14709        // Add modal to body
14710        document.body.insertAdjacentHTML('beforeend', modalHTML);
14711
14712        // Show modal with animation
14713        const modal = document.getElementById('stock-modal-overlay');
14714        setTimeout(() => {
14715            modal.classList.add('show');
14716        }, 10);
14717
14718        // Add event listeners to modal buttons
14719        const closeBtn = modal.querySelector('[data-action="close"]');
14720        const reloadBtn = modal.querySelector('[data-action="reload"]');
14721
14722        if (closeBtn) {
14723            addEventListener(closeBtn, 'click', closeStockModal);
14724        }
14725
14726        if (reloadBtn) {
14727            addEventListener(reloadBtn, 'click', function() {
14728                closeStockModal();
14729                window.location.reload();
14730            });
14731        }
14732
14733        // Close modal when clicking overlay
14734        addEventListener(modal, 'click', function(event) {
14735            if (event.target === modal) {
14736                closeStockModal();
14737            }
14738        });
14739
14740        // Auto-close after 8 seconds
14741        setTimeout(() => {
14742            closeStockModal();
14743        }, 8000);
14744    }
14745
14746    /**
14747     * Get icon SVG based on type
14748     */
14749    function getIconSVG(type) {
14750        const icons = {
14751            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>',
14752            warning: '<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
14752<path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>',
14753            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>',
14754            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>'
14755        };
14756        return icons[type] || icons.info;
14757    }
14758
14759    /**
14760     * Close stock modal
14761     */
14762    function closeStockModal() {
14763        const modal = document.getElementById('stock-modal-overlay');
14764        if (modal) {
14765            modal.classList.remove('show');
14766            setTimeout(() => {
14767                modal.remove();
14768            }, 200);
14769        }
14770    }
14771
14772    // Make closeStockModal globally available
14773    if (typeof window !== 'undefined') {
14774        window.closeStockModal = closeStockModal;
14775    }
14776
14777    /**
14778     * Get element data attribute with fallback
14779     */
14780    function getDataAttribute(element, attr) {
14781        if (element.dataset) {
14782            return element.dataset[attr];
14783        } else {
14784            return element.getAttribute('data-' + attr);
14785        }
14786    }
14787
14788    /**
14789     * Validate stock before cart action
14790     */
14791    async function validateBeforeCart(productId, variantId, qty, action = 'add') {
14792        try {
14793            // SMART VALIDATION: For simple products, check if we already have cached stock info
14794            // If buttons are enabled and cached info shows stock available, skip API validation
14795            if (window._currentStockInfo && window._currentStockInfo.can_add_to_cart) {
14796
14797                return true;
14798            }
14799
14800            // For variant products or when cached info is not available, do API validation
14801            const stockInfo = await checkStock(productId, variantId, qty);
14802
14803            if (!stockInfo.ok) {
14804                showStockModal({
14805                    message: stockInfo.error || 'Gagal mengecek stok',
14806                    is_active: false
14807                }, action);
14808                return false;
14809            }
14810
14811            if (!stockInfo.can_add_to_cart) {
14812                showStockModal(stockInfo, action);
14813                return false;
14814            }
14815
14816            return true; // Stock available, proceed
14817
14818        } catch (error) {
14819            console.error('Stock validation error:', error);
14820            showStockModal({
14821                message: 'Terjadi kesalahan saat mengecek stok. Silakan coba lagi.',
14822                is_active: false
14823            }, action);
14824            return false;
14825        }
14826    }
14827
14828    /**
14829     * Handle button click with stock validation
14830     */
14831    function handleButtonClick(event, action) {
14832        const button = event.target;
14833
14834
14835        // FIRST CHECK: Is button marked as stock-blocked? (Anti-tampering)
14836        if (button.hasAttribute('data-stock-blocked')) {
14837            event.preventDefault();
14838            event.stopImmediatePropagation();
14839
14840            const reason = button.getAttribute('data-stock-reason') || 'Stok tidak tersedia';
14841            showStockModal(reason);
14842
14843            // Show security warning
14844            if (window.showToast) {
14845                window.showToast({
14846                    type: 'warning',
14847                    title: 'Aksi Tidak Diizinkan',
14848                    message: 'Produk ini sedang habis stok. Silakan pilih varian lain.',
14849                    duration: 3000
14850                });
14851            }
14852            return false;
14853        }
14854
14855        // SECOND CHECK: Real-time stock validation against cached stock info
14856        if (window._currentStockInfo && !window._currentStockInfo.can_add_to_cart) {
14857            event.preventDefault();
14858            event.stopImmediatePropagation();
14859
14860            showStockModal(window._currentStockInfo.message || 'Stok tidak tersedia');
14861
14862            // Re-enforce button disabling
14863            button.disabled = true;
14864            button.style.opacity = '0.5';
14865            button.style.cursor = 'not-allowed';
14866            button.setAttribute('data-stock-blocked', 'true');
14867
14868            return false;
14869        }
14870
14871        // Skip if already processing
14872        if (button.classList.contains(config.loadingClass)) {
14873            event.preventDefault();
14874            return false;
14875        }
14876
14877        // Get product data from button attributes or form
14878        let productId = parseInt(getDataAttribute(button, 'productId') || getDataAttribute(button, 'product-id') || 0);
14879        let variantId = parseInt(getDataAttribute(button, 'variantId') || getDataAttribute(button, 'variant-id') || 0);
14880        let qty = parseInt(getDataAttribute(button, 'qty') || 1);
14881
14882        // Try to get from global variables (set by product detail page)
14883        if (productId <= 0 && typeof window.PD_ID !== 'undefined') {
14884            productId = parseInt(window.PD_ID || 0);
14885        }
14886
14887        // Try to get from parent form if not in button
14888        if (productId <= 0) {
14889            const form = button.closest ? button.closest('form') : null;
14890            if (form) {
14891                const pidAttr = form.getAttribute('data-pid');
14892                const pidInput = form.querySelector('[name="product_id"]');
14893                const vidInput = form.querySelector('[name="variant_id"]');
14894                const qtyInput = form.querySelector('[name="qty"]');
14895
14896                if (pidAttr) productId = parseInt(pidAttr);
14897                if (pidInput) productId = parseInt(pidInput.value || 0);
14898                if (vidInput) variantId = parseInt(vidInput.value || 0);
14899                if (qtyInput) qty = parseInt(qtyInput.value || 1);
14900            }
14901        }
14902
14903        // CRITICAL: Always get current variant from selector (overrides button attributes)
14904        // This ensures we validate against the currently selected variant, not cached button data
14905        const variantSelector = document.getElementById('pd-variant-id');
14906        if (variantSelector) {
14907            const currentVariantId = parseInt(variantSelector.value || 0);
14908            if (currentVariantId > 0) {
14909                variantId = currentVariantId;
14910            }
14911        }
14912
14913
14914        if (productId <= 0) {
14915            return true; // Let default handler deal with it
14916        }
14917
14918        // Prevent default and validate stock
14919        event.preventDefault();
14920        event.stopPropagation();
14921
14922        // Show loading state
14923        button.classList.add(config.loadingClass);
14924        const originalDisabled = button.disabled;
14925        button.disabled = true;
14926
14927
14928        validateBeforeCart(productId, variantId, qty, action)
14929            .then(isValid => {
14930                if (isValid) {
14931                    // Stock OK, proceed with original action
14932                    if (action === 'cart' && typeof window.addToCart === 'function') {
14933                        window.addToCart(productId, qty, variantId);
14934                    } else if (action === 'buy' && typeof window.buyNow === 'function') {
14935                        // Call buyNow from pd-buynow.js without parameters (it gets data internally)
14936                        window.buyNow();
14937                    } else {
14938                        // Add fallback cart/buy logic here if needed
14939                    }
14940                }
14941            })
14942            .catch(error => {
14943            })
14944            .finally(() => {
14945                // Remove loading state
14946                button.classList.remove(config.loadingClass);
14947                button.disabled = originalDisabled;
14948            });
14949
14950        return false;
14951    }
14952
14953    /**
14954     * Handle mobile button click with stock validation
14955     */
14956    function handleMobileButtonClick(event, action) {
14957        const button = event.target;
14958
14959
14960        // FIRST CHECK: Is button marked as stock-blocked? (Anti-tampering)
14961        if (button.hasAttribute('data-stock-blocked')) {
14962            event.preventDefault();
14963            event.stopImmediatePropagation();
14964
14965            const reason = button.getAttribute('data-stock-reason') || 'Stok tidak tersedia';
14966            showStockModal(reason);
14967
14968            // Show security warning
14969            if (window.showToast) {
14970                window.showToast({
14971                    type: 'warning',
14972                    title: 'Aksi Tidak Diizinkan',
14973                    message: 'Produk ini sedang habis stok. Silakan pilih varian lain.',
14974                    duration: 3000
14975                });
14976            }
14977            return false;
14978        }
14979
14980        // SECOND CHECK: Real-time stock validation against cached stock info
14981        if (window._currentStockInfo && !window._currentStockInfo.can_add_to_cart) {
14982            event.preventDefault();
14983            event.stopImmediatePropagation();
14984
14985            showStockModal(window._currentStockInfo.message || 'Stok tidak tersedia');
14986
14987            // Re-enforce button disabling
14988            button.disabled = true;
14989            button.style.opacity = '0.5';
14990            button.style.cursor = 'not-allowed';
14991            button.setAttribute('data-stock-blocked', 'true');
14992
14993            return false;
14994        }
14995
14996        // Skip if already processing
14997        if (button.classList.contains(config.loadingClass)) {
14998            event.preventDefault();
14999            return false;
15000        }
15001
15002        // Get product data from mobile FAB container
15003        let productId = 0;
15004        let variantId = 0;
15005        let qty = 1;
15006
15007        // Try to get from mobile FAB data-pid
15008        const mobileFab = document.getElementById('pd-mobile-fab');
15009        if (mobileFab) {
15010            const pidAttr = getDataAttribute(mobileFab, 'pid');
15011            if (pidAttr) productId = parseInt(pidAttr);
15012        }
15013
15014        // Try to get from global variables (set by product detail page)
15015        if (productId <= 0 && typeof window.PD_ID !== 'undefined') {
15016            productId = parseInt(window.PD_ID || 0);
15017        }
15018
15019        // CRITICAL: Always get current variant from selector (real-time)
15020        // This ensures we validate against the currently selected variant
15021        const variantSelector = document.getElementById('pd-variant-id');
15022        if (variantSelector) {
15023            variantId = parseInt(variantSelector.value || 0);
15024        }
15025
15026        // Try to get quantity from quantity input
15027        const qtySelector = document.querySelector('[name="qty"]');
15028        if (qtySelector) {
15029            qty = parseInt(qtySelector.value || 1);
15030        }
15031
15032        // Ensure minimum quantity is 1
15033        qty = Math.max(1, qty);
15034
15035
15036        if (productId <= 0) {
15037            return true; // Let default handler deal with it
15038        }
15039
15040        // Event already prevented at interceptor level, just proceed with validation
15041
15042        // Show loading state
15043        button.classList.add(config.loadingClass);
15044        const originalDisabled = button.disabled;
15045        const originalText = button.textContent;
15046        button.disabled = true;
15047        button.textContent = action === 'cart' ? 'Validasi...' : 'Validasi...';
15048
15049
15050        // PREEMPTIVE BLOCKING: Set blocking flags BEFORE validation to prevent race conditions
15051        if (window.addToCart && typeof window.addToCart === 'function') {
15052            window.addToCart._stockValidatorBlocked = true;
15053        }
15054
15055        // Also preemptively block other cart functions
15056        const originalAddToCartSubmit = window.addToCartSubmit;
15057        const originalPdAddToCartSubmit = window._pdAddToCartSubmit;
15058
15059        // Temporarily block ALL cart functions during validation
15060        window.addToCartSubmit = (...args) => {
15061            return Promise.resolve(false);
15062        };
15063
15064        if (originalPdAddToCartSubmit) {
15065            window._pdAddToCartSubmit = (...args) => {
15066                return Promise.resolve(false);
15067            };
15068        }
15069
15070        validateBeforeCart(productId, variantId, qty, action)
15071            .then(isValid => {
15072                if (isValid) {
15073                    // Stock OK, proceed with original action
15074
15075                    if (action === 'cart') {
15076                        // For mobile cart, we need to recreate the original behavior
15077                        // Strategy: Try multiple methods to ensure cart add + toast success
15078
15079
15080                        let cartAddSuccess = false;
15081                        let errorMessage = '';
15082
15083                        // Method 1: Try original addToCart function
15084                        const originalAddToCart = window.addToCart && window.addToCart._original;
15085                        if (originalAddToCart && !cartAddSuccess) {
15086                            try {
15087                                const result = originalAddToCart(productId, qty, variantId);
15088
15089                                // Handle both sync and async returns
15090                                if (result && typeof result.then === 'function') {
15091                                    result.then(() => {
15092                                        cartAddSuccess = true;
15093                                    }).catch(err => {
15094                                        errorMessage = err.message || 'addToCart failed';
15095                                    });
15096                                } else {
15097                                    cartAddSuccess = true;
15098                                }
15099                            } catch (error) {
15100                                errorMessage = error.message || 'addToCart exception';
15101                            }
15102                        }
15103
15104                        // Method 2: Try pd-addtocart.js function (backup method)
15105                        if (typeof window._pdAddToCartSubmit === 'function' && !cartAddSuccess) {
15106                            try {
15107
15108                                // Create fake form for pd-addtocart.js
15109                                const fakeForm = document.createElement('form');
15110                                const qtyInput = document.createElement('input');
15111                                qtyInput.name = 'qty';
15112                                qtyInput.value = qty;
15113                                fakeForm.appendChild(qtyInput);
15114
15115                                // Set variant selector value for pd-addtocart to read
15116                                const variantSelector = document.getElementById('pd-variant-id');
15117                                if (variantSelector) {
15118                                    variantSelector.value = variantId;
15119                                }
15120
15121                                const fakeEvent = {
15122                                    preventDefault: () => {},
15123                                    target: fakeForm
15124                                };
15125
15126                                // Call pd-addtocart.js function (this should handle toast internally)
15127                                const result = window._pdAddToCartSubmit(fakeEvent, productId);
15128                                if (result && typeof result.then === 'function') {
15129                                    result.then(() => {
15130                                        cartAddSuccess = true;
15131                                    });
15132                                } else {
15133                                    cartAddSuccess = true;
15134                                }
15135                            } catch (error) {
15136                            }
15137                        }
15138
15139                        // Method 3: Direct addToCart call (fallback)
15140                        if (typeof window.addToCart === 'function' && !cartAddSuccess) {
15141                            try {
15142                                const result = window.addToCart(productId, qty, variantId);
15143                                if (result && typeof result.then === 'function') {
15144                                    result.then(() => {
15145                                        cartAddSuccess = true;
15146                                    });
15147                                } else {
15148                                    cartAddSuccess = true;
15149                                }
15150                            } catch (error) {
15151                            }
15152                        }
15153
15154                        // Always show success toast for mobile (regardless of which method worked)
15155                        // This ensures user gets feedback for successful cart addition
15156                        setTimeout(() => {
15157                            if (window.showToast) {
15158
15159                                // Get product details for toast
15160                                const getVariantSummaryText = () => {
15161                                    const vs = document.getElementById('pd-variant-summary');
15162                                    if (!vs) return '';
15163                                    const items = Array.from(vs.querySelectorAll('.vs-item strong'))
15164                                        .map(el => el.textContent.trim())
15165                                        .filter(Boolean);
15166                                    return items.length ? items.join(' / ') : '';
15167                                };
15168
15169                                const escapeHtml = (s) => String(s||'').replace(/[&<>"']/g, m=>({
15170                                    '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'
15171                                }[m]));
15172
15173                                const variantText = getVariantSummaryText();
15174                                const skuText = (function(){
15175                                    const el = document.getElementById('pd-sku');
15176                                    return el ? el.textContent.trim() : (window.PD_SKU || 'N/A');
15177                                })();
15178                                const productName = window.PD_NAME || 'Product';
15179
15180                                window.showToast({
15181                                    type: 'success',
15182                                    title: 'Berhasil ditambahkan ke Keranjang',
15183                                    message: `<strong>${escapeHtml(productName)}</strong>${variantText ? ` · <em>${escapeHtml(variantText)}</em>` : ''}<br>SKU: ${escapeHtml(skuText)}
15183 · Qty: ${qty}`,
15184                                    duration: 5000,
15185                                    actions: [
15186                                        {
15187                                            label: 'Lihat Keranjang',
15188                                            onClick: () => { window.location.href = '/keranjang'; }
15189                                        },
15190                                        {
15191                                            label: 'Lanjut Belanja',
15192                                            variant: 'alt',
15193                                            onClick: () => {}
15194                                        }
15195                                    ]
15196                                });
15197                            } else {
15198                            }
15199                        }, 100); // Small delay to ensure cart operation completes first
15200
15201                    } else if (action === 'buy') {
15202                        // For mobile buy, call buyNow
15203                        if (typeof window.buyNow === 'function') {
15204                            window.buyNow();
15205                        }
15206                    }
15207                } else {
15208                    // Stock validation FAILED - absolutely NO cart function calls allowed
15209
15210                    // DO NOT CALL ANY CART FUNCTIONS - only show modal (already handled by validateBeforeCart)
15211                }
15212
15213                // Always restore functions after validation (success or failure)
15214                setTimeout(() => {
15215                    // Remove blocking flag
15216                    if (window.addToCart && typeof window.addToCart === 'function') {
15217                        window.addToCart._stockValidatorBlocked = false;
15218                    }
15219
15220                    // Restore other functions
15221                    if (originalAddToCartSubmit && typeof originalAddToCartSubmit === 'function') {
15222                        window.addToCartSubmit = originalAddToCartSubmit;
15223                    }
15224                    if (originalPdAddToCartSubmit && typeof originalPdAddToCartSubmit === 'function') {
15225                        window._pdAddToCartSubmit = originalPdAddToCartSubmit;
15226                    }
15227
15228                }, 1000); // Shorter timeout since we want to restore quickly after success
15229            })
15230            .catch(error => {
15231                // On error, also block the action
15232            })
15233            .finally(() => {
15234                // Remove loading state
15235                button.classList.remove(config.loadingClass);
15236                button.disabled = originalDisabled;
15237                button.textContent = originalText;
15238            });
15239
15240        return false;
15241    }
15242
15243    /**
15244     * Intercept cart buttons
15245     */
15246    function interceptCartButtons() {
15247
15248
15249        // CRITICAL: Remove existing mobile cart handlers from product-detail.php
15250        setTimeout(() => {
15251            const mobileCartBtn = document.getElementById('mf-cart');
15252            if (mobileCartBtn) {
15253                // Clone the button to remove ALL existing event listeners
15254                const newBtn = mobileCartBtn.cloneNode(true);
15255                mobileCartBtn.parentNode.replaceChild(newBtn, mobileCartBtn);
15256
15257
15258                // Add our own handler to the new button
15259                newBtn.addEventListener('click', function(event) {
15260                    event.preventDefault();
15261                    event.stopPropagation();
15262                    event.stopImmediatePropagation();
15263                    handleMobileButtonClick(event, 'cart');
15264                    return false;
15265                }, true);
15266            }
15267        }, 50);
15268
15269        // Note: Add to cart buttons are now handled via form submission override (addToCartSubmit)
15270        // This provides better integration with existing pd-addtocart.js toast system
15271
15272        // Buy now buttons - updated selectors (desktop + mobile)
15273        addEventListener(document, 'click', function(event) {
15274            const target = event.target;
15275
15276            // Desktop buy buttons
15277            if (target.classList.contains('btn-buy') ||           // Product detail buy button
15278                target.classList.contains('btn-buy-now') ||
15279                target.classList.contains('buy-now-btn') ||
15280                target.id === 'btn-buy' ||                        // Specific ID match
15281                getDataAttribute(target, 'action') === 'buy-now' ||
15282                getDataAttribute(target, 'action') === 'buy') {
15283
15284                handleButtonClick(event, 'buy');
15285            }
15286
15287            // Mobile buy buttons
15288            else if (target.classList.contains('mf-buy') ||       // Mobile fab buy button
15289                     target.id === 'mf-buy') {                   // Mobile buy button ID
15290
15291                handleMobileButtonClick(event, 'buy');
15292            }
15293
15294            // Mobile cart buttons - ENHANCED BLOCKING
15295            else if (target.classList.contains('mf-cart') ||      // Mobile fab cart button
15296                     target.id === 'mf-cart') {                  // Mobile cart button ID
15297
15298
15299                // Immediately prevent all event propagation
15300                event.preventDefault();
15301                event.stopPropagation();
15302                event.stopImmediatePropagation();
15303
15304                handleMobileButtonClick(event, 'cart');
15305
15306                // Return false to ensure no other handlers run
15307                return false;
15308            }
15309        }, true);
15309 // Use capturing phase to ensure we intercept before other handlers
15310
15311        // Additional protection: Override addToCart function to add mobile button state checking
15312        const originalAddToCart = typeof window.addToCart === 'function' ? window.addToCart : null;
15313        if (originalAddToCart) {
15314            window.addToCart = async function(...args) {
15315                // Check if this call is coming from a mobile button that's currently being validated
15316                const mobileCartBtn = document.getElementById('mf-cart');
15317                if (mobileCartBtn && mobileCartBtn.classList.contains('loading-stock')) {
15318                    return Promise.reject(new Error('Mobile validation in progress - cart action blocked'));
15319                }
15320
15321                // Check if functions are temporarily blocked (during stock failure)
15322                if (window.addToCart._stockValidatorBlocked) {
15323                    return Promise.reject(new Error('Stock validation failed - cart action blocked'));
15324                }
15325
15326                // Allow normal calls
15327                return originalAddToCart.apply(this, args);
15328            };
15329
15330            // Store reference to original for restoration
15331            window.addToCart._original = originalAddToCart;
15332        }
15333    }
15334
15335    /**
15336     * Setup global click interceptor for security
15337     * This catches ALL clicks on cart/buy buttons and validates stock BEFORE any other handler
15338     */
15339    function setupGlobalClickInterceptor() {
15340        // Add capture-phase listener to catch clicks before other handlers
15341        document.addEventListener('click', function(event) {
15342            const target = event.target;
15343
15344            // Check if this is a cart/buy button
15345            const isCartButton = target.matches('.btn-add-cart, .mf-cart, #mf-cart, #btn-add-cart') ||
15346                                target.closest('.btn-add-cart, .mf-cart, #mf-cart, #btn-add-cart');
15347            const isBuyButton = target.matches('.btn-buy-now, .mf-buy, #mf-buy, #btn-buy') ||
15348                               target.closest('.btn-buy-now, .mf-buy, #mf-buy, #btn-buy');
15349
15350            if (!isCartButton && !isBuyButton) {
15351                return; // Not our target, allow normal processing
15352            }
15353
15354            const button = isCartButton ?
15355                (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')) :
15356                (target.matches('.btn-buy-now, .mf-buy, #mf-buy, #btn-buy') ? target : target.closest('.btn-buy-now, .mf-buy, #mf-buy, #btn-buy'));
15357
15358            // IMMEDIATE BLOCK: Prevent any processing first
15359            event.preventDefault();
15360            event.stopImmediatePropagation();
15361
15362            // SECURITY CHECK 1: Is button marked as stock-blocked?
15363            if (button && button.hasAttribute('data-stock-blocked')) {
15364
15365                const reason = button.getAttribute('data-stock-reason') || 'Stok tidak tersedia';
15366                showStockModal(reason);
15367
15368                // Show security alert
15369                if (window.showToast) {
15370                    window.showToast({
15371                        type: 'error',
15372                        title: 'Aksi Diblokir',
15373                        message: 'Produk ini sedang habis stok dan tidak dapat ditambahkan ke keranjang.',
15374                        duration: 4000
15375                    });
15376                }
15377
15378                return false;
15379            }
15380
15381            // SECURITY CHECK 2: Real-time validation against cached stock info
15382            if (window._currentStockInfo && !window._currentStockInfo.can_add_to_cart) {
15383
15384                showStockModal(window._currentStockInfo.message || 'Stok tidak tersedia');
15385
15386                // Re-enforce button disabling
15387                if (button) {
15388                    button.disabled = true;
15389                    button.style.opacity = '0.5';
15390                    button.style.cursor = 'not-allowed';
15391                    button.setAttribute('data-stock-blocked', 'true');
15392                    button.setAttribute('data-stock-reason', window._currentStockInfo.message || 'Stok tidak tersedia');
15393                }
15394
15395                return false;
15396            }
15397
15398            // SECURITY CHECK 3: Smart client-side validation
15399            const productId = window.PD_ID ? parseInt(window.PD_ID) : 0;
15400            const variantSelector = document.getElementById('pd-variant-id');
15401            const variantId = variantSelector ? parseInt(variantSelector.value || 0
15401) : 0;
15402
15403            if (productId > 0) {
15404                // SMART VALIDATION: If we already have cached stock info showing stock is available,
15405                // and buttons are enabled, proceed immediately without re-validation
15406                if (window._currentStockInfo && window._currentStockInfo.can_add_to_cart &&
15407                    button && !button.disabled && !button.hasAttribute('data-stock-blocked')) {
15408
15409                    // Allow the action to proceed immediately with toast
15410                    if (isCartButton) {
15411                        // Trigger cart action with proper toast handling
15412                        if (typeof window._pdAddToCartSubmit === 'function') {
15413                            // Use pd-addtocart.js function for proper toast handling
15414                            const form = document.getElementById('pd-buy-form') || button.closest('form');
15415                            if (form) {
15416                                const fakeEvent = {
15417                                    target: form,
15418                                    preventDefault: () => {}
15419                                };
15420                                window._pdAddToCartSubmit(fakeEvent, productId);
15421                            }
15422                        } else if (typeof window.addToCartSubmit === 'function') {
15423                            // Fallback to addToCartSubmit
15424                            const form = document.getElementById('pd-buy-form') || button.closest('form');
15425                            if (form) {
15426                                const fakeEvent = {
15427                                    target: form,
15428                                    preventDefault: () => {}
15429                                };
15430                                window.addToCartSubmit(fakeEvent, productId);
15431                            }
15432                        } else if (typeof window.addToCart === 'function') {
15433                            // Direct addToCart call
15434                            window.addToCart(productId, 1, variantId);
15435                        }
15436                    } else if (isBuyButton) {
15437                        // Trigger buy action
15438                        if (typeof window.buyNow === 'function') {
15439                            window.buyNow();
15440                        }
15441                    }
15442
15443                    return false; // Action completed
15444                }
15445
15446                // Get stock info from embedded variant data (for variant products)
15447                const stockInfo = getStockFromEmbeddedData(variantId);
15448
15449                if (stockInfo && stockInfo.hasStock) {
15450
15451                    // Update global stock cache
15452                    window._currentStockInfo = {
15453                        can_add_to_cart: true,
15454                        is_in_stock: true,
15455                        available_stock: stockInfo.stock,
15456                        message: 'Stok tersedia'
15457                    };
15458
15459                    // IMPORTANT: Check if button was explicitly blocked by variant logic (e.g., pre-order exhausted)
15460                    // If data-stock-blocked is set, respect it and don't enable the button
15461                    const isExplicitlyBlocked = button.getAttribute('data-stock-blocked') === 'true';
15462
15463                    if (!isExplicitlyBlocked) {
15464                        // Remove stock blocking if it was set and enable button
15465                        button.removeAttribute('data-stock-blocked');
15466                        button.removeAttribute('data-stock-reason');
15467                        button.disabled = false;
15468                        button.style.opacity = '';
15469                        button.style.cursor = '';
15470                        button.classList.remove(config.disabledClass);
15471                    } else {
15472                        // Button is blocked by variant logic, show reason
15473                        const reason = button.getAttribute('data-stock-reason') || 'Tidak tersedia';
15474                        console.log('Button blocked by variant logic:', reason);
15475                        e.preventDefault();
15476                        e.stopPropagation();
15477                        return false;
15478                    }
15479
15480                    // Allow the action to proceed immediately with toast
15481                    if (isCartButton) {
15482                        // Trigger cart action with proper toast handling
15483                        if (typeof window._pdAddToCartSubmit === 'function') {
15484                            // Use pd-addtocart.js function for proper toast handling
15485                            const form = document.getElementById('pd-buy-form') || button.closest('form');
15486                            if (form) {
15487                                const fakeEvent = {
15488                                    target: form,
15489                                    preventDefault: () => {}
15490                                };
15491                                window._pdAddToCartSubmit(fakeEvent, productId);
15492                            }
15493                        } else if (typeof window.addToCartSubmit === 'function') {
15494                            // Fallback to addToCartSubmit
15495                            const form = document.getElementById('pd-buy-form') || button.closest('form');
15496                            if (form) {
15497                                const fakeEvent = {
15498                                    target: form,
15499                                    preventDefault: () => {}
15500                                };
15501                                window.addToCartSubmit(fakeEvent, productId);
15502                            }
15503                        } else if (typeof window.addToCart === 'function') {
15504                            // Direct addToCart call
15505                            window.addToCart(productId, 1, variantId);
15506                        }
15507                    } else if (isBuyButton) {
15508                        // Trigger buy action
15509                        if (typeof window.buyNow === 'function') {
15510                            window.buyNow();
15511                        }
15512                    }
15513                } else if (stockInfo && !stockInfo.hasStock) {
15514
15515                    // Update global stock cache
15516                    window._currentStockInfo = {
15517                        can_add_to_cart: false,
15518                        is_in_stock: false,
15519                        available_stock: stockInfo.stock,
15520                        message: `Stok tidak tersedia${stockInfo.sku ? ` untuk ${stockInfo.sku}` : ''}`
15521                    };
15522
15523                    // Force block button
15524                    button.disabled = true;
15525                    button.style.opacity = '0.5';
15526                    button.style.cursor = 'not-allowed';
15527                    button.setAttribute('data-stock-blocked', 'true');
15528                    button.setAttribute('data-stock-reason', window._currentStockInfo.message);
15529
15530                    // Show modal with variant info
15531                    const modalMessage = stockInfo ?
15532                        `Varian yang dipilih (${stockInfo.sku}) sedang habis stok. Silakan pilih varian lain.` :
15533                        'Produk ini sedang habis stok. Silakan pilih varian lain.';
15534
15535                    showStockModal(modalMessage);
15536
15537                    // Show security warning
15538                    if (window.showToast) {
15539                        window.showToast({
15540                            type: 'warning',
15541                            title: 'Stok Tidak Tersedia',
15542                            message: 'Varian yang dipilih sedang habis stok. Silakan pilih varian lain.',
15543                            duration: 3000
15544                        });
15545                    }
15546                } else {
15547                    // stockInfo is null (simple product) - this means embedded data is not available
15548                    // For simple products, if buttons are enabled, we should proceed (trust the API response from updateStockDisplay)
15549                    if (button && !button.disabled && !button.hasAttribute('data-stock-blocked')) {
15550
15551                        // Allow the action to proceed immediately with toast
15552                        if (isCartButton) {
15553                            // Trigger cart action with proper toast handling
15554                            if (typeof window._pdAddToCartSubmit === 'function') {
15555                                // Use pd-addtocart.js function for proper toast handling
15556                                const form = document.getElementById('pd-buy-form') || button.closest('form');
15557                                if (form) {
15558                                    const fakeEvent = {
15559                                        target: form,
15560                                        preventDefault: () => {}
15561                                    };
15562                                    window._pdAddToCartSubmit(fakeEvent, productId);
15563                                }
15564                            } else if (typeof window.addToCartSubmit === 'function') {
15565                                // Fallback to addToCartSubmit
15566                                const form = document.getElementById('pd-buy-form') || button.closest('form');
15567                                if (form) {
15568                                    const fakeEvent = {
15569                                        target: form,
15570                                        preventDefault: () => {}
15571                                    };
15572                                    window.addToCartSubmit(fakeEvent, productId);
15573                                }
15574                            } else if (typeof window.addToCart === 'function') {
15575                                // Direct addToCart call
15576                                window.addToCart(productId, 1, variantId);
15577                            }
15578                        } else if (isBuyButton) {
15579                            // Trigger buy action
15580                            if (typeof window.buyNow === 'function') {
15581                                window.buyNow();
15582                            }
15583                        }
15584                    } else {
15585                        // Button is disabled, don't allow action
15586                        showStockModal('Produk ini sedang tidak tersedia.');
15587                    }
15588                }
15589            } else {
15590                // No product ID, block action
15591                showStockModal('Data produk tidak valid.');
15592            }
15593
15594            return false; // Always block at capture phase
15595
15596        }, true); // Use capture phase to run BEFORE other listeners
15597
15598
15599    }
15600
15601    /**
15602     * Initialize button data attributes from current page
15603     */
15604    function initializeButtonAttributes() {
15605        // Get product data from page globals or form
15606        let productId = 0, variantId = 0;
15607
15608        // Try to get from global variables (set by PHP)
15609        if (window.PD_ID) productId = parseInt(window.PD_ID);
15610
15611        // Try to get from form
15612        const form = document.getElementById('pd-buy-form');
15613        if (form) {
15614            const pidAttr = form.getAttribute('data-pid');
15615            if (pidAttr) productId = parseInt(pidAttr);
15616
15617            const varInput = document.getElementById('pd-variant-id');
15618            if (varInput) variantId = parseInt(varInput.value || 0);
15619        }
15620
15621        // Try to get from mobile FAB
15622        const mobileFab = document.getElementById('pd-mobile-fab');
15623        if (mobileFab && productId <= 0) {
15624            const pidAttr = getDataAttribute(mobileFab, 'pid');
15625            if (pidAttr) productId = parseInt(pidAttr);
15626        }
15627
15628        // Set attributes on desktop buttons
15629        const desktopButtons = document.querySelectorAll('.btn-add-cart, .btn-buy-now, #btn-add-cart, #btn-buy, #btn-buy-voucher');
15630        desktopButtons.forEach(button => {
15631            if (productId > 0) {
15632                button.setAttribute('data-product-id', productId);
15633                if (variantId > 0) {
15634                    button.setAttribute('data-variant-id', variantId);
15635                }
15636            }
15637        });
15638
15639        // Set attributes on mobile buttons
15640        const mobileButtons = document.querySelectorAll('.mf-cart, .mf-buy, #mf-cart, #mf-buy');
15641        mobileButtons.forEach(button => {
15642            if (productId > 0) {
15643                button.setAttribute('data-product-id', productId);
15644                if (variantId > 0) {
15645                    button.setAttribute('data-variant-id', variantId);
15646                }
15647            }
15648        });
15649
15650
15651
15652        // Initial stock display update on page load
15653        if (productId > 0) {
15654            setTimeout(() => {
15655                updateStockDisplay(productId, variantId);
15656
15657            }, 100);
15658        }
15659    }
15660
15661    /**
15662     * Update button attributes when variant changes
15663     */
15664    function updateButtonAttributesForVariant(variantId) {
15665        const productId = window.PD_ID ? parseInt(window.PD_ID) : 0;
15666
15667        if (productId <= 0) return;
15668
15669        // Update desktop buttons
15670        const desktopButtons = document.querySelectorAll('.btn-add-cart, .btn-buy-now, #btn-add-cart, #btn-buy, #btn-buy-voucher');
15671        desktopButtons.forEach(button => {
15672            button.setAttribute('data-product-id', productId);
15673            if (variantId > 0) {
15674                button.setAttribute('data-variant-id', variantId);
15675            } else {
15676                button.removeAttribute('data-variant-id');
15677            }
15678        });
15679
15680        // Update mobile buttons
15681        const mobileButtons = document.querySelectorAll('.mf-cart, .mf-buy, #mf-cart, #mf-buy');
15682        mobileButtons.forEach(button => {
15683            button.setAttribute('data-product-id', productId);
15684            if (variantId > 0) {
15685                button.setAttribute('data-variant-id', variantId);
15686            } else {
15687                button.removeAttribute('data-variant-id');
15688            }
15689        });
15690
15691
15692    }
15693
15694    /**
15695     * Setup variant change integration with existing product-detail.php handlers
15696     */
15697    function setupVariantChangeIntegration() {
15698        // Strategy: Hook into existing applyVariant function instead of adding new listener
15699        // This prevents conflicts with existing variant change functionality (gallery, pricing, etc.)
15700
15701        // Method 1: Hook into existing applyVariant function
15702        setTimeout(() => {
15703            if (typeof window.applyVariant === 'function') {
15704                const originalApplyVariant = window.applyVariant;
15705                window.applyVariant = function(v, focusImage) {
15706                    // Call original function first
15707                    const result = originalApplyVariant.call(this, v, focusImage);
15708
15709                // Then add our stock validator logic
15710                const variantId = v ? (v.id || v.ID || 0) : 0;
15711
15712                // Update button attributes for stock validator
15713                updateButtonAttributesForVariant(variantId);
15714
15715                // Update stock display using embedded data (instant)
15716                const productId = window.PD_ID ? parseInt(window.PD_ID) : 0;
15717                if (productId > 0) {
15718                    updateStockDisplay(productId, variantId);
15719                }                    return result;
15720                };
15721
15722
15723            } else {
15724
15725                setupVariantObserver();
15726            }
15727        }, 500);
15728
15729        // Method 2: Direct observation of hidden input value changes (fallback)
15730        setTimeout(() => {
15731            setupVariantObserver();
15732        }, 1000);
15733    }
15734
15735    /**
15736     * Setup variant observer as fallback when applyVariant hook fail
15736s
15737     */
15738    function setupVariantObserver() {
15739        const variantInput = document.getElementById('pd-variant-id');
15740        if (variantInput) {
15741            let lastVariantId = parseInt(variantInput.value || '0');
15742
15743            // Use MutationObserver to watch for value changes
15744            if (window.MutationObserver) {
15745                const observer = new MutationObserver((mutations) => {
15746                    mutations.forEach((mutation) => {
15747                        if (mutation.type === 'attributes' && mutation.attributeName === 'value') {
15748                            const newVariantId = parseInt(variantInput.value || '0');
15749                            if (newVariantId !== lastVariantId) {
15750
15751                                updateButtonAttributesForVariant(newVariantId);
15752
15753                                // Update stock display with embedded data
15754                                const productId = window.PD_ID ? parseInt(window.PD_ID) : 0;
15755                                if (productId > 0) {
15756                                    updateStockDisplay(productId, newVariantId);
15757                                }
15758
15759                                lastVariantId = newVariantId;
15760                            }
15761                        }
15762                    });
15763                });
15764
15765                observer.observe(variantInput, {
15766                    attributes: true,
15767                    attributeFilter: ['value']
15768                });
15769
15770
15771            }
15772
15773            // Additional polling as ultimate fallback
15774            setInterval(() => {
15775                const currentVariantId = parseInt(variantInput.value || '0');
15776                if (currentVariantId !== lastVariantId) {
15777
15778                    updateButtonAttributesForVariant(currentVariantId);
15779
15780                    // Update stock display with embedded data
15781                    const productId = window.PD_ID ? parseInt(window.PD_ID) : 0;
15782                    if (productId > 0) {
15783                        updateStockDisplay(productId, currentVariantId);
15784                    }
15785
15786                    lastVariantId = currentVariantId;
15787                }
15788            }, 1000);
15789
15790
15791        }
15792    }
15793
15794    /**
15795     * Update stock display and enforce stock-based button blocking using embedded data
15796     */
15797    function updateStockDisplay(productId, variantId = 0) {
15798
15799
15800        // Get stock info from embedded data (instant)
15801        const embeddedStockInfo = getStockFromEmbeddedData(variantId);
15802
15803        let stockInfo;
15804        if (embeddedStockInfo) {
15805            // Convert embedded data to standard format
15806            stockInfo = {
15807                can_add_to_cart: embeddedStockInfo.hasStock,
15808                is_in_stock: embeddedStockInfo.hasStock,
15809                available_stock: embeddedStockInfo.stock,
15810                message: embeddedStockInfo.hasStock ? 'Stok tersedia' : `Stok habis${embeddedStockInfo.sku ? ` untuk ${embeddedStockInfo.sku}` : ''}`,
15811                product_name: window.PD_NAME || 'Produk',
15812                sku: embeddedStockInfo.sku
15813            };
15814
15815
15816        } else {
15817            // Fallback to API call if embedded data not available
15818
15819            checkStock(productId, variantId, 1)
15820                .then(apiStockInfo => {
15821                    updateStockDisplayWithInfo(apiStockInfo, productId, variantId);
15822                })
15823                .catch(error => {
15824                    console.error('Failed to update stock display via API:', error);
15825                });
15826            return;
15827        }
15828
15829        updateStockDisplayWithInfo(stockInfo, productId, variantId);
15830    }
15831
15832    /**
15833     * Update stock display with provided stock info
15834     */
15835    function updateStockDisplayWithInfo(stockInfo, productId, variantId) {
15836        // Store current stock status globally for blocking checks
15837        window._currentStockInfo = stockInfo;
15838
15839        // Update stock indicators
15840        const stockIndicators = document.querySelectorAll('.stock-indicator');
15841        stockIndicators.forEach(indicator => {
15842            if (stockInfo.is_in_stock) {
15843                indicator.classList.add('in-stock');
15844                indicator.classList.remove('out-of-stock');
15845            } else {
15846                indicator.classList.add('out-of-stock');
15847                indicator.classList.remove('in-stock');
15848            }
15849        });
15850
15851        // Update stock count
15852        const stockCounts = document.querySelectorAll('.stock-count');
15853        stockCounts.forEach(count => {
15854            count.textContent = stockInfo.available_stock || 0;
15855        });
15856
15857        // CRITICAL: For digital products, DO NOT modify button states here
15858        // PHP already handles button disable/enable based on LICENSE_POOL count
15859        // Only physical products need JavaScript stock validation
15860        const isDigitalProduct = window.IS_DIGITAL === true || window.IS_DIGITAL === 'true';
15861
15862        if (isDigitalProduct) {
15863            // For digital products: ONLY update global stock info, don't touch buttons
15864            // Let PHP-rendered disabled state remain authoritative
15865            console.log('[Stock Validator] Digital product detected - skipping button state modification');
15866            return; // Exit early, preserve PHP-set button states
15867        }
15868
15869        // ENHANCED: Force disable buttons for out-of-stock with anti-tampering (PHYSICAL PRODUCTS ONLY)
15870        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');
15871        allButtons.forEach(button => {
15872            if (stockInfo.can_add_to_cart) {
15873                // STOCK AVAILABLE - ENABLE BUTTONS
15874                button.classList.remove(config.disabledClass);
15875                button.disabled = false;
15876                button.style.opacity = '';
15877                button.style.cursor = '';
15878
15879                // CRITICAL: Remove stock blocking attributes when stock becomes available
15880                button.removeAttribute('data-stock-blocked');
15881                button.removeAttribute('data-stock-reason');
15882
15883
15884            } else {
15885                // OUT OF STOCK - FORCE DISABLE WITH ANTI-TAMPERING
15886                button.classList.add(config.disabledClass);
15887                button.disabled = true;
15888                button.style.opacity = '0.5';
15889                button.style.cursor = 'not-allowed';
15890
15891                // Mark as stock-blocked to prevent re-enabling
15892                button.setAttribute('data-stock-blocked', 'true');
15893                button.setAttribute('data-stock-reason', stockInfo.message || 'Stok tidak tersedia');
15894
15895
15896            }
15897        });
15898
15899        // Start aggressive button monitoring for out-of-stock variants
15900        if (!stockInfo.can_add_to_cart) {
15901            enforceStockDisabling(productId, variantId);
15902        }
15903
15904
15905    }
15906
15907    /**
15908     * Aggressively enforce button disabling for out-of-stock items
15909     * CRITICAL: For digital products, DO NOT modify button states (PHP is authoritative)
15910     */
15911    function enforceStockDisabling(productId, variantId) {
15912        // CRITICAL: Skip enforcement for digital products - PHP handles button states
15913        const isDigitalProduct = window.IS_DIGITAL === true || window.IS_DIGITAL === 'true';
15914        if (isDigitalProduct) {
15915            console.log('[Stock Validator] Digital product - skipping button enforcement');
15916            return; // Exit early, let PHP-rendered disabled state remain
15917        }
15918
15919        const monitorInterval = setInterval(() => {
15920            // Check if variant changed
15921            const currentVariantSelector = document.getElementById('pd-variant-id');
15922            const currentVariantId = currentVariantSelector ? parseInt(currentVariantSelector.value || 0) : 0;
15923
15924            if (currentVariantId !== variantId) {
15925                // Variant changed, stop monitoring this variant
15926                clearInterval(monitorInterval);
15927
15928                return;
15929            }
15930
15931            // Check current stock status using embedded data (real-time check)
15932            const currentStockInfo = getStockFromEmbeddedData(currentVariantId);
15933
15934            if (currentStockInfo && !currentStockInfo.hasStock) {
15935                // Still out of stock - continue monitoring for tampering
15936                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');
15937                buttons.forEach(button => {
15938                    // Check if button was manually enabled (inspect attack)
15939                    if (!button.disabled || button.style.opacity !== '0.5') {
15940
15941                        // Force re-disable
15942                        button.disabled = true;
15943                        button.style.opacity = '0.5';
15944                        button.style.cursor = 'not-allowed';
15945                        button.setAttribute('data-stock-blocked', 'true');
15946                        button.setAttribute('data-stock-reason', `Stok tidak tersedia${currentStockInfo.sku ? ` untuk ${currentStockInfo.sku}` : ''}`);
15947
15948                        // Show warning toast
15949                        if (window.showToast) {
15950                            window.showToast({
15951                                type: 'warning',
15952                                title: 'Aksi Tidak Diizinkan',
15953                                message: 'Produk ini sedang habis stok. Silakan pilih varian lain.',
15954                                duration: 3000
15955                            });
15956                        }
15957                    }
15958                });
15959            } else {
15960                // Stock available OR embedded data shows stock - stop monitoring
15961                clearInterval(monitorInterval);
15962
15963                // For PHYSICAL products only, enable buttons
15964                // For DIGITAL products, skip (PHP is authoritative)
15965                const isDigitalProduct = window.IS_DIGITAL === true || window.IS_DIGITAL === 'true';
15966
15967                if (!isDigitalProduct && currentStockInfo && currentStockInfo.hasStock) {
15968                    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');
15969                    buttons.forEach(button => {
15970                        button.disabled = false;
15971                        button.style.opacity = '';
15972                        button.style.cursor = '';
15973                        button.removeAttribute('data-stock-blocked');
15974                        button.removeAttribute('data-stock-reason');
15975                        button.classList.remove(config.disabledClass);
15976                    });
15977
15978                }
15979            }
15980        }, 500); // Check every 500ms for tampering attempts
15981
15982        // Auto-stop monitoring after 30 seconds to prevent memory leaks
15983        setTimeout(() => {
15984            clearInterval(monitorInterval);
15985
15986        }, 30000);
15987    }
15988
15989    /**
15990     * Override addToCartSubmit function to add stock validation
15991     * This function now works with pd-addtocart.js integration
15992     */
15993    function overrideAddToCartSubmitFunction() {
15994        // Check if pd-addtocart.js is already loaded
15995        const pdAddToCartExists = typeof window.addToCartSubmit === 'function' &&
15996                                  window.addToCartSubmit.toString().includes('showToast');
15997
15998        if (pdAddToCartExists) {
15999
16000
16001            // Store the pd-addtocart.js function
16002            const pdAddToCartSubmit = window.addToCartSubmit;
16003            window._pdAddToCartSubmit = pdAddToCartSubmit;
16004
16005            // Override with stock validation that calls pd-addtocart.js
16006            window.addToCartSubmit = async function(e, productId) {
16007
16008                // Don't prevent default here - let pd-addtocart handle it if stock is valid
16009
16010                try {
16011                    // Extract product data from form (same way as pd-addtocart.js)
16012                    const form = e.target;
16013                    const qty = parseInt(form.querySelector('[name=qty]')?.value || '1', 10) || 1;
16014
16015                    // CRITICAL: Always get current variant from selector (real-time)
16016                    let variantId = 0;
16017                    const variantSelector = document.getElementById('pd-variant-id');
16018                    if (variantSelector) {
16019                        variantId = parseInt(variantSelector.value || '0', 10) || 0;
16020                    }
16021
16022                    if (!productId || productId <= 0) {
16023                        return pdAddToCartSubmit(e, productId);
16024                    }
16025
16026
16027                    // Validate stock before proceeding
16028                    const isValid = await validateBeforeCart(productId, variantId, qty, 'cart');
16029
16030                    if (isValid) {
16031                        // Stock OK, call pd-addtocart.js function (it will handle toast)
16032                        return pdAddToCartSubmit(e, productId);
16033                    } else {
16034                        // Prevent the form submission
16035                        e.preventDefault();
16036                        // Stock validation failed, modal already shown by validateBeforeCart
16037                        return false;
16038                    }
16039
16040                } catch (error) {
16041                    e.preventDefault();
16042                    // On error, show modal and don't proceed
16043                    showStockModal({
16044                        message: 'Terjadi kesalahan saat memvalidasi stok. Silakan coba lagi.',
16045                        is_active: false
16046                    }, 'cart');
16047                    return false;
16048                }
16049            };
16050        } else {
16051            // Original logic for when pd-addtocart.js is not present
16052            const originalAddToCartSubmit = typeof window.addToCartSubmit === 'function' ? window.addToCartSubmit : null;
16053
16054            if (originalAddToCartSubmit) {
16055                window._originalAddToCartSubmit = originalAddToCartSubmit;
16056            }
16057
16058            // Override with stock validation
16059            window.addToCartSubmit = async function(e, productId) {
16060
16061                e.preventDefault();
16062
16063                try {
16064                    const form = e.target;
16065                    const qty = parseInt(form.querySelector('[name=qty]')?.value || '1', 10) || 1;
16066
16067                    // CRITICAL: Always get current variant from selector (real-time)
16068                    let variantId = 0;
16069                    const variantSelector = document.getElementById('pd-variant-id');
16070                    if (variantSelector) {
16071                        variantId = parseInt(variantSelector.value || '0', 10) || 0;
16072                    }
16073
16074                    if (!productId || productId <= 0) {
16075                        if (originalAddToCartSubmit) return originalAddToCartSubmit(e, productId);
16076                        return false;
16077                    }
16078
16079
16080                    const isValid = await validateBeforeCart(productId, variantId, qty, 'cart');
16081
16082                    if (isValid) {
16083                        if (originalAddToCartSubmit) {
16084                            return originalAddToCartSubmit(e, productId);
16085                        } else {
16086                            if (typeof window.addToCart === 'function') {
16087                                await window.addToCart(productId, qty, variantId);
16088                            }
16089                        }
16090                    } else {
16091                    }
16092
16093                } catch (error) {
16094                    showStockModal({
16095                        message: 'Terjadi kesalahan saat memvalidasi stok. Silakan coba lagi.',
16096                        is_active: false
16097                    }, 'cart');
16098                }
16099
16100                return false;
16101            };
16102        }
16103
16104
16105    }
16106
16107    /**
16108     * Override buyNow function to add stock validation
16109     */
16110    function overrideBuyNowFunction() {
16111        // Store original buyNow function if it exists
16112        const originalBuyNow = typeof window.buyNow === 'function' ? window.buyNow : null;
16113
16114        // Override with stock validation
16115        window.buyNow = async function(e) {
16116
16117            try {
16118                // Get product data (same way as pd-buynow.js)
16119                const form = document.getElementById('pd-buy-form');
16120                if (!form) {
16121                    if (originalBuyNow) return originalBuyNow(e);
16122                    return;
16123                }
16124
16125                const productId = parseInt(form.dataset.pid || '0', 10);
16126                const qty = parseInt(form.querySelector('[name=qty]')?.value || '1', 10) || 1;
16127
16128                // CRITICAL: Always get current variant from selector (real-time)
16129                let variantId = 0;
16130                const variantSelector = document.getElementById('pd-variant-id');
16131                if (variantSelector) {
16132                    variantId = parseInt(variantSelector.value || '0', 10) || 0;
16133                }
16134
16135                if (!productId || productId <= 0) {
16136                    if (originalBuyNow) return originalBuyNow(e);
16137                    return;
16138                }
16139
16140
16141                // Validate stock before proceeding
16142                const isValid = await validateBeforeCart(productId, variantId, qty, 'buy');
16143
16144                if (isValid) {
16145                    // Stock OK, call original buyNow function
16146                    if (originalBuyNow) {
16147                        return originalBuyNow(e);
16148                    } else {
16149                    }
16150                } else {
16151                    // Stock validation failed, modal already shown by validateBeforeCart
16152                }
16153
16154            } catch (error) {
16155                // On error, show modal and don't proceed
16156                showStockModal({
16157                    message: 'Terjadi kesalahan saat memvalidasi stok. Silakan coba lagi.',
16158                    is_active: false
16159                }, 'buy');
16160            }
16161        };
16162
16163
16164    }
16165
16166    /**
16167     * Initialize stock validation
16168     */
16169    function init() {
16170        // Wait for DOM ready
16171        if (document.readyState === 'loading') {
16172            addEventListener(document, 'DOMContentLoaded', function() {
16173        initializeButtonAttributes();
16174        setupVariantChangeIntegration(); // Setup variant change integration
16175        interceptCartButtons();
16176        setupGlobalClickInterceptor(); // Setup global click security interceptor                // Override functions after other scripts load
16177                // Use multiple timeouts to catch late-loading scripts like pd-addtocart.js
16178                setTimeout(() => {
16179                    overrideAddToCartSubmitFunction();
16180                    overrideBuyNowFunction();
16181                }, 100);
16182
16183                setTimeout(() => {
16184                    // Re-check and re-override if pd-addtocart.js loaded later
16185                    overrideAddToCartSubmitFunction();
16186                }, 500);
16187
16188                setTimeout(() => {
16189                    // Final check for very late loading scripts
16190                    overrideAddToCartSubmitFunction();
16191                }, 1000);
16192
16193
16194            });
16195        } else {
16196            initializeButtonAttributes();
16197            setupVariantChangeIntegration(); // Setup variant change integration
16198            interceptCartButtons();
16199            setupGlobalClickInterceptor(); // Setup global click security interceptor
16200
16201            // Override functions after other scripts load
16202            // Use multiple timeouts to catch late-loading scripts like pd-addtocart.js
16203            setTimeout(() => {
16204                overrideAddToCartSubmitFunction();
16205                overrideBuyNowFunction();
16206            }, 100);
16207
16208            setTimeout(() => {
16209                // Re-check and re-override if pd-addtocart.js loaded later
16210                overrideAddToCartSubmitFunction();
16211            }, 500);
16212
16213            setTimeout(() => {
16214                // Final check for very late loading scripts
16215                overrideAddToCartSubmitFunction();
16216            }, 1000);
16217
16218
16219        }
16220    }
16221
16222    // Public API
16223    return {
16224        init: init,
16225        checkStock: checkStock,
16226        validateBeforeCart: validateBeforeCart,
16227        updateStockDisplay: updateStockDisplay,
16228        showStockModal: showStockModal,
16229        config: config
16230    };
16231})();
16232
16233// Auto-initialize
16234if (typeof window !== 'undefined') {
16235    StockValidator.init();
16236}
16237
16238</script>
vendor: 2 bytes, line 16238
16238
16239<script data-inlined="pd-addtocart.js">
16240(function(){
16241  function esc(s){ return String(s||'').replace(/[&<>"']/g, m=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;' }[m])); }
16242  function getVariantSummaryText(){
16243    const vs = document.getElementById('pd-variant-summary');
16244    if (!vs) return '';
16245    const items = Array.from(vs.querySelectorAll('.vs-item strong')).map(el => el.textContent.trim()).filter(Boolean);
16246    return items.length ? items.join(' / ') : '';
16247  }
16248  window.addToCartSubmit = async function(e, productId){
16249    e.preventDefault();
16250    try {
16251      const form = e.target;
16252      const qty = parseInt(form.querySelector('[name=qty]')?.value || '1', 10) || 1;
16253      const variantId = parseInt(document.getElementById('pd-variant-id')?.value || '0', 10) || 0;
16254      await (window.addToCart ? window.addToCart(productId, qty, variantId) : Promise.reject(new Error('addToCart tidak tersedia')));
16255
16256      if (window.PlazaAnalytics) {
16257        window.PlazaAnalytics.track('add_to_cart', { slug: String(window._pit_slug || '') });
16258      }
16259
16260      const name = (window.PD_NAME !== undefined) ? String(window.PD_NAME) : (document.getElementById('pd-title')?.textContent || '');
16261      const sku  = (function(){ const el=document.getElementById('pd-sku'); return el?el.textContent.trim(): (window.PD_SKU||''); })();
16262      const variantText = getVariantSummaryText();
16263
16264      if (window.showToast) {
16265        window.showToast({
16266          type: 'success',
16267          title: 'Berhasil ditambahkan ke Keranjang',
16268          message: `<strong>${esc(name)}</strong>${variantText?` · <em>${esc(variantText)}</em>`:''}<br>SKU: ${esc(sku)} · Qty: ${qty}`,
16269          duration: 5000,
16270          actions: [
16271            { label: 'Lihat Keranjang', onClick: ()=> { window.location.href = '/keranjang'; } },
16272            { label: 'Lanjut Belanja', variant: 'alt', onClick: ()=>{} }
16273          ]
16274        });
16275      }
16276    } catch (err) {
16277      if (window.showToast) {
16278        window.showToast({ type:'warn', title:'Gagal menambahkan', message: String(err?.message||'Terjadi kesalahan'), duration: 4500 });
16279      } else {
16280        console.error(err);
16281      }
16282    }
16283    return false;
16284  };
16285})();
16286</script>
vendor: 2 bytes, line 16286
16286
16287<script data-inlined="pd-buynow.js">
16288(function(){
16289
16290  // CSRF Token Helper
16291  function getCsrfToken() {
16292    return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
16293  }
16294
16295  // Utility functions
16296  function esc(s){ return String(s||'').replace(/[&<>"']/g, m=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;' }[m])); }
16297
16298  function getVariantSummaryText(){
16299    const vs = document.getElementById('pd-variant-summary');
16300    if (!vs) return '';
16301    const items = Array.from(vs.querySelectorAll('.vs-item strong')).map(el => el.textContent.trim()).filter(Boolean);
16302    return items.length ? items.join(' / ') : '';
16303  }
16304
16305  function getCurrentProductData(){
16306    const form = document.getElementById('pd-buy-form');
16307    if (!form) return null;
16308
16309    const productId = parseInt(form.dataset.pid || '0', 10);
16310    const qty = parseInt(form.querySelector('[name=qty]')?.value || '1', 10) || 1;
16311    const variantId = parseInt(document.getElementById('pd-variant-id')?.value || '0', 10) || 0;
16312
16313    if (!productId || productId <= 0) {
16314      throw new Error('Product ID tidak valid');
16315    }
16316
16317    if (qty <= 0) {
16318      throw new Error('Quantity harus lebih dari 0');
16319    }
16320
16321    return { productId, qty, variantId };
16322  }
16323
16324  function createBuyNowPayload(productId, qty, variantId) {
16325    // Get product metadata
16326    const name = (window.PD_NAME !== undefined) ? String(window.PD_NAME) : (document.getElementById('pd-title')?.textContent || '');
16327
16328    // Get correct price, SKU, and weight from selected variant data instead of DOM elements
16329    let price = 0;
16330    let sku = '';
16331    let weight = 0;
16332    let dimensions = null;
16333
16334    if (variantId > 0 && typeof window.variants !== 'undefined' && Array.isArray(window.variants)) {
16335      // Find the selected variant in the variants array
16336      const selectedVariant = window.variants.find(v => v.id === variantId);
16337      if (selectedVariant) {
16338        price = selectedVariant.final || 0;
16339        sku = selectedVariant.sku || '';
16340        weight = selectedVariant.weight || 0;
16341
16342        // Include dimensions if available
16343        if (selectedVariant.length_cm || selectedVariant.width_cm || selectedVariant.height_cm) {
16344          dimensions = {
16345            length_cm: selectedVariant.length_cm || 0,
16346            width_cm: selectedVariant.width_cm || 0,
16347            height_cm: selectedVariant.height_cm || 0
16348          };
16349        }
16350
16351        // Debug info to verify fix is working
16352        if (window.console && window.console.debug) {
16353          console.debug('BuyNow: Using variant data', {
16354            variantId: variantId,
16355            price: price,
16356            sku: sku,
16357            weight: weight,
16358            dimensions: dimensions,
16359            variantData: selectedVariant
16360          });
16361        }
16362      }
16363    }
16364
16365    // Fallback to DOM elements if variant data not found
16366    if (price === 0) {
16367      const priceTxt = document.getElementById('pd-price-current')?.textContent || '';
16368      price = parseInt(String(priceTxt).replace(/[^\d]/g,''), 10) || 0;
16369
16370      if (window.console && window.console.debug) {
16371        console.debug('BuyNow: Using DOM price fallback', {
16372          priceTxt: priceTxt,
16373          price: price,
16374          variantId: variantId,
16375          reason: variantId > 0 ? 'variant_data_not_found' : 'no_variant_selected'
16376        });
16377      }
16378    }
16379
16380    if (!sku) {
16381      const skuEl = document.getElementById('pd-sku');
16382      sku = skuEl ? skuEl.textContent.trim() : (window.PD_SKU || '');
16383    }
16384
16385    const imgEl = document.getElementById('pd-main-img');
16386    const image_url = imgEl ? (imgEl.getAttribute('src') || '') : '';
16387
16388    const variantText = getVariantSummaryText();
16389
16390    // Get pre-order data if product is pre-order
16391    const preorderData = window.PD_PREORDER_DATA || null;
16392
16393    return {
16394      product_id: productId,
16395      qty: qty,
16396      variant_id: variantId,
16397      meta: {
16398        name,
16399        sku,
16400        price,
16401        image_url,
16402        variant_text: variantText,
16403        weight: weight,
16404        dimensions: dimensions
16405      },
16406      is_preorder: preorderData ? 'Y' : 'N',
16407      preorder_estimated_arrival: preorderData ? preorderData.estimated_arrival : null,
16408      preorder_note: preorderData ? preorderData.note : ''
16409    };
16410  }
16411
16412  /**
16413   * Main Buy Now function
16414   * Performs validation and redirects to checkout with buy_now mode
16415   */
16416  window.buyNow = async function(e) {
16417    if (e) e.preventDefault();
16418
16419    try {
16420      // Check user login status
16421      const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true' ||
16422                        document.body.classList.contains('logged-in') ||
16423                        window.userLoggedIn === true ||
16424                        document.body.dataset.userLoggedIn === 'true';
16425
16426      if (!isLoggedIn) {
16427        // Redirect to login with return URL to current page
16428        const currentUrl = encodeURIComponent(window.location.pathname + window.location.search);
16429        window.location.href = '/login?return=' + currentUrl;
16430        return;
16431      }
16432
16433      // Get current product data
16434      const { productId, qty, variantId } = getCurrentProductData();
16435
16436      // Create payload for buy now
16437      const payload = createBuyNowPayload(productId, qty, variantId);
16438
16439      // Store buy now data in sessionStorage for checkout page
16440      sessionStorage.setItem('buyNowData', JSON.stringify(payload));
16441
16442      // Track add_to_cart (buy now counts as purchase intent)
16443      if (window.PlazaAnalytics) {
16444        window.PlazaAnalytics.track('add_to_cart', { slug: String(window._pit_slug || '') });
16445      }
16446
16447      // Redirect to checkout with buy_now mode
16448      window.location.href = '/checkout?mode=buy_now';
16449
16450    } catch (err) {
16451      console.error('Buy Now error:', err);
16452
16453      if (window.showToast) {
16454        window.showToast({
16455          type: 'warn',
16456          title: 'Gagal memproses',
16457          message: String(err?.message || 'Terjadi kesalahan saat memproses pembelian'),
16458          duration: 4500
16459        });
16460      } else {
16461        alert(err.message || 'Terjadi kesalahan saat memproses pembelian');
16462      }
16463    }
16464  };
16465
16466  /**
16467   * Alternative Buy Now with confirmation dialog
16468   */
16469  window.buyNowWithConfirm = async function(e) {
16470    if (e) e.preventDefault();
16471
16472    try {
16473      const { productId, qty, variantId } = getCurrentProductData();
16474
16475      const name = (window.PD_NAME !== undefined) ? String(window.PD_NAME) : (document.getElementById('pd-title')?.textContent || '');
16476      const variantText = getVariantSummaryText();
16477      const priceTxt = document.getElementById('pd-price-current')?.textContent || 'Rp0';
16478
16479      const confirmMessage = `Lanjut ke checkout?\n\nProduk: ${name}${variantText ? `\nVariant: ${variantText}` : ''}\nQty: ${qty}\nHarga: ${priceTxt}`;
16480
16481      if (!confirm(confirmMessage)) {
16482        return;
16483      }
16484
16485      // Proceed with buy now
16486      await window.buyNow(e);
16487
16488    } catch (err) {
16489      console.error('Buy Now with confirm error:', err);
16490      alert(err.message || 'Terjadi kesalahan');
16491    }
16492  };
16493
16494  /**
16495   * Attach event listener for Buy with Voucher button
16496   */
16497  function setupBuyButtons() {
16498    // Setup desktop buy button (regular buy now without voucher)
16499    const buyBtn = document.getElementById('btn-buy');
16500    if (buyBtn) {
16501      buyBtn.addEventListener('click', function(e) {
16502        e.preventDefault();
16503        if (buyBtn.disabled) return;
16504        if (window.buyNow) {
16505          window.buyNow(e);
16506        }
16507      });
16508    }
16509
16510    // Setup desktop buy with voucher button
16511    const voucherBtn = document.getElementById('btn-buy-voucher');
16512    if (voucherBtn) {
16513      voucherBtn.addEventListener('click', function(e) {
16514        e.preventDefault();
16515
16516        // Validate stock availability before proceeding
16517        if (this.disabled || this.getAttribute('data-stock-blocked') === 'true') {
16518          const stockReason = this.getAttribute('data-stock-reason') || 'Stok tidak tersedia';
16519
16520          if (window.showToast) {
16521            window.showToast({
16522              type: 'warn',
16523              title: 'Stok Tidak Tersedia',
16524              message: stockReason,
16525              duration: 3500
16526            });
16527          } else {
16528            alert(stockReason);
16529          }
16530          return;
16531        }
16532
16533        const voucherCode = this.dataset.voucherCode;
16534        if (voucherCode) {
16535          window.buyNowWithVoucher(e, voucherCode);
16536        }
16537      });
16538    }
16539
16540  }
16541
16542  // CRITICAL: Attach mobile button handler at DOCUMENT level with CAPTURE phase
16543  // This is the ONLY way to guarantee it fires regardless of other scripts
16544  document.addEventListener('click', function(e) {
16545    // Check if click is on mobile buy button or its children
16546    const target = e.target.closest('#mf-buy');
16547    if (!target) return; // Not our button
16548
16549    e.preventDefault();
16550    e.stopPropagation();
16551    e.stopImmediatePropagation();
16552
16553    if (target.disabled) return;
16554
16555    // Get voucher code from parent FAB's data attribute
16556    const mobileFab = document.getElementById('pd-mobile-fab');
16557    const voucherCode = mobileFab ? (mobileFab.getAttribute('data-voucher-code') || '') : '';
16558
16559    if (voucherCode && voucherCode.trim() !== '' && window.buyNowWithVoucher) {
16560      window.buyNowWithVoucher(e, voucherCode);
16561    } else if (window.buyNow) {
16562      window.buyNow(e);
16563    }
16564  }, true); // CAPTURE PHASE - fires BEFORE any bubble phase handlers
16565
16566  // Execute immediately if DOM already loaded, otherwise wait
16567  if (document.readyState === 'loading') {
16568    document.addEventListener('DOMContentLoaded', setupBuyButtons);
16569  } else {
16570    setupBuyButtons();
16571  }
16572
16573  /**
16574   * Buy Now with Auto-Applied Voucher
16575   * Applies voucher to session before redirecting to checkout (like cart does)
16576   *
16577   * @param {Event} e - Click event
16578   * @param {string} voucherCode - Voucher code to apply
16579   */
16580  window.buyNowWithVoucher = async function(e, voucherCode) {
16581    if (e) e.preventDefault();
16582
16583    console.log('[buyNowWithVoucher] Called with voucher code:', voucherCode);
16584
16585    try {
16586      // CRITICAL: Check stock availability first
16587      const voucherBtn = document.getElementById('btn-buy-voucher');
16588      if (voucherBtn) {
16589        if (voucherBtn.disabled || voucherBtn.getAttribute('data-stock-blocked') === 'true') {
16590          const stockReason = voucherBtn.getAttribute('data-stock-reason') || 'Stok tidak tersedia';
16591
16592          if (window.showToast) {
16593            window.showToast({
16594              type: 'warn',
16595              title: 'Stok Tidak Tersedia',
16596              message: stockReason,
16597              duration: 3500
16598            });
16599          } else {
16600            alert(stockReason);
16601          }
16602          return;
16603        }
16604      }
16605
16606      // Check login status first
16607      const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true' ||
16608                        document.body.classList.contains('logged-in') ||
16609                        window.userLoggedIn === true ||
16610                        document.body.dataset.userLoggedIn === 'true';
16611
16612      console.log('[buyNowWithVoucher] User logged in:', isLoggedIn);
16613
16614      if (!isLoggedIn) {
16615        const currentUrl = encodeURIComponent(window.location.pathname + window.location.search);
16616        window.location.href = '/login?return=' + currentUrl;
16617        return;
16618      }
16619
16620      // Get current product data
16621      const { productId, qty, variantId } = getCurrentProductData();
16622      const payload = createBuyNowPayload(productId, qty, variantId);
16623
16624      console.log('[buyNowWithVoucher] Product data:', { productId, qty, variantId });
16625
16626      // Get category path for voucher validation
16627      const categoryPath = window.PD_CATEGORY_PATH || '';
16628
16629      // Step 1: Clear any existing voucher first (important for clean state)
16630      console.log('[buyNowWithVoucher] Step 1: Clearing existing voucher...');
16631      try {
16632        await fetch('/api/voucher/remove.php', {
16633          method: 'POST',
16634          headers: {
16635            'X-CSRF-Token': getCsrfToken()
16636          },
16637          body: JSON.stringify({
16638            csrf_token: getCsrfToken()
16639          }),
16640          credentials: 'same-origin'
16641        });
16642      } catch (e) {
16643        // Ignore error if no voucher exists
16644        console.debug('No existing voucher to remove or removal failed');
16645      }
16646
16647      // Step 2: Apply voucher to session (like cart does)
16648      console.log('[buyNowWithVoucher] Step 2: Applying voucher to session...');
16649      const applyRes = await fetch('/api/voucher/apply.php', {
16650        method: 'POST',
16651        headers: {
16652          'Content-Type': 'application/json',
16653          'X-CSRF-Token': getCsrfToken()
16654        },
16655        credentials: 'same-origin',
16656        body: JSON.stringify({
16657          csrf_token: getCsrfToken(),
16658          code: voucherCode,
16659          items: [{
16660            pid: productId,
16661            price: payload.meta.price,
16662            qty: qty,
16663            category_path: categoryPath
16664          }],
16665          shipping_cost: null // Will be calculated in checkout
16666        })
16667      });
16668
16669      const applyData = await applyRes.json();
16670
16671      console.log('[buyNowWithVoucher] Voucher apply response:', applyData);
16672
16673      if (!applyData.success) {
16674        // Voucher invalid - show error and fallback to regular buy now
16675        console.warn('[buyNowWithVoucher] Voucher application failed:', applyData.message);
16676        if (window.showToast) {
16677          window.showToast({
16678            type: 'error',
16679            title: 'Voucher Tidak Valid',
16680            message: applyData.message || 'Voucher tidak dapat diterapkan untuk produk ini',
16681            duration: 4000
16682          });
16683        } else {
16684          alert(applyData.message || 'Voucher tidak dapat diterapkan');
16685        }
16686
16687        // Fallback to regular buy now without voucher
16688        console.log('Voucher failed, proceeding with regular buy now');
16689        return window.buyNow(e);
16690      }
16691
16692      // Step 3: Store buy now data in sessionStorage
16693      console.log('[buyNowWithVoucher] Step 3: Storing buy now data in sessionStorage');
16694      sessionStorage.setItem('buyNowData', JSON.stringify(payload));
16695
16696      // Step 4: Redirect to checkout
16697      // Voucher sudah di session, checkout akan auto-load via loadAppliedVoucher()
16698      console.log('[buyNowWithVoucher] Step 4: Redirecting to checkout with voucher applied');
16699      window.location.href = '/checkout?mode=buy_now';
16700
16701    } catch (err) {
16702      console.error('Buy Now with Voucher error:', err);
16703
16704      if (window.showToast) {
16705        window.showToast({
16706          type: 'error',
16707          title: 'Terjadi Kesalahan',
16708          message: 'Gagal memproses pembelian dengan voucher. Silakan coba lagi.',
16709          duration: 3000
16710        });
16711      } else {
16712        alert('Gagal memproses pembelian. Silakan coba lagi.');
16713      }
16714    }
16715  };
16716})();
16717</script>
vendor: 2 bytes, line 16717
16717
16718<script data-inlined="pd-voucher-recalc.js">
16719/**
16720 * Product Detail - Voucher Discount Recalculation
16721 *
16722 * Handles dynamic voucher discount recalculation when variant price changes
16723 * Supports both PERCENTAGE and FIXED_AMOUNT voucher types
16724 */
16725
16726(function() {
16727  'use strict';
16728
16729  /**
16730   * Calculate voucher discount based on type and price
16731   *
16732   * @param {string} type - Voucher type: 'PERCENTAGE' or 'FIXED_AMOUNT'
16733   * @param {number} value - Voucher value (percentage or fixed amount)
16734   * @param {number} price - Current variant price
16735   * @param {number} maxDiscount - Maximum discount for percentage vouchers (0 = no limit)
16736   * @return {number} Calculated discount amount
16737   */
16738  function calculateVoucherDiscount(type, value, price, maxDiscount) {
16739    if (type === 'PERCENTAGE') {
16740      // Calculate percentage discount
16741      let discount = (price * value) / 100;
16742
16743      // Apply max discount cap if set
16744      if (maxDiscount > 0 && discount > maxDiscount) {
16745        discount = maxDiscount;
16746      }
16747
16748      return Math.round(discount);
16749    } else {
16750      // FIXED_AMOUNT - discount doesn't change with price
16751      return Math.round(value);
16752    }
16753  }
16754
16755  /**
16756   * Format number as Rupiah without 'Rp' prefix
16757   *
16758   * @param {number} num - Number to format
16759   * @return {string} Formatted number (e.g., "20.000")
16760   */
16761  function formatNumber(num) {
16762    return Math.round(num).toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.');
16763  }
16764
16765  /**
16766   * Update voucher button discount label
16767   *
16768   * @param {number} newPrice - New variant price
16769   */
16770  function updateVoucherDiscount(newPrice) {
16771    const voucherBtn = document.getElementById('btn-buy-voucher');
16772    if (!voucherBtn) return;
16773
16774    // Get voucher data from button attributes
16775    const voucherType = voucherBtn.dataset.voucherType;
16776    const voucherValue = parseFloat(voucherBtn.dataset.voucherValue) || 0;
16777    const voucherMax = parseFloat(voucherBtn.dataset.voucherMax) || 0;
16778
16779    if (!voucherType || voucherValue === 0) return;
16780
16781    // Calculate new discount
16782    const newDiscount = calculateVoucherDiscount(voucherType, voucherValue, newPrice, voucherMax);
16783
16784    // Update the discount label
16785    const saveLabel = voucherBtn.querySelector('.bv-save');
16786    if (saveLabel) {
16787      saveLabel.textContent = '-Rp' + formatNumber(newDiscount);
16788    }
16789
16790    // Also update mobile FAB voucher display if exists
16791    updateMobileFabVoucher(newDiscount);
16792  }
16793
16794  /**
16795   * Update mobile FAB voucher value
16796   *
16797   * @param {number} discountAmount - New discount amount
16798   */
16799  function updateMobileFabVoucher(discountAmount) {
16800    const fab = document.getElementById('pd-mobile-fab');
16801    if (fab) {
16802      fab.dataset.voucherValue = discountAmount.toString();
16803    }
16804  }
16805
16806  /**
16807   * Listen to variant change events and recalculate discount
16808   */
16809  function initVoucherRecalculation() {
16810    window.addEventListener('pd-variant-changed', function(e) {
16811      const detail = e.detail || {};
16812      const newPrice = detail.price || 0;
16813
16814      if (newPrice > 0) {
16815        updateVoucherDiscount(newPrice);
16816      }
16817    });
16818  }
16819
16820  // Initialize when DOM is ready
16821  if (document.readyState === 'loading') {
16822    document.addEventListener('DOMContentLoaded', initVoucherRecalculation);
16823  } else {
16824    initVoucherRecalculation();
16825  }
16826
16827})();
16828
16829</script>
vendor: 2 bytes, line 16829
16829
16830<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>
16830
16831
16832<script nonce="se3iyzEHMc9FQEB4wSyd0w==">
16833(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'}})}}
16834
16835function _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()}})();
16836
16837(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()}})})();
16838</script>
16838<footer class="site-footer footer-compact variant-stripes" role="contentinfo">
16839  <div class="ft-top ft-collapsible" id="ftTop">
16840    <div class="container ft-grid">
16841
16842      <div class="ft-col ft-help">
16843        <h4 class="ft-title">Butuh Bantuan?</h4>
16844        <p class="ft-desc">Hubungi kami melalui Official WhatsApp interaktif sekarang.</p>
16845        <a class="ft-wa-btn" href="https://calcourtsfitness.nichesite.org/" target="_blank" rel="nofollow noopener">
16846          <span class="ico" aria-hidden="true">
16847            <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>
16848          </span>
16849          Kirim Pesan
16850        </a>
16851
16852        <p class="ft-desc sm-gap">Ikuti kami:</p>
16853        <div class="ft-social">
16854          <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>
16855          <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
16855V7.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>
16856          <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>
16857        </div>
16858      </div>
16859
16860            <!-- Collapsible Content Wrapper (Non-Homepage Only) -->
16861      <div class="ft-collapsible-content" id="ftCollapsible">
16862
16863      <div class="ft-col">
16864        <h4 class="ft-title">Tentang PIONTOGEL</h4>
16865        <ul class="ft-links">
16866          <li><a href="https://calcourtsfitness.com/">Tentang PIONTOGEL</a></li>
16867          <li><a href="https://calcourtsfitness.com/">Kontak PIONTOGEL</a></li>
16868          <li><a href="https://calcourtsfitness.com/">Lokasi Toko</a></li>
16869          <li><a href="https://calcourtsfitness.com/">FAQ</a></li>
16870        </ul>
16871        <div class="ft-address">
16872          <p class="company">PT.ANGIN MALAM</p>
16873                      <p>JL. Harapan Blok Melati No. 27, Kel. Pademangan Barat,</p>
16874                      <p>Kec. Jakarta, Kota Jakarta, DKI JAKARTA, 11752</p>
16875                  </div>
16876      </div>
16877
16878      <div class="ft-col">
16879        <h4 class="ft-title">Informasi</h4>
16880        <ul class="ft-links">
16881          <li><a href="/syarat-dan-ketentuan">Syarat &amp; Ketentuan</a></li>
16882          <li><a href="/kebijakan-privasi">Kebijakan Privasi</a></li>
16883          <li><a href="/kebijakan-cookie">Kebijakan Cookie</a></li>
16884          <li><a href="/pengiriman-dan-pengembalian">Pengiriman &amp; Pengembalian</a></li>
16885          <li><a href="/pembayaran-dan-cicilan">Pembayaran &amp; Cicilan</a></li>
16886          <li><a href="/garansi-dan-layanan">Garansi &amp; Layanan</a></li>
16887        </ul>
16888
16889        <div class="ft-payments payments-small" aria-label="Metode Pembayaran">
16890          <div class="pay-row">
16891            <img src="https://xn--wcsp1x05m74d.live/images/2026/07/18/mandiri-PIONTOGEL.png" alt="Mandiri" loading="lazy">
16892            <img src="https://xn--wcsp1x05m74d.live/images/2026/07/18/bni-PIONTOGEL.gif" alt="BNI" loading="lazy">
16893            <img src="https://xn--wcsp1x05m74d.live/images/2026/07/18/bri-PIONTOGEL.png" alt="BRI" loading="lazy">
16894            <img src="https://xn--wcsp1x05m74d.live/images/2026/07/18/qris-PIONTOGEL.gif" alt="QRIS" loading="lazy">
16895            <img src="https://xn--wcsp1x05m74d.live/images/2026/07/18/bca-PIONTOGEL.gif" alt="BCA" loading="lazy">
16896          </div>
16897        </div>
16898      </div>
16899
16900            </div>
16901      <!-- End Collapsible Content -->
16902
16903      <!-- Expand Button (Mobile Only) -->
16904      <button class="ft-expand-btn" id="ftExpandBtn" aria-label="Lihat semua informasi footer">
16905        <span class="expand-text">Selengkapnya</span>
16906        <svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
16907          <path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/>
16908        </svg>
16909      </button>
16910
16911    </div>
16912  </div>
16913  <div class="ft-bottom">
16914    <div class="container ft-bottom-inner">
16915      <div class="ft-copy">
16916        <span>ANGIN-MALAM &copy; <span id="yearNow">2026</span></span>
16917        <span>PT ANGIN-MALAM. All rights reserved.</span>
16918      </div>
16919    </div>
16920  </div>
16921
16922          <style nonce="se3iyzEHMc9FQEB4wSyd0w==">
16923            /* Chat Widget Styles - Lightweight & Minimal */
16924            .chat-widget {
16925                position: fixed;
16926                bottom: 20px;
16927                right: 20px;
16928                z-index: 9999;
16929                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
16930            }
16931
16932            /* Hide floating button on mobile - use bottom nav instead */
16933            @media (max-width: 640px) {
16934                .chat-toggle-btn {
16935                    display: none !important;
16936                }
16937
16938                /* Ensure popup is still visible on mobile even when button is hidden */
16939                .chat-widget {
16940                    display: block !important;
16941                }
16942
16943                .chat-popup {
16944                    display: none; /* Hidden by default, shown via JS */
16945                }
16946
16947                .chat-popup[style*="display: block"],
16948                .chat-popup[style*="display:block"] {
16949                    display: block !important;
16950                }
16951            }
16952
16953            .chat-toggle-btn {
16954                width: 60px;
16955                height: 60px;
16956                border-radius: 50%;
16957                background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
16958                border: none;
16959                color: white;
16960                cursor: pointer;
16961                box-shadow: 0 4px 12px rgba(242, 140, 26, 0.3);
16962                position: relative;
16963                transition: transform 0.2s, box-shadow 0.2s;
16964            }
16965
16966            .chat-toggle-btn:hover {
16967                transform: scale(1.05);
16968                box-shadow: 0 6px 20px rgba(242, 140, 26, 0.4);
16969            }
16970
16971            #chat-toggle-btn .chat-icon {
16972                position: absolute;
16973                top: 50%;
16974                left: 50%;
16975                transform: translate(-50%, -50%);
16976                width: 28px !important;
16977                height: 28px !important;
16978            }
16979
16980            .chat-unread-badge {
16981                position: absolute;
16982                top: -5px;
16983                right: -5px;
16984                background: #ef4444;
16985                color: white;
16986                border-radius: 12px;
16987                padding: 2px 6px;
16988                font-size: 11px;
16989                font-weight: 600;
16990                min-width: 20px;
16991                text-align: center;
16992            }
16993
16994            .chat-popup {
16995                position: absolute;
16996                bottom: 80px;
16997                right: 0;
16998                width: 380px;
16999                max-width: calc(100vw - 40px);
17000                height: 500px;
17001                max-height: calc(100vh - 120px);
17002                background: white;
17003                border-radius: 12px;
17004                box-shadow: 0 10px 40px rgba(0,0,0,0.2);
17005                display: flex;
17006                flex-direction: column;
17007                overflow: hidden;
17008            }
17009
17010            .chat-popup-header {
17011                background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
17012                color: white;
17013                padding: 16px 20px;
17014                display: flex;
17015                justify-content: space-between;
17016                align-items: center;
17017                flex-shrink: 0;
17018            }
17019
17020            .chat-header-title {
17021                display: flex;
17022                flex-direction: column;
17023                gap: 2px;
17024                min-height: 0;
17025            }
17026
17027            .chat-popup-header h3 {
17028                margin: 0;
17029                font-size: 18px;
17030                font-weight: 600;
17031                line-height: 1.2;
17032            }
17033
17034            .chat-operating-hours {
17035                font-size: 11px;
17036                opacity: 0.9;
17037                font-weight: 400;
17038                display: flex;
17039                align-items: center;
17040                gap: 4px;
17041                line-height: 1.2;
17042            }
17043
17044            .chat-operating-hours svg {
17045                width: 12px;
17046                height: 12px;
17047                opacity: 0.8;
17048                flex-shrink: 0;
17049            }
17050
17051            .chat-header-actions {
17052                display: flex;
17053                align-items: center;
17054                gap: 8px;
17055            }
17056
17057            /* Notification Toggle Button */
17058            .btn-notif-toggle {
17059                background: none;
17060                border: none;
17061                color: white;
17062                font-size: 18px;
17063                cursor: pointer;
17064                padding: 4px 8px;
17065                transition: all 0.2s;
17066                opacity: 0.8;
17067            }
17068
17069            .btn-notif-toggle:hover {
17070                opacity: 1;
17071                transform: scale(1.1);
17072            }
17073
17074            /* Icon states */
17075            .btn-notif-toggle[data-status="not-subscribed"] svg {
17076                opacity: 0.7;
17077            }
17078
17079            .btn-notif-toggle[data-status="not-subscribed"]:hover svg {
17080                opacity: 1;
17081            }
17082
17083            .btn-notif-toggle[data-status="subscribed"] svg {
17084                opacity: 1;
17085                animation: bellRing 2s ease-in-out;
17086            }
17087
17088            .btn-notif-toggle[data-status="blocked"] svg {
17089                color: #ff6b6b;
17090            }
17091
17092            .btn-notif-toggle[data-status="not-supported"] {
17093                display: none !important;
17094            }
17095
17096            @keyframes bellRing {
17097                0%, 100% { transform: rotate(0deg); }
17098                10%, 30% { transform: rotate(-10deg); }
17099                20%, 40% { transform: rotate(10deg); }
17100            }
17101
17102            /* Notification Info Bar */
17103            .chat-notif-info-bar {
17104                position: absolute;
17105                top: 64px;
17106                left: 0;
17107                right: 0;
17108                background: linear-gradient(to right, #e3f2fd, #f5f5f5);
17109                border-bottom: 1px solid #bbdefb;
17110                padding: 8px 12px;
17111                display: flex;
17112                align-items: center;
17113                gap: 8px;
17114                font-size: 12px;
17115                color: #616161;
17116                z-index: 10;
17117                animation: slideDown 0.3s ease;
17118            }
17119
17120            .chat-notif-info-bar svg {
17121                flex-shrink: 0;
17122            }
17123
17124            .chat-notif-info-bar .small {
17125                flex: 1;
17126                line-height: 1.4;
17127            }
17128
17129            .chat-notif-info-bar .btn-dismiss {
17130                background: none;
17131                border: none;
17132                color: #9e9e9e;
17133                cursor: pointer;
17134                padding: 0 4px;
17135                font-size: 16px;
17136                transition: color 0.2s;
17137            }
17138
17139            .chat-notif-info-bar .btn-dismiss:hover {
17140                color: #616161;
17141            }
17142
17143            @keyframes slideDown {
17144                from {
17145                    max-height: 0;
17146                    opacity: 0;
17147                }
17148                to {
17149                    max-height: 50px;
17150                    opacity: 1;
17151                }
17152            }
17153
17154            .chat-close-btn {
17155                background: none;
17156                border: none;
17157                color: white;
17158                font-size: 28px;
17159                line-height: 1;
17160                cursor: pointer;
17161                padding: 0;
17162                width: 30px;
17163                height: 30px;
17164            }
17165
17166            .chat-popup-body {
17167                flex: 1 1 auto;
17168                display: flex;
17169                flex-direction: column;
17170                min-height: 0;
17171                overflow: hidden;
17172                height: calc(100% - 64px);
17173                position: relative;
17174                transition: padding-top 0.3s ease;
17175            }
17176
17177            /* Add padding when info bar is visible */
17178            .chat-popup-body.has-info-bar {
17179                padding-top: 40px;
17180            }
17181
17182            .chat-view {
17183                flex: 1 1 auto;
17184                display: none;
17185                flex-direction: column;
17186                min-height: 0;
17187                overflow: hidden;
17188            }
17189
17190            #chat-list-view {
17191                display: flex;
17192            }
17193
17194            #chat-messages-view {
17195                display: none;
17196            }
17197
17198            #chat-messages-view.active {
17199                display: flex;
17200                flex-direction: column;
17201            }
17202
17203            #chat-new-view {
17204                display: none;
17205            }
17206
17207            #chat-new-view.active {
17208                display: flex;
17209            }
17210
17211            .chat-conversations-list {
17212                flex: 1;
17213                overflow-y: auto;
17214                padding: 0;
17215                background: white;
17216            }
17217
17218            /* Custom Scrollbar for Conversations List */
17219            .chat-conversations-list::-webkit-scrollbar {
17220                width: 6px;
17221            }
17222
17223            .chat-conversations-list::-webkit-scrollbar-track {
17224                background: #f1f1f1;
17225            }
17226
17227            .chat-conversations-list::-webkit-scrollbar-thumb {
17228                background: #F28C1A;
17229                border-radius: 3px;
17230            }
17231
17232            .chat-conversations-list::-webkit-scrollbar-thumb:hover {
17233                background: #D97809;
17234            }
17235
17236            /* Firefox */
17237            .chat-conversations-list {
17238                scrollbar-width: thin;
17239                scrollbar-color: #F28C1A #f1f1f1;
17240            }
17241
17242            .chat-conversation-item {
17243                padding: 12px 16px;
17244                cursor: pointer;
17245                display: flex;
17246                gap: 12px;
17247                align-items: flex-start;
17248                transition: background 0.15s ease;
17249                color: #33371f;
17250                border-bottom: 1px solid #f0f0f0;
17251            }
17252
17253            .chat-conversation-item:hover {
17254                background: #f5f5f5;
17255            }
17256
17257            .chat-conversation-item.active {
17258                background: #FFF5EB;
17259            }
17260
17261            /* WhatsApp-like Avatar */
17262            .conversation-avatar {
17263                width: 48px;
17264                height: 48px;
17265                border-radius: 50%;
17266                background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
17267                flex-shrink: 0;
17268                display: flex;
17269                align-items: center;
17270                justify-content: center;
17271                color: white;
17272                font-weight: 600;
17273                font-size: 18px;
17274            }
17275
17276            .conversation-avatar svg {
17277                width: 24px;
17278                height: 24px;
17279            }
17280
17281            /* WhatsApp-like Content */
17282            .conversation-content {
17283                flex: 1;
17284                min-width: 0;
17285                display: flex;
17286                flex-direction: column;
17287                gap: 4px;
17288            }
17289
17290            .conversation-header {
17291                display: flex;
17292                justify-content: space-between;
17293                align-items: center;
17294                gap: 8px;
17295            }
17296
17297            .conversation-name {
17298                font-weight: 600;
17299                font-size: 15px;
17300                color: #111;
17301                white-space: nowrap;
17302                overflow: hidden;
17303                text-overflow: ellipsis;
17304            }
17305
17306            .conversation-time {
17307                font-size: 12px;
17308                color: #7d8166;
17309                flex-shrink: 0;
17310            }
17311
17312            .conversation-preview {
17313                display: flex;
17314                justify-content: space-between;
17315                align-items: center;
17316                gap: 8px;
17317            }
17318
17319            .conversation-message {
17320                font-size: 14px;
17321                color: #667781;
17322                white-space: nowrap;
17323                overflow: hidden;
17324                text-overflow: ellipsis;
17325                margin: 0;
17326                flex: 1;
17327            }
17328
17329            .conversation-unread {
17330                background: #25D366;
17331                color: white;
17332                border-radius: 12px;
17333                padding: 2px 8px;
17334                font-size: 12px;
17335                font-weight: 600;
17336                min-width: 20px;
17337                text-align: center;
17338                flex-shrink: 0;
17339            }
17340
17341            /* Active item styling */
17342            .chat-conversation-item.active .conversation-name {
17343                color: #F28C1A;
17344            }
17345
17346            .chat-conversation-item.active .conversation-time {
17347                color: #F28C1A;
17348            }
17349
17350            .chat-new-btn {
17351                margin: 12px;
17352                padding: 12px;
17353                background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
17354                color: white;
17355                border: none;
17356                border-radius: 8px;
17357                cursor: pointer;
17358                font-weight: 500;
17359                font-size: 14px;
17360                display: flex;
17361                align-items: center;
17362                justify-content: center;
17363                gap: 8px;
17364                transition: all 0.2s;
17365                flex-shrink: 0;
17366            }
17367
17368            .chat-new-btn:hover {
17369                transform: translateY(-1px);
17370                box-shadow: 0 4px 8px rgba(242, 140, 26, 0.3);
17371            }
17372
17373            .chat-conversation-title {
17374                flex: 1;
17375                font-weight: 600;
17376                font-size: 16px;
17377                color: #1f2937;
17378            }
17379
17380            .chat-messages-container {
17381                flex: 1;
17382                overflow-y: auto;
17383                padding: 16px;
17384                display: flex;
17385                flex-direction: column;
17386                gap: 8px;
17387                min-height: 0;
17388                background: #E5DDD5;
17389            }
17390
17391            /* Custom Scrollbar for Messages Container */
17392            .chat-messages-container::-webkit-scrollbar {
17393                width: 6px;
17394            }
17395
17396            .chat-messages-container::-webkit-scrollbar-track {
17397                background: rgba(0, 0, 0, 0.05);
17398            }
17399
17400            .chat-messages-container::-webkit-scrollbar-thumb {
17401                background: rgba(242, 140, 26, 0.6);
17402                border-radius: 3px;
17403            }
17404
17405            .chat-messages-container::-webkit-scrollbar-thumb:hover {
17406                background: #F28C1A;
17407            }
17408
17409            /* Firefox */
17410            .chat-messages-container {
17411                scrollbar-width: thin;
17412                scrollbar-color: rgba(242, 140, 26, 0.6) rgba(0, 0, 0, 0.05);
17413            }
17414
17415            /* WhatsApp Group-like Messages */
17416            .chat-message {
17417                display: flex;
17418                margin-bottom: 4px;
17419                animation: fadeIn 0.2s ease;
17420            }
17421
17422            @keyframes fadeIn {
17423                from { opacity: 0; transform: translateY(10px); }
17424                to { opacity: 1; transform: translateY(0); }
17425            }
17426
17427            .chat-message.mine {
17428                justify-content: flex-end;
17429            }
17430
17431            .chat-message-bubble {
17432                max-width: 80%;
17433                padding: 6px 8px 8px 10px;
17434                border-radius: 8px;
17435                position: relative;
17436                box-shadow: 0 1px 0.5px rgba(0,0,0,0.13);
17437            }
17438
17439            /* Bubble from others (Admin/CS) */
17440            .chat-message:not(.mine) .chat-message-bubble {
17441                background: #FFFFFF;
17442                border-radius: 0 8px 8px 8px;
17443            }
17444
17445            /* Bubble tail for others */
17446            .chat-message:not(.mine) .chat-message-bubble::before {
17447                content: '';
17448                position: absolute;
17449                top: 0;
17450                left: -8px;
17451                width: 0;
17452                height: 0;
17453                border-style: solid;
17454                border-width: 0 8px 13px 0;
17455                border-color: transparent #FFFFFF transparent transparent;
17456            }
17457
17458            /* Bubble from user (mine) */
17459            .chat-message.mine .chat-message-bubble {
17460                background: #e4f5d0; /* Light blue for user messages */
17461                border-radius: 8px 0 8px 8px;
17462            }
17463
17464            /* Bubble tail for mine */
17465            .chat-message.mine .chat-message-bubble::before {
17466                content: '';
17467                position: absolute;
17468                top: 0;
17469                right: -8px;
17470                width: 0;
17471                height: 0;
17472                border-style: solid;
17473                border-width: 0 0 13px 8px;
17474                border-color: transparent transparent transparent #eaf5d0;
17475            }
17476
17477            .message-sender {
17478                font-size: 12.5px;
17479                font-weight: 600;
17480                color: #F28C1A !important;
17481                margin-bottom: 4px;
17482            }
17483
17484            .chat-message.mine .message-sender {
17485                display: none;
17486            }
17487
17488            /* Message text */
17489            .message-text {
17490                font-size: 14px;
17491                line-height: 19px;
17492                color: #ffffff !important;
17493                word-wrap: break-word;
17494                white-space: pre-wrap;
17495                margin-bottom: 4px;
17496            }
17497
17498            /* Message metadata (time + status) */
17499            .message-meta {
17500                display: flex;
17501                align-items: center;
17502                justify-content: flex-end;
17503                gap: 4px;
17504                margin-top: 2px;
17505            }
17506
17507            .message-time {
17508                font-size: 11px;
17509                color: #728166 !important;
17510                line-height: 15px;
17511            }
17512
17513            /* Use multiple selectors to override global styles */
17514            .chat-message-bubble .message-status,
17515            .message-meta .message-status,
17516            .chat-message .message-status {
17517                width: 16px !important;
17518                height: 16px !important;
17519                min-width: 16px !important;
17520                min-height: 16px !important;
17521                max-width: 16px !important;
17522                max-height: 16px !important;
17523                flex-shrink: 0;
17524                color: #728166 !important;
17525                fill: currentColor !important;
17526            }
17527
17528            .chat-message:not(.mine) .message-status {
17529                display: none;
17530            }
17531
17532            /* Loading dots animation */
17533            @keyframes loadingDots {
17534                0%, 20% { content: '.'; }
17535                40% { content: '..'; }
17536                60%, 100% { content: '...'; }
17537            }
17538
17539            .message-status.loading::after {
17540                content: '...';
17541                animation: loadingDots 1.5s infinite;
17542                font-size: 16px;
17543                line-height: 16px;
17544            }
17545
17546            .message-status.loading path {
17547                display: none;
17548            }
17549
17550            .message-status.failed {
17551                color: #ef4444 !important;
17552                cursor: pointer;
17553            }
17554
17555            .message-status.sent {
17556                color: #667781 !important;
17557            }
17558
17559            .chat-new-form {
17560                flex: 1;
17561                overflow-y: auto;
17562                padding: 16px;
17563                display: flex;
17564                flex-direction: column;
17565            }
17566
17567            .chat-new-form form {
17568                display: flex;
17569                flex-direction: column;
17570                gap: 16px;
17571                flex: 1;
17572            }
17573
17574            .chat-new-form .form-group {
17575                display: flex;
17576                flex-direction: column;
17577                gap: 6px;
17578            }
17579
17580            .chat-new-form label {
17581                font-size: 14px;
17582                font-weight: 500;
17583                color: #374151;
17584            }
17585
17586            .chat-new-form .form-control {
17587                width: 100%;
17588                padding: 10px 12px;
17589                border: 1px solid #d1d5db;
17590                border-radius: 8px;
17591                font-size: 14px;
17592                font-family: inherit;
17593                transition: border-color 0.2s;
17594                box-sizing: border-box;
17595            }
17596
17597            .chat-new-form .form-control:focus {
17598                outline: none;
17599                border-color: #F28C1A;
17600                box-shadow: 0 0 0 3px rgba(242, 140, 26, 0.1);
17601            }
17602
17603            .chat-new-form textarea.form-control {
17604                resize: vertical;
17605                min-height: 100px;
17606            }
17607
17608            .chat-new-form .btn {
17609                padding: 12px 20px;
17610                border: none;
17611                border-radius: 8px;
17612                font-size: 15px;
17613                font-weight: 500;
17614                cursor: pointer;
17615                transition: all 0.2s;
17616                text-align: center;
17617            }
17618
17619            .chat-new-form .btn-primary {
17620                background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
17621                color: white;
17622            }
17623
17624            .chat-new-form .btn-primary:hover {
17625                transform: translateY(-1px);
17626                box-shadow: 0 4px 8px rgba(242, 140, 26, 0.3);
17627            }
17628
17629            .chat-new-form .btn-primary:active {
17630                transform: scale(0.98);
17631            }
17632
17633            .chat-new-form .btn-block {
17634                width: 100%;
17635                margin-top: auto;
17636            }
17637
17638            .chat-message-form {
17639                padding: 12px;
17640                border-top: 1px solid #e5e7eb;
17641                display: flex;
17642                flex-wrap: wrap;
17643                gap: 8px;
17644                align-items: flex-end;
17645                flex-shrink: 0;
17646                background: white;
17647            }
17648
17649            .chat-product-card {
17650                flex: 1 1 100%;
17651                max-width: 100%;
17652                display: flex;
17653                gap: 12px;
17654                padding: 12px;
17655                background: #f3f4f6;
17656                border-radius: 8px;
17657                margin-bottom: 4px;
17658                border: 1px solid #e5e7eb;
17659            }
17660
17661            .chat-product-card img {
17662                width: 60px;
17663                height: 60px;
17664                object-fit: cover;
17665                border-radius: 6px;
17666                flex-shrink: 0;
17667            }
17668
17669            .chat-product-card-content {
17670                flex: 1;
17671                min-width: 0;
17672            }
17673
17674            .chat-product-card-name {
17675                font-weight: 500;
17676                font-size: 14px;
17677                color: #1f2937;
17678                margin-bottom: 4px;
17679                white-space: nowrap;
17680                overflow: hidden;
17681                text-overflow: ellipsis;
17682            }
17683
17684            .chat-product-card-variant {
17685                font-size: 12px;
17686                color: #74806b;
17687                margin-bottom: 4px;
17688            }
17689
17690            .chat-product-card-price {
17691                font-size: 13px;
17692                color: #8bea66;
17693                font-weight: 600;
17694            }
17695
17696            .chat-product-card-remove {
17697                align-self: flex-start;
17698                background: none;
17699                border: none;
17700                color: #9ca3af;
17701                cursor: pointer;
17702                padding: 4px;
17703                font-size: 18px;
17704                line-height: 1;
17705                transition: color 0.2s;
17706            }
17707
17708            .chat-product-card-remove:hover {
17709                color: #ef4444;
17710            }
17711
17712            /* Product card in messages (read-only display) */
17713            .message-product-card {
17714                display: flex;
17715                gap: 10px;
17716                padding: 10px;
17717                background: rgba(255, 255, 255, 0.5);
17718                border-radius: 8px;
17719                margin-bottom: 6px;
17720                border: 1px solid rgba(0, 0, 0, 0.08);
17721                align-items: center;
17722            }
17723
17724            .chat-message.mine .message-product-card {
17725                background: rgba(255, 255, 255, 0.7);
17726            }
17727
17728            .message-product-card img {
17729                width: 50px;
17730                height: 50px;
17731                object-fit: cover;
17732                border-radius: 6px;
17733                flex-shrink: 0;
17734            }
17735
17736            .message-product-info {
17737                flex: 1;
17738                min-width: 0;
17739            }
17740
17741            .message-product-name {
17742                font-weight: 500;
17743                font-size: 13px;
17744                color: #33371f;
17745                margin-bottom: 2px;
17746                overflow: hidden;
17747                text-overflow: ellipsis;
17748                display: -webkit-box;
17749                -webkit-line-clamp: 2;
17750                -webkit-box-orient: vertical;
17751                line-height: 1.3;
17752            }
17753
17754            .message-product-variant {
17755                font-size: 11px;
17756                color: #78806b;
17757                margin-bottom: 2px;
17758            }
17759
17760            .message-product-price {
17761                font-size: 12px;
17762                color: #c7ea66;
17763                font-weight: 600;
17764            }
17765
17766            .message-product-link {
17767                flex-shrink: 0;
17768                width: 32px;
17769                height: 32px;
17770                display: flex;
17771                align-items: center;
17772                justify-content: center;
17773                background: rgba(102, 126, 234, 0.1);
17774                border-radius: 6px;
17775                color: #c7ea66;
17776                text-decoration: none;
17777                transition: all 0.2s;
17778            }
17779
17780            .message-product-link:hover {
17781                background: #b7ea66;
17782                color: white;
17783            }
17784
17785            .message-product-link svg {
17786                width: 16px;
17787                height: 16px;
17788            }
17789
17790            .chat-message-input {
17791                flex: 1;
17792                border: 1px solid #d1d5db;
17793                border-radius: 20px;
17794                padding: 10px 16px;
17795                resize: none;
17796                max-height: 100px;
17797                font-family: inherit;
17798                font-size: 14px;
17799                line-height: 1.5;
17800                box-sizing: border-box;
17801                overflow-y: hidden;
17802            }
17803
17804            .chat-message-input:focus {
17805                outline: none;
17806                border-color: #F28C1A;
17807                box-shadow: 0 0 0 3px rgba(242, 140, 26, 0.1);
17808            }
17809
17810            .chat-send-btn {
17811                width: 40px;
17812                height: 40px;
17813                border-radius: 50%;
17814                background: linear-gradient(135deg, #F28C1A 0%, #D97809 100%);
17815                border: none;
17816                color: white;
17817                cursor: pointer;
17818                flex-shrink: 0;
17819                display: flex;
17820                align-items: center;
17821                justify-content: center;
17822                transition: all 0.2s;
17823            }
17824
17825            .chat-send-btn:hover {
17826                transform: scale(1.05);
17827                box-shadow: 0 2px 8px rgba(242, 140, 26, 0.3);
17828            }
17829
17830            .chat-send-btn:active {
17831                transform: scale(0.95);
17832            }
17833
17834            .chat-loading {
17835                text-align: center;
17836                padding: 20px;
17837                color: #6b7280;
17838            }
17839
17840            /* Date Separator (WhatsApp-style) */
17841            .date-separator {
17842                display: flex;
17843                align-items: center;
17844                gap: 12px;
17845                margin: 24px 0 16px;
17846                opacity: 0;
17847                animation: fadeInSeparator 0.3s ease forwards;
17848            }
17849
17850            @keyframes fadeInSeparator {
17851                from {
17852                    opacity: 0;
17853                    transform: translateY(-10px);
17854                }
17855                to {
17856                    opacity: 1;
17857                    transform: translateY(0);
17858                }
17859            }
17860
17861            .date-separator-line {
17862                flex: 1;
17863                height: 1px;
17864                background: linear-gradient(90deg, transparent, rgba(0, 0, 0, 0.1) 50%, transparent);
17865            }
17866
17867            .date-separator-text {
17868                flex-shrink: 0;
17869                padding: 4px 12px;
17870                background: #e9ecef;
17871                color: #6c757d;
17872                font-size: 11px;
17873                font-weight: 600;
17874                text-transform: uppercase;
17875                letter-spacing: 0.5px;
17876                border-radius: 12px;
17877                box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
17878            }
17879
17880            /* Mobile responsive */
17881            @media (max-width: 640px) {
17882                .chat-popup {
17883                    position: fixed !important; /* Override absolute positioning */
17884                    bottom: 45px !important; /* Above bottom nav */
17885                    right: 0 !important;
17886                    left: 0 !important;
17887                    width: 100% !important;
17888                    max-width: 100% !important;
17889                    /* Use dvh (dynamic viewport height) for browser UI awareness */
17890                    height: calc(100dvh - 45px) !important;
17891                    max-height: calc(100dvh - 45px) !important;
17892                    /* Fallback for browsers that don't support dvh */
17893                    height: calc(var(--viewport-height, 100vh) - 45px) !important;
17894                    max-height: calc(var(--viewport-height, 100vh) - 45px) !important;
17895                    border-radius: 0 !important;
17896                    z-index: 9999 !important;
17897                }
17898
17899                .chat-popup-header {
17900                    border-radius: 0;
17901                }
17902
17903                .chat-messages-container,
17904                .chat-conversations-list {
17905                    /* Adjust for mobile keyboard using dynamic viewport */
17906                    max-height: calc(100dvh - 190px);
17907                    /* Fallback for browsers that don't support dvh */
17908                    max-height: calc(var(--viewport-height, 100vh) - 190px);
17909                }
17910            }
17911        </style>
17912                <!-- Chat Widget Button -->
17913        <div id="chat-widget" class="chat-widget"
17914             data-user-id=""
17915             data-is-logged-in="false"
17916             data-csrf-token="df5d776748b1ed8c8dd0d987bae99b1ee3dea6c6dcbe0eedb1bea766c56d569a">
17917            <button id="chat-toggle-btn" class="chat-toggle-btn" aria-label="Toggle chat">
17918                <svg class="chat-icon" width="28" height="28" viewBox="0 0 640 640" fill="currentColor">
17919                    <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"/>
17920                </svg>
17921                <span id="chat-unread-badge" class="chat-unread-badge" style="display: none;">0</span>
17922            </button>
17923
17924            <div id="chat-popup" class="chat-popup" style="display: none;">
17925                <div class="chat-popup-header">
17926                    <div class="chat-header-title">
17927                        <h3>Bantuan</h3>
17928                        <span id="chatOperatingHours" class="chat-operating-hours" style="display: none;"></span>
17929                    </div>
17930                    <div class="chat-header-actions">
17931                        <button id="chatNotifToggle" class="btn-notif-toggle" data-status="not-supported" title="Push notification" style="display: none;">
17932                            <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
17933                                <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"/>
17934                            </svg>
17935                        </button>
17936                        <button id="chat-close-btn" class="chat-close-btn" aria-label="Close chat">
17936×</button>
17937                    </div>
17938                </div>
17939
17940                <!-- Notification Info Bar (conditional) -->
17941                <div class="chat-notif-info-bar" id="chatNotifInfoBar" style="display:none;">
17942                    <svg width="14" height="14" viewBox="0 0 24 24" fill="#ffc107">
17943                        <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"/>
17944                    </svg>
17945                    <span class="small">Aktifkan notifikasi untuk pemberitahuan balasan</span>
17946                    <button class="btn-dismiss" onclick="ChatNotification.dismissInfo()">✕</button>
17947                </div>
17948
17949                <div class="chat-popup-body">
17950                    <!-- Conversations List View -->
17951                    <div id="chat-list-view" class="chat-view">
17952                        <div id="chat-conversations-list" class="chat-conversations-list">
17953                            <div class="chat-loading">Memuat...</div>
17954                            <!-- Conversation Item Structure (rendered by JS):
17955                            <div class="chat-conversation-item">
17956                                <div class="conversation-avatar">
17957                                    <svg>...</svg>
17958                                </div>
17959                                <div class="conversation-content">
17960                                    <div class="conversation-header">
17961                                        <span class="conversation-name">Admin Support</span>
17962                                        <span class="conversation-time">10:30</span>
17963                                    </div>
17964                                    <div class="conversation-preview">
17965                                        <p class="conversation-message">Terima kasih telah menghubungi kami...</p>
17966                                        <span class="conversation-unread">2</span>
17967                                    </div>
17968                                </div>
17969                            </div>
17970                            -->
17971                        </div>
17972                        <button id="chat-new-btn" class="chat-new-btn">
17973                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
17974                                <line x1="12" y1="5" x2="12" y2="19"></line>
17975                                <line x1="5" y1="12" x2="19" y2="12"></line>
17976                            </svg>
17977                            Percakapan Baru
17978                        </button>
17979                    </div>
17980
17981                    <!-- Chat Messages View -->
17982                    <div id="chat-messages-view" class="chat-view" style="display: none;">
17983                        <div id="chat-messages-container" class="chat-messages-container">
17984                            <div class="chat-loading">Memuat pesan...</div>
17985                            <!-- Message Structure (rendered by JS):
17986                            <div class="chat-message"> (or .chat-message.mine)
17987                                <div class="chat-message-bubble">
17988                                    <div class="message-sender">Admin Support</div> (only for non-mine)
17989                                    <div class="message-text">Halo, ada yang bisa dibantu?</div>
17990                                    <div class="message-meta">
17991                                        <span class="message-time">10:30</span>
17992                                        <svg class="message-status">...</svg> (only for mine)
17993                                    </div>
17994                                </div>
17995                            </div>
17996                            -->
17997                        </div>
17998
17999                        <form id="chat-message-form" class="chat-message-form">
18000                            <input type="hidden" id="chat-current-conversation-id" value="">
18001                            <textarea
18002                                id="chat-message-input"
18003                                class="chat-message-input"
18004                                placeholder="Ketik pesan..."
18005                                rows="1"
18006                                maxlength="5000"
18007                            ></textarea>
18008                            <button type="submit" class="chat-send-btn" aria-label="Send message">
18009                                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
18010                                    <line x1="22" y1="2" x2="11" y2="
1801013"></line>
18011                                    <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
18012                                </svg>
18013                            </button>
18014                        </form>
18015                    </div>
18016
18017                    <!-- New Chat View -->
18018                    <div id="chat-new-view" class="chat-view" style="display: none;">
18019                        <div class="chat-messages-header">
18020                            <button id="chat-new-back-btn" class="chat-back-btn" aria-label="Back to list">
18021                                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
18022                                    <path d="M19 12H5M12 19l-7-7 7-7"/>
18023                                </svg>
18024                            </button>
18025                            <div class="chat-conversation-title">Percakapan Baru</div>
18026                        </div>
18027
18028                        <div class="chat-new-form">
18029                            <form id="chat-start-form">
18030                                <div class="form-group">
18031                                    <label for="chat-new-subject">Subjek</label>
18032                                    <input
18033                                        type="text"
18034                                        id="chat-new-subject"
18035                                        class="form-control"
18036                                        placeholder="Bantuan Pelanggan"
18037                                        maxlength="255"
18038                                    >
18039                                </div>
18040                                <div class="form-group">
18041                                    <label for="chat-new-message">Pesan</label>
18042                                    <textarea
18043                                        id="chat-new-message"
18044                                        class="form-control"
18045                                        rows="4"
18046                                        placeholder="Tulis pertanyaan Anda..."
18047                                        required
18048                                        maxlength="5000"
18049                                    ></textarea>
18050                                </div>
18051                                <button type="submit" class="btn btn-primary btn-block">Kirim</button>
18052                            </form>
18053                        </div>
18054                    </div>
18055                </div>
18056            </div>
18057        </div>
18058
18059        <!-- Login Required Modal -->
18060        <div class="modal-overlay" id="chatLoginModal">
18061            <div class="modal-dialog">
18062                <div class="modal-header">
18063                    <h3 class="modal-title">
18064                        <div class="modal-icon info">
18065                            <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18066                                <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"/>
18067                            </svg>
18068                        </div>
18069                        Login Diperlukan
18070                    </h3>
18071                </div>
18072                <div class="modal-body">
18073                    <p class="modal-message">Anda harus login terlebih dahulu untuk menggunakan fitur chat bantuan pelanggan.</p>
18074                    <div class="modal-actions">
18075                        <button class="modal-btn modal-btn-secondary" id="chatLoginCancel">Batal</button>
18076                        <button class="modal-btn modal-btn-primary" id="chatLoginConfirm">Login Sekarang</button>
18077                    </div>
18078                </div>
18079            </div>
18080        </div>
18081        
18081<script src="https://plazait.co.id/assets/js/chat-widget.js" nonce="se3iyzEHMc9FQEB4wSyd0w=="></script>
18081<script data-inlined="chat-notification.js">
18082/**
18083 * Customer Chat Push Notification Manager
18084 * Handles browser push notification subscription for customers
18085 * Version: 1.0.0
18086 * Date: 2026-01-19
18087 */
18088
18089const ChatNotification = {
18090    // State
18091    status: 'not-supported', // not-supported, not-subscribed, subscribed, blocked
18092    subscription: null,
18093    vapidPublicKey: null, // Will be loaded from server
18094
18095    /**
18096     * Initialize notification manager
18097     */
18098    async init() {
18099        // Check if user is logged in (only for logged-in users)
18100        if (!this.isUserLoggedIn()) {
18101            this.updateUI('not-supported');
18102            return;
18103        }
18104
18105        // Check browser support
18106        if (!this.isSupported()) {
18107            this.updateUI('not-supported');
18108            return;
18109        }
18110
18111        // Load VAPID key from server
18112        await this.loadVapidKey();
18113
18114        if (!this.vapidPublicKey) {
18115            this.updateUI('not-supported');
18116            return;
18117        }
18118
18119        // Register service worker if needed
18120        await this.registerServiceWorker();
18121
18122        // Check current permission
18123        const permission = Notification.permission;
18124
18125        if (permission === 'denied') {
18126            this.updateUI('blocked');
18127        } else if (permission === 'granted') {
18128            // Check if already subscribed
18129            const isSubscribed = await this.checkSubscriptionStatus();
18130            this.updateUI(isSubscribed ? 'subscribed' : 'not-subscribed');
18131        } else {
18132            // Default: not subscribed yet
18133            this.updateUI('not-subscribed');
18134            this.showInfoBar();
18135        }
18136    },
18137
18138    /**
18139     * Check if user is logged in
18140     */
18141    isUserLoggedIn() {
18142        const metaTag = document.querySelector('meta[name="user-logged-in"]');
18143        return metaTag ? metaTag.getAttribute('content') === 'true' : false;
18144    },
18145
18146    /**
18147     * Check if browser supports push notifications
18148     */
18149    isSupported() {
18150        return (
18151            'serviceWorker' in navigator &&
18152            'PushManager' in window &&
18153            'Notification' in window
18154        );
18155    },
18156
18157    /**
18158     * Load VAPID public key from server
18159     */
18160    async loadVapidKey() {
18161        try {
18162            const response = await fetch('/api/chat/notifications/vapid_key', {
18163                method: 'GET',
18164                headers: {
18165                    'Accept': 'application/json'
18166                }
18167            });
18168
18169            if (!response.ok) {
18170                return false;
18171            }
18172
18173            const data = await response.json();
18174            if (data.success && data.data?.public_key) {
18175                this.vapidPublicKey = data.data.public_key;
18176                return true;
18177            }
18178
18179            return false;
18180
18181        } catch (error) {
18182            console.error('[ChatNotification] Error loading VAPID key:', error);
18183            return false;
18184        }
18185    },
18186
18187    /**
18188     * Register service worker
18189     */
18190    async registerServiceWorker() {
18191        if (!('serviceWorker' in navigator)) return null;
18192
18193        try {
18194            const registration = await navigator.serviceWorker.register('/sw-push.js', {
18195                scope: '/'
18196            });
18197            return registration;
18198        } catch (error) {
18199            return null;
18200        }
18201    },
18202
18203    /**
18204     * Check subscription status from server
18205     */
18206    async checkSubscriptionStatus() {
18207        try {
18208            const response = await fetch('/api/chat/notifications/status', {
18209                method: 'GET',
18210                credentials: 'include',
18211                headers: {
18212                    'Accept': 'application/json'
18213                }
18214            });
18215
18216            if (!response.ok) {
18217                return false;
18218            }
18219
18220            const data = await response.json();
18221            return data.data?.subscribed || false;
18222
18223        } catch (error) {
18224            console.error('[ChatNotification] Error checking subscription:', error);
18225            return false;
18226        }
18227    },
18228
18229    /**
18230     * Update UI based on status
18231     */
18232    updateUI(status) {
18233        this.status = status;
18234        const btn = document.getElementById('chatNotifToggle');
18235
18236        if (!btn) {
18237            // Retry after a short delay (button might not be in DOM yet)
18238            setTimeout(() => this.updateUI(status), 100);
18239            return;
18240        }
18241
18242        btn.setAttribute('data-status', status);
18243
18244        // Show/hide button based on support
18245        if (status === 'not-supported') {
18246            btn.style.display = 'none';
18247        } else {
18248            btn.style.display = 'block';
18249        }
18250
18251        // Update SVG icon (bell vs bell-slash)
18252        const svg = btn.querySelector('svg');
18253        if (svg) {
18254            if (status === 'subscribed') {
18255                // Bell icon (active)
18256                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"/>';
18257            } else {
18258                // Bell slash icon (inactive/blocked)
18259                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"/>';
18260            }
18261        }
18262
18263        // Update tooltip
18264        const tooltips = {
18265            'not-subscribed': 'Klik untuk aktifkan notifikasi',
18266            'subscribed': 'Notifikasi aktif',
18267            'blocked': 'Notifikasi diblokir'
18268        };
18269
18270        if (tooltips[status]) {
18271            btn.title = tooltips[status];
18272        }
18273
18274        // Hide info bar if subscribed or blocked
18275        if (status === 'subscribed' || status === 'blocked') {
18276            this.hideInfoBar();
18277        }
18278    },
18279
18280    /**
18281     * Show info bar (if not dismissed)
18282     */
18283    showInfoBar() {
18284        if (localStorage.getItem('chat_notif_info_dismissed')) {
18285            return;
18286        }
18287
18288        const infoBar = document.getElementById('chatNotifInfoBar');
18289        const popupBody = document.getElementById('chat-popup')?.querySelector('.chat-popup-body');
18290
18291        if (infoBar) {
18292            infoBar.style.display = 'flex';
18293
18294            // Bind dismiss button here so it's always attached when bar is visible
18295            const btn = infoBar.querySelector('#chatNotifDismissBtn');
18296            if (btn && !btn._dismissBound) {
18297                btn._dismissBound = true;
18298                btn.addEventListener('click', () => {
18299                    infoBar.style.display = 'none';
18300                    localStorage.setItem('chat_notif_info_dismissed', '1');
18301                    if (popupBody) popupBody.classList.remove('has-info-bar');
18302                });
18303            }
18304        }
18305
18306        if (popupBody) {
18307            popupBody.classList.add('has-info-bar');
18308        }
18309    },
18310
18311    /**
18312     * Hide info bar
18313     */
18314    hideInfoBar() {
18315        const infoBar = document.getElementById('chatNotifInfoBar');
18316        const popupBody = document.getElementById('chat-popup')?.querySelector('.chat-popup-body');
18317
18318        if (infoBar) {
18319            infoBar.style.display = 'none';
18320        }
18321
18322        if (popupBody) {
18323            popupBody.classList.remove('has-info-bar');
18324        }
18325    },
18326
18327    /**
18328     * Dismiss info bar permanently
18329     */
18330    dismissInfo() {
18331        this.hideInfoBar();
18332        localStorage.setItem('chat_notif_info_dismissed', '1');
18333    },
18334
18335    /**
18336     * Handle bell icon click
18337     */
18338    async handleToggleClick() {
18339        if (this.status === 'not-subscribed') {
18340            await this.subscribe();
18341        } else if (this.status === 'subscribed') {
18342            await this.unsubscribe();
18343        } else if (this.status === 'blocked') {
18344            this.showUnblockHelp();
18345        }
18346    },
18347
18348    /**
18349     * Subscribe to push notifications
18350     */
18351    async subscribe() {
18352        try {
18353            // Request permission
18354            const permission = await Notification.requestPermission();
18355
18356            if (permission === 'denied') {
18357                this.updateUI('blocked');
18358                this.showToast('Notifikasi diblokir', 'error');
18359                return;
18360            }
18361
18362            if (permission !== 'granted') {
18363                return;
18364            }
18365
18366            // Get service worker registration
18367            const registration = await navigator.serviceWorker.ready;
18368
18369            // Subscribe to push
18370            const subscription = await registration.pushManager.subscribe({
18371                userVisibleOnly: true,
18372                applicationServerKey: this.urlBase64ToUint8Array(this.vapidPublicKey)
18373            });
18374
18375            // Send subscription to server
18376            const response = await fetch('/api/chat/notifications/subscribe', {
18377                method: 'POST',
18378                credentials: 'include',
18379                headers: {
18380                    'Content-Type': 'application/json',
18381                    'Accept': 'application/json'
18382                },
18383                body: JSON.stringify(subscription.toJSON())
18384            });
18385
18386            if (!response.ok) {
18387                throw new Error('Failed to save subscription');
18388            }
18389
18390            const result = await response.json();
18391
18392            this.subscription = subscription;
18393            this.updateUI('subscribed');
18394            this.hideInfoBar();
18395            this.showToast('Notifikasi berhasil diaktifkan!', 'success');
18396
18397            // Send test notification
18398            setTimeout(() => this.sendTestNotification(), 1000);
18399
18400        } catch (error) {
18401            console.error('[ChatNotification] Subscribe error:', error);
18402            this.showToast('Gagal mengaktifkan notifikasi', 'error');
18403        }
18404    },
18405
18406    /**
18407     * Unsubscribe from push notifications
18408     */
18409    async unsubscribe() {
18410        try {
18411            // Confirm first
18412            if (!confirm('Nonaktifkan notifikasi chat?')) {
18413                return;
18414            }
18415
18416            // Get current subscription
18417            const registration = await navigator.serviceWorker.ready;
18418            const subscription = await registration.pushManager.getSubscription();
18419
18420            if (subscription) {
18421                // Unsubscribe from browser
18422                await subscription.unsubscribe();
18423
18424                // Tell server
18425                await fetch('/api/chat/notifications/unsubscribe', {
18426                    method: 'POST',
18427                    credentials: 'include',
18428                    headers: {
18429                        'Content-Type': 'application/json',
18430                        'Accept': 'application/json'
18431                    },
18432                    body: JSON.stringify(subscription.toJSON())
18433                });
18434            }
18435
18436            this.subscription = null;
18437            this.updateUI('not-subscribed');
18438            this.showInfoBar();
18439            this.showToast('Notifikasi dinonaktifkan', 'info');
18440
18441        } catch (error) {
18442            console.error('[ChatNotification] Unsubscribe error:', error);
18443            this.showToast('Gagal menonaktifkan notifikasi', 'error');
18444        }
18445    },
18446
18447    /**
18448     * Send test notification
18449     */
18450    async sendTestNotification() {
18451        try {
18452            const response = await fetch('/api/chat/notifications/send_test', {
18453                method: 'POST',
18454                credentials: 'include',
18455                headers: {
18456                    'Accept': 'application/json'
18457                }
18458            });
18459
18460            if (response.ok) {
18461                // Test notification sent successfully
18462            }
18463        } catch (error) {
18464            // Test notification failed silently
18465        }
18466    },
18467
18468    /**
18469     * Show unblock help modal/message
18470     */
18471    showUnblockHelp() {
18472        const message = `
18473            Notifikasi diblokir oleh browser.\n\n
18474            Untuk mengaktifkan:\n
18475            1. Klik ikon gembok/info di address bar\n
18476            2. Cari "Notifications" atau "Notifikasi"\n
18477            3. Ubah menjadi "Allow" atau "Izinkan"\n
18478            4. Refresh halaman ini
18479        `.trim().replace(/\s+/g, ' ');
18480
18481        alert(message);
18482    },
18483
18484    /**
18485     * Show toast notification
18486     */
18487    showToast(message, type = 'info') {
18488        // Simple toast implementation
18489        const toast = document.createElement('div');
18490        toast.className = `chat-toast chat-toast-${type}`;
18491        toast.textContent = message;
18492        toast.style.cssText = `
18493            position: fixed;
18494            bottom: 80px;
18495            right: 20px;
18496            background: ${type === 'success' ? '#4caf50' : type === 'error' ? '#f44336' : '#2196f3'};
18497            color: white;
18498            padding: 12px 20px;
18499            border-radius: 4px;
18500            box-shadow: 0 2px 8px rgba(0,0,0,0.2);
18501            z-index: 10000;
18502            animation: slideInUp 0.3s ease;
18503        `;
18504
18505        document.body.appendChild(toast);
18506
18507        setTimeout(() => {
18508            toast.style.animation = 'slideOutDown 0.3s ease';
18509            setTimeout(() => toast.remove(), 300);
18510        }, 3000);
18511    },
18512
18513    /**
18514     * Convert VAPID key to Uint8Array
18515     */
18516    urlBase64ToUint8Array(base64String) {
18517        const padding = '='.repeat((4 - base64String.length % 4) % 4);
18518        const base64 = (base64String + padding)
18519            .replace(/\-/g, '+')
18520            .replace(/_/g, '/');
18521
18522        const rawData = window.atob(base64);
18523        const outputArray = new Uint8Array(rawData.length);
18524
18525        for (let i = 0; i < rawData.length; ++i) {
18526            outputArray[i] = rawData.charCodeAt(i);
18527        }
18528        return outputArray;
18529    },
18530
18531    /**
18532     * Check if push notification is currently active
18533     * @returns {Promise<boolean>}
18534     */
18535    async isPushActive() {
18536        try {
18537            // Check browser subscription
18538            if (!('serviceWorker' in navigator)) return false;
18539
18540            const registration = await navigator.serviceWorker.getRegistration();
18541            if (!registration) return false;
18542
18543            const subscription = await registration.pushManager.getSubscription();
18544            if (!subscription) return false;
18545
18546            // Also check server status
18547            const serverStatus = await this.checkSubscriptionStatus();
18548
18549            return serverStatus;
18550        } catch (error) {
18551            return false;
18552        }
18553    },
18554
18555    /**
18556     * Show logout confirmation if push notification is active
18557     * Call this before logout action
18558     * @returns {Promise<boolean>} true if user confirms logout, false if cancelled
18559     */
18560    async confirmLogoutWithPushNotification() {
18561        const isActive = await this.isPushActive();
18562
18563        if (!isActive) {
18564            // No active push, proceed with logout
18565            return true;
18566        }
18567
18568        // Show modal confirmation using modal.css
18569        return new Promise((resolve) => {
18570            // Create modal HTML
18571            const modalHTML = `
18572                <div class="modal-overlay" id="logoutConfirmModal">
18573                    <div class="modal-dialog">
18574                        <div class="modal-header">
18575                            <h3 class="modal-title">
18576                                <span class="modal-icon warning">⚠️</span>
18577                                Konfirmasi Logout
18578                            </h3>
18579                        </div>
18580                        <div class="modal-body">
18581                            <p class="modal-message">
18582                                Anda memiliki <strong>notifikasi push yang aktif</strong>.<br><br>
18583                                Setelah logout, Anda tidak akan menerima notifikasi chat lagi di perangkat ini.<br><br>
18584                                Apakah Anda yakin ingin melanjutkan logout?
18585                            </p>
18586                        </div>
18587                        <div class="modal-actions" style="padding: 0 20px 20px;">
18588                            <button class="modal-btn modal-btn-secondary" id="logoutCancelBtn">
18589                                Batal
18590                            </button>
18591                            <button class="modal-btn modal-btn-danger" id="logoutConfirmBtn">
18592                                Lanjutkan Logout
18593                            </button>
18594                        </div>
18595                    </div>
18596                </div>
18597            `;
18598
18599            // Append to body
18600            const modalContainer = document.createElement('div');
18601            modalContainer.innerHTML = modalHTML;
18602            document.body.appendChild(modalContainer.firstElementChild);
18603
18604            const modal = document.getElementById('logoutConfirmModal');
18605            const confirmBtn = document.getElementById('logoutConfirmBtn');
18606            const cancelBtn = document.getElementById('logoutCancelBtn');
18607
18608            // Show modal with animation
18609            requestAnimationFrame(() => {
18610                modal.classList.add('show');
18611            });
18612
18613            // Handle confirm
18614            const handleConfirm = () => {
18615                cleanup();
18616                resolve(true);
18617            };
18618
18619            // Handle cancel
18620            const handleCancel = () => {
18621                cleanup();
18622                resolve(false);
18623            };
18624
18625            // Cleanup function
18626            const cleanup = () => {
18627                modal.classList.remove('show');
18628                setTimeout(() => {
18629                    modal.remove();
18630                }, 200);
18631                confirmBtn.removeEventListener('click', handleConfirm);
18632                cancelBtn.removeEventListener('click', handleCancel);
18633                modal.removeEventListener('click', handleBackdropClick);
18634            };
18635
18636            // Handle backdrop click
18637            const handleBackdropClick = (e) => {
18638                if (e.target === modal) {
18639                    handleCancel();
18640                }
18641            };
18642
18643            // Add event listeners
18644            confirmBtn.addEventListener('click', handleConfirm);
18645            cancelBtn.addEventListener('click', handleCancel);
18646            modal.addEventListener('click', handleBackdropClick);
18647        });
18648    }
18649};
18650
18651// Add CSS animations
18652const chatNotifStyle = document.createElement('style');
18653chatNotifStyle.textContent = `
18654    @keyframes slideInUp {
18655        from {
18656            transform: translateY(100%);
18657            opacity: 0;
18658        }
18659        to {
18660            transform: translateY(0);
18661            opacity: 1;
18662        }
18663    }
18664
18665    @keyframes slideOutDown {
18666        from {
18667            transform: translateY(0);
18668            opacity: 1;
18669        }
18670        to {
18671            transform: translateY(100%);
18672            opacity: 0;
18673        }
18674    }
18675`;
18676document.head.appendChild(chatNotifStyle);
18677
18678// Auto-initialize when DOM is ready
18679if (document.readyState === 'loading') {
18680    document.addEventListener('DOMContentLoaded', () => ChatNotification.init());
18681} else {
18682    ChatNotification.init();
18683}
18684
18685// Expose to window for access from other scripts
18686window.ChatNotification = ChatNotification;
18687
18688</script>
18688
18689    <!-- Footer Collapse Toggle Script -->
18690  
18690<script nonce="se3iyzEHMc9FQEB4wSyd0w==">
18691  document.addEventListener('DOMContentLoaded', function() {
18692    const ftTop = document.getElementById('ftTop');
18693    const expandBtn = document.getElementById('ftExpandBtn');
18694
18695    if (ftTop && expandBtn) {
18696      const expandText = expandBtn.querySelector('.expand-text');
18697
18698      expandBtn.addEventListener('click', function() {
18699        ftTop.classList.toggle('expanded');
18700        const isExpanded = ftTop.classList.contains('expanded');
18701        if (expandText) {
18702          expandText.textContent = isExpanded ? 'Sembunyikan' : 'Selengkapnya';
18703        }
18704      });
18705    }
18706  });
18707  </script>
18707
18708  </footer>
18709
18710<!-- Compare Sidebar Overlay -->
18711<div class="compare-overlay" id="compareOverlay"></div>
18712
18713<!-- Floating Compare Badge (Sticky) -->
18714<button class="compare-floating-badge" id="compareFloatingBadge" aria-label="Lihat produk yang dibandingkan" style="display:none;">
18715  <svg class="cfb-icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
18716    <path d="M9.01 14H2v2h7.01v3L13 15l-3.99-4v3zm5.98-1v-3H22V8h-7.01V5L11 9l3.99 4z"/>
18717  </svg>
18718  <span class="cfb-text">Bandingkan</span>
18719  <span class="cfb-count" id="cfbCount">0</span>
18720</button>
18721
18722<!-- AI Comparison Modal -->
18723<div class="ai-compare-modal" id="aiCompareModal">
18724  <div class="modal-backdrop"></div>
18725  <div class="modal-container">
18726    <div class="modal-header">
18727      <h3 class="modal-title">
18728        <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
18729          <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"/>
18730        </svg>
18731        Analisis AI: Perbandingan Produk
18732      </h3>
18733      <button class="modal-close" id="aiCompareModalClose" aria-label="Tutup">
18734        <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18735          <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"/>
18736        </svg>
18737      </button>
18738    </div>
18739
18740    <div class="modal-content">
18741      <!-- Loading State -->
18742      <div class="ai-loading" id="aiLoading">
18743        <div class="loading-animation">
18744          <div class="typing-indicator">
18745            <span></span>
18746            <span></span>
18747            <span></span>
18748          </div>
18749          <p>AI sedang menganalisis produk...</p>
18750          <small>Proses ini membutuhkan waktu 10-30 detik</small>
18751        </div>
18752      </div>
18753
18754      <!-- Error State -->
18755      <div class="ai-error" id="aiError" hidden>
18756        <div class="error-icon">
18757          <svg width="48" height="48" viewBox="0 0 24 24" fill="#ef4444">
18758            <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"/>
18759          </svg>
18760        </div>
18761        <h4>Analisis AI Gagal</h4>
18762        <p id="aiErrorMessage">Terjadi kesalahan saat memproses analisis.</p>
18763        <button class="btn btn-outline-primary" id="aiRetry">Coba Lagi</button>
18764      </div>
18765
18766      <!-- Analysis Results -->
18767      <div class="ai-results" id="aiResults" hidden>
18768        <!-- Summary Section -->
18769        <div class="ai-summary-card">
18770          <h4>
18771            <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18772              <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"/>
18773            </svg>
18774            Ringkasan Analisis
18775          </h4>
18776          <p id="aiSummary" class="summary-text"></p>
18777          <div class="winner-badge" id="winnerBadge">
18778            <strong>Rekomendasi Terbaik:</strong> <span id="winnerProduct"></span>
18779            <small id="winnerReason"></small>
18780          </div>
18781        </div>
18782
18783        <!-- Comparison Table -->
18784        <div class="ai-comparison-table">
18785          <h4>
18786            <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18787              <path d="M3,3H21V5H3V3M3,7H21V9H3V7M3,11H21V13H3V11M3,15H21V17H3V15M3,19H21V21H3V19Z"/>
18788            </svg>
18789            Perbandingan Detail
18790          </h4>
18791          <div id="comparisonTable" class="comparison-grid"></div>
18792        </div>
18793
18794        <!-- Detailed Analysis -->
18795        <div class="ai-detailed-analysis">
18796          <h4>
18797            <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18798              <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"/>
18799            </svg>
18800            Analisis Mendalam
18801          </h4>
18802          <div id="detailedAnalysis" class="products-analysis"></div>
18803        </div>
18804
18805        <!-- Recommendations -->
18806        <div class="ai-recommendations">
18807          <h4>
18808            <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18809              <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"/>
18810            </svg>
18811            Rekomendasi Berdasarkan Kebutuhan
18812          </h4>
18813          <div class="recommendations-grid">
18814            <div class="recommendation-card">
18815              <h5>💰 Budget Conscious</h5>
18816              <p id="budgetRecommendation"></p>
18817            </div>
18818            <div class="recommendation-card">
18819              <h5>⚡ Performance Focused</h5>
18820              <p id="performanceRecommendation"></p>
18821            </div>
18822            <div class="recommendation-card">
18823              <h5>⚖️ Balanced Choice</h5>
18824              <p id="balancedRecommendation"></p>
18825            </div>
18826          </div>
18827        </div>
18828
18829        <!-- Buying Tips -->
18830        <div class="ai-tips">
18831          <h4>
18832            <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18833              <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
18833M5,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"/>
18834            </svg>
18835            Tips Pembelian
18836          </h4>
18837          <ul id="buyingTips" class="tips-list"></ul>
18838        </div>
18839      </div>
18840    </div>
18841  </div>
18842</div>
18843
18844<!-- Cart Login Modal -->
18845<div class="modal-overlay" id="cartLoginModal">
18846  <div class="modal-dialog">
18847    <div class="modal-header">
18848      <h3 class="modal-title">
18849        <div class="modal-icon info">
18850          <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
18851            <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"/>
18852          </svg>
18853        </div>
18854        Login untuk Melanjutkan
18855      </h3>
18856    </div>
18857    <div class="modal-body">
18858      <p class="modal-message" style="margin-bottom: 16px;">Silakan login atau daftar untuk mengakses keranjang belanja dan menyelesaikan pesanan Anda.</p>
18859      <div style="background: #f8fafc; border-radius: 8px; padding: 12px; margin-bottom: 20px;">
18860        <p style="font-size: 14px; color: #646947; margin: 0; line-height: 1.6;">
18861          <strong style="color: #2a2a0f;">Keuntungan dengan akun:</strong><br>
18862          ✓ Simpan keranjang belanja Anda<br>
18863          ✓ Lacak status pesanan secara real-time<br>
18864          ✓ Checkout lebih cepat dengan data tersimpan<br>
18865          ✓ Dapatkan notifikasi promo & diskon eksklusif
18866        </p>
18867      </div>
18868      <div class="modal-actions">
18869        <button class="modal-btn modal-btn-secondary" id="cartLoginCancel">Batal</button>
18870        <button class="modal-btn modal-btn-primary" id="cartLoginConfirm" style="background: #ff6b35; border-color: #ff6b35;">Login</button>
18871        <button class="modal-btn modal-btn-primary" id="cartRegisterBtn" style="background: #f7931e; border-color: #f7931e;">Daftar</button>
18872      </div>
18873    </div>
18874  </div>
18875</div>
18876
18877
18878<script data-inlined="app.js">
18879(function initLiveSearch(){
18880  function setup() {
18881    const searchInput = document.getElementById('searchInput');
18882    const suggest = document.getElementById('searchSuggest');
18883    const searchBtn = document.getElementById('searchBtn');
18884    const aiToggleBtn = document.getElementById('aiToggleBtn');
18885
18886    if (!searchInput || !suggest || !aiToggleBtn) {
18887      console.log('[LiveSearch] Elemen belum ada, retry...');
18888      return false;
18889    }
18890
18891    let debounceTimer = null;
18892    let lastQuery = '';
18893    let currentIndex = -1;
18894    let currentAbortController = null;
18895    let isAIModeEnabled = false; // AI mode OFF by default
18896    let isAIProcessing = false; // Flag to prevent closing suggest box during AI processing
18897
18898    // Trending cache
18899    let trendingCache = null;
18900    let trendingLoading = false;
18901
18902    // Trending products cache
18903    let trendingProductsCache = null;
18904    let trendingProductsLoading = false;
18905
18906    const MIN_LEN = 2;
18907    const DEBOUNCE = 300;
18908    const AI_TIMEOUT = 15000; // 15 seconds timeout for AI endpoint
18909
18910    // Load AI mode preference from localStorage
18911    const savedAIMode = localStorage.getItem('plazait_ai_search_mode');
18912    if (savedAIMode === 'enabled') {
18913      isAIModeEnabled = false;
18914      aiToggleBtn.classList.remove('active');
18915    }
18916
18917    // Update placeholder based on AI mode
18918    function updatePlaceholder() {
18919      if (isAIModeEnabled) {
18920        searchInput.placeholder = '🤖 Coba: "laptop HP core i3 untuk kerja" atau "tinta epson l3210"';
18921      } else {
18922        // Static placeholder for typing animation
18923        searchInput.placeholder = '';
18924      }
18925    }
18926
18927    // Typing animation for placeholder
18928    let typingTimeout = null;
18929    let currentProductIndex = 0;
18930    let currentCharIndex = 0;
18931    let isTyping = false;
18932    let isDeleting = false;
18933
18934    const popularProducts = [
18935      'Oppo A6',
18936      'Asus Ryzen 3',
18937      'Starlink V4',
18938      'HP Victus',
18939      'Lenovo RTX 5060',
18940      'ROG Strix',
18941      'iPhone 13',
18942      'Xpad 20',
18943      'Xiaomi 14',
18944      'Epson L3210',
18945      'Realme C85',
18946      'Brother T720',
18947      'Mouse Logitech'
18948    ];
18949
18950    function typeWriter() {
18951      if (isAIModeEnabled || document.activeElement === searchInput) {
18952        return; // Skip typing if AI mode is on or input is focused
18953      }
18954
18955      const currentProduct = popularProducts[currentProductIndex];
18956
18957      if (!isDeleting) {
18958        // Typing forward
18959        if (currentCharIndex < currentProduct.length) {
18960          searchInput.placeholder = currentProduct.substring(0, currentCharIndex + 1);
18961          currentCharIndex++;
18962          typingTimeout = setTimeout(typeWriter, 100); // Typing speed
18963        } else {
18964          // Finished typing, wait before deleting
18965          typingTimeout = setTimeout(() => {
18966            isDeleting = true;
18967            typeWriter();
18968          }, 2000); // Pause before deleting
18969        }
18970      } else {
18971        // Deleting backward
18972        if (currentCharIndex > 0) {
18973          searchInput.placeholder = currentProduct.substring(0, currentCharIndex - 1);
18974          currentCharIndex--;
18975          typingTimeout = setTimeout(typeWriter, 50); // Deleting speed (faster)
18976        } else {
18977          // Finished deleting, move to next product
18978          isDeleting = false;
18979          currentProductIndex = (currentProductIndex + 1) % popularProducts.length;
18980          typingTimeout = setTimeout(typeWriter, 500); // Pause before typing next
18981        }
18982      }
18983    }
18984
18985    function startTypingAnimation() {
18986      if (!isAIModeEnabled && !typingTimeout) {
18987        currentCharIndex = 0;
18988        isDeleting = false;
18989        typeWriter();
18990      }
18991    }
18992
18993    function stopTypingAnimation() {
18994      if (typingTimeout) {
18995        clearTimeout(typingTimeout);
18996        typingTimeout = null;
18997      }
18998    }
18999
19000    // Set initial placeholder
19001    updatePlaceholder();
19002
19003    // Start typing animation for normal mode
19004    if (!isAIModeEnabled) {
19005      startTypingAnimation();
19006    }
19007
19008    function esc(str){
19009      if (str === null || str === undefined) return '';
19010      return String(str)
19011        .replace(/&/g,'&amp;')
19012        .replace(/</g,'&lt;')
19013        .replace(/>/g,'&gt;')
19014        .replace(/"/g,'&quot;')
19015        .replace(/'/g,'&#39;');
19016    }
19017
19018    function clearSuggest(){
19019      suggest.innerHTML = '';
19020      suggest.hidden = true;
19021      suggest.classList.remove('open', 'trending-mode');
19022      currentIndex = -1;
19023    }
19024
19025    // ==================== TRENDING FUNCTIONS ====================
19026
19027    async function fetchTrending() {
19028      if (trendingCache) return trendingCache;
19029      if (trendingLoading) return null;
19030
19031      trendingLoading = true;
19032
19033      try {
19034        const res = await fetch('/api/trending_searches.php?timeframe=7d&limit=8');
19035        const data = await res.json();
19036
19037        if (data.ok && Array.isArray(data.data)) {
19038          trendingCache = data.data;
19039          console.log('[Trending] Loaded', trendingCache.length, 'items');
19040          return trendingCache;
19041        }
19042      } catch (err) {
19043        console.error('[Trending] Fetch error:', err);
19044      } finally {
19045        trendingLoading = false;
19046      }
19047
19048      return null;
19049    }
19050
19051    async function fetchTrendingProducts() {
19052      if (trendingProductsCache) return trendingProductsCache;
19053      if (trendingProductsLoading) return null;
19054
19055      trendingProductsLoading = true;
19056
19057      try {
19058        const res = await fetch('/api/trending_products.php?timeframe=7d&limit=6');
19059        const data = await res.json();
19060
19061        if (data.ok && Array.isArray(data.data)) {
19062          trendingProductsCache = data.data;
19063          console.log('[Trending Products] Loaded', trendingProductsCache.length, 'items');
19064          return trendingProductsCache;
19065        }
19066      } catch (err) {
19067        console.error('[Trending Products] Fetch error:', err);
19068      } finally {
19069        trendingProductsLoading = false;
19070      }
19071
19072      return null;
19073    }
19074
19075    function renderTrendingSuggestions(trends, products) {
19076      if ((!trends || trends.length === 0) && (!products || products.length === 0)) {
19077        clearSuggest();
19078        return;
19079      }
19080
19081      let html = `<div class="trending-container">`;
19082
19083      // Left column: Trending searches
19084      if (trends && trends.length > 0) {
19085        html += `
19086          <div class="trending-column trending-searches">
19087            <div class="trending-header">
19088              <span class="trending-icon">🔥</span>
19089              <span class="trending-title">Pencarian Trending</span>
19090            </div>
19091        `;
19092
19093        html += trends.map((t, idx) => {
19094          const categoryHint = t.category_hint ? `<span class="trending-category">${esc(t.category_hint)}</span>` : '';
19095          return `
19096            <div class="trending-item" data-query="${esc(t.query)}" data-index="${idx}">
19097              <span class="trending-rank">
19098                <img src="/assets/img/uptrend.image" alt="trending" width="16" height="16" style="vertical-align: middle;">
19099              </span>
19100              <span class="trending-query">${esc(t.query)}</span>
19101              ${categoryHint}
19102            </div>
19103          `;
19104        }).join('');
19105
19106        html += `</div>`;
19107      }
19108
19109      // Right column: Trending products
19110      if (products && products.length > 0) {
19111        html += `
19112          <div class="trending-column trending-products">
19113            <div class="trending-header">
19114              <span class="trending-icon">⭐</span>
19115              <span class="trending-title">Produk Populer</span>
19116            </div>
19117        `;
19118
19119        html += products.map((p, idx) => {
19120          return `
19121            <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}">
19122              <img src="${esc(p.image)}" alt="${esc(p.name)}" class="trending-product-thumb">
19123              <div class="trending-product-info">
19124                <div class="trending-product-name">${esc(p.name)}</div>
19125                <div class="trending-product-price">${esc(p.price_label)}</div>
19126              </div>
19127            </div>
19128          `;
19129        }).join('');
19130
19131        html += `</div>`;
19132      }
19133
19134      html += `</div>`;
19135
19136      suggest.innerHTML = html;
19137      suggest.hidden = false;
19138      suggest.classList.add('open', 'trending-mode');
19139      suggest.style.display = 'block';
19140
19141      console.log('[Trending] Rendered', trends?.length || 0, 'searches and', products?.length || 0, 'products');
19142    }
19143
19144    function trackSearchClick(query, productId) {
19145      fetch('/api/track_search_click.php', {
19146        method: 'POST',
19147        headers: { 'Content-Type': 'application/json' },
19148        body: JSON.stringify({
19149          query: query,
19150          product_id: productId,
19151          click_type: 'suggestion'
19152        })
19153      }).catch(() => {}); // silent fail
19154    }
19155
19156    function trackTrendingClick(query) {
19157      fetch('/api/track_search_click.php', {
19158        method: 'POST',
19159        headers: { 'Content-Type': 'application/json' },
19160        body: JSON.stringify({
19161          query: query,
19162          click_type: 'trending'
19163        })
19164      }).catch(() => {});
19165    }
19166
19167    function trackTrendingProductClick(productId, query) {
19168      fetch('/api/track_search_click.php', {
19169        method: 'POST',
19170        headers: { 'Content-Type': 'application/json' },
19171        body: JSON.stringify({
19172          query: query || 'trending_product',
19173          product_id: productId,
19174          click_type: 'trending_product'
19175        })
19176      }).catch(() => {});
19177    }
19178
19179    function trackSearchSubmit(query) {
19180      fetch('/api/track_search_click.php', {
19181        method: 'POST',
19182        headers: { 'Content-Type': 'application/json' },
19183        body: JSON.stringify({
19184          query: query,
19185          click_type: 'submit'
19186        })
19187      }).catch(() => {});
19188    }
19189
19190    function logFinalSearch(query) {
19191      // Log the final search to analytics (not during typing)
19192      fetch('/api/search_products.php?q=' + encodeURIComponent(query) + '&log=1', {
19193        headers: { 'Accept': 'application/json' }
19194      }).catch(() => {});
19195    }
19196
19197    // ==================== END TRENDING FUNCTIONS ====================
19198
19199    function clearSuggest(){
19200      suggest.innerHTML = '';
19201      suggest.hidden = true;
19202      suggest.classList.remove('open', 'trending-mode');
19203      currentIndex = -1;
19204    }
19205
19206    // AI Toggle Handler
19207    aiToggleBtn.addEventListener('click', (e) => {
19208      e.preventDefault();
19209      e.stopPropagation();
19210
19211      isAIModeEnabled = !isAIModeEnabled;
19212
19213      if (isAIModeEnabled) {
19214        aiToggleBtn.classList.add('active');
19215        localStorage.setItem('plazait_ai_search_mode', 'enabled');
19216        updatePlaceholder(); // Update placeholder text
19217        stopTypingAnimation(); // Stop typing animation in AI mode
19218
19219        // Show AI prompt if there's a query
19220        const query = searchInput.value.trim();
19221        if (query.length >= MIN_LEN) {
19222          showAISearchPrompt(query);
19223        } else {
19224          showAIActivationMessage();
19225        }
19226      } else {
19227        aiToggleBtn.classList.remove('active');
19228        localStorage.setItem('plazait_ai_search_mode', 'disabled');
19229        updatePlaceholder(); // Update placeholder text
19230        startTypingAnimation(); // Start typing animation in normal mode
19231        clearSuggest();
19232
19233        // Switch to normal search if there's a query
19234        const query = searchInput.value.trim();
19235        if (query.length >= MIN_LEN) {
19236          performSearch(query);
19237        }
19238      }
19239    });
19240
19241    function showAIActivationMessage() {
19242      suggest.innerHTML = `
19243        <div class="ai-progress-container">
19244          <div class="ai-progress-header">
19245            <div class="ai-progress-icon">✨</div>
19246            <div class="ai-progress-text">
19247              <div class="ai-progress-title">AI Search Mode Activated!</div>
19248              <div class="ai-progress-subtitle">Ketik query Anda untuk pencarian cerdas</div>
19249            </div>
19250          </div>
19251        </div>
19252      `;
19253      suggest.hidden = false;
19254      suggest.classList.add('open');
19255      suggest.style.display = 'block';
19256    }
19257
19258    function renderAIInsights(insights){
19259      if (!insights) return '';
19260
19261      // Generate AI summary via backend endpoint
19262      const summary = insights.ai_summary || generateAISummary(insights);
19263
19264      if (!summary) return '';
19265
19266      return `
19267        <div class="ai-result-banner">
19268          <div class="ai-result-icon">✨</div>
19269          <div class="ai-result-text">
19270            <div class="ai-result-title">AI Assistant</div>
19271            <div class="ai-result-description">${summary}</div>
19272          </div>
19273        </div>
19274      `;
19275    }
19276
19277    function generateAISummary(insights) {
19278      // Fallback client-side summary generation if backend doesn't provide ai_summary
19279      const productName = insights.product_name || '';
19280      const categoryPath = insights.category_path || '';
19281      const priceRange = insights.price_range || {};
19282      const intent = insights.intent || '';
19283
19284      let summary = '';
19285
19286      // Parse category for natural display
19287      let categoryDisplay = '';
19288      if (categoryPath) {
19289        const categoryParts = categoryPath.split('/');
19290        categoryDisplay = categoryParts[categoryParts.length - 1] || categoryParts[categoryParts.length - 2] || categoryPath;
19291      }
19292
19293      // Intent-based opening
19294      if (intent === 'cek_stok') {
19295        summary = `Berikut ketersediaan stok untuk <strong>${esc(productName)}</strong>. `;
19296      } else if (intent === 'rekomendasi_produk') {
19297        summary = `Berikut rekomendasi produk <strong>${esc(categoryDisplay || productName)}</strong> yang kami pilihkan untuk Anda. `;
19298      } else {
19299        // Default: cari_harga or general
19300        if (productName && categoryDisplay) {
19301          summary = `Kami menemukan <strong>${esc(productName)}</strong> di kategori ${esc(categoryDisplay)}`;
19302        } else if (productName) {
19303          summary = `Kami menemukan produk <strong>${esc(productName)}</strong>`;
19304        } else if (categoryDisplay) {
19305          summary = `Kami menemukan produk ${esc(categoryDisplay)}`;
19306        }
19307
19308        // Add price context
19309        if (priceRange.min > 0 && priceRange.max > 0) {
19310          const minPrice = new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(priceRange.min);
19311          const maxPrice = new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(priceRange.max);
19312          summary += ` dengan kisaran harga ${minPrice} - ${maxPrice}`;
19313        } else if (priceRange.min > 0) {
19314          const minPrice = new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(priceRange.min);
19315          summary += ` dengan harga mulai ${minPrice}`;
19316        } else if (priceRange.max > 0) {
19317          const maxPrice = new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(priceRange.max);
19318          summary += ` dengan harga maksimal ${maxPrice}`;
19319        }
19320
19321        summary += ' yang sesuai dengan kebutuhan Anda.';
19322      }
19323
19324      return summary || esc(insights.explanation || '');
19325    }
19326
19327    function showAIProgressSteps() {
19328      return `
19329        <div class="ai-progress-container">
19330          <div class="ai-progress-header">
19331            <div class="ai-progress-icon">🤖</div>
19332            <div class="ai-progress-text">
19333              <div class="ai-progress-title">AI sedang menganalisis...</div>
19334              <div class="ai-progress-subtitle">Memproses query Anda dengan kecerdasan buatan</div>
19335            </div>
19336          </div>
19337          <div class="ai-progress-steps">
19338            <div class="ai-progress-step active">
19339              <div class="ai-step-icon">1</div>
19340              <div class="ai-step-text">Memahami maksud pencarian...</div>
19341            </div>
19342            <div class="ai-progress-step pending">
19343              <div class="ai-step-icon">2</div>
19344              <div class="ai-step-text">Mengekstrak parameter pencarian...</div>
19345            </div>
19346            <div class="ai-progress-step pending">
19347              <div class="ai-step-icon">3</div>
19348              <div class="ai-step-text">Mencari produk yang relevan...</div>
19349            </div>
19350          </div>
19351        </div>
19352      `;
19353    }
19354
19355    function updateAIProgressStep(step) {
19356      const steps = suggest.querySelectorAll('.ai-progress-step');
19357
19358      steps.forEach((stepEl, idx) => {
19359        const icon = stepEl.querySelector('.ai-step-icon');
19360
19361        if (idx < step) {
19362          stepEl.classList.remove('active', 'pending');
19363          stepEl.classList.add('completed');
19364          icon.textContent = '✓';
19365        } else if (idx === step) {
19366          stepEl.classList.remove('pending', 'completed');
19367          stepEl.classList.add('active');
19368        }
19369      });
19370    }
19371
19372    function renderSuggest(items, aiInsights){
19373      if (!items.length){
19374        clearSuggest();
19375        return;
19376      }
19377
19378      let html = '';
19379
19380      // Show AI insights if available
19381      if (aiInsights && aiInsights.explanation) {
19382        html += renderAIInsights(aiInsights);
19383      }
19384
19385      // Section header for products with hint
19386      html += `
19387        <div class="suggest-section-header">
19388          <div class="suggest-header-main">
19389            <span class="suggest-header-icon">📦</span>
19390            <span class="suggest-header-text">Produk yang ditemukan (${items.length})</span>
19391          </div>
19392          <div class="suggest-header-hint">Tekan <kbd>Enter</kbd> untuk lihat semua hasil</div>
19393        </div>
19394      `;
19395
19396      // Render product items
19397      html += items.map((it, idx)=>{
19398        // Normalisasi & fallback
19399        const id    = it.id != null ? it.id : '';
19400        const name  = esc(it.name);
19401        const slug  = esc(it.slug != null ? it.slug : it.id);
19402        const img   = esc(it.image || '/assets/img/no-image.png');
19403        const price = esc(it.price_label || '');
19404
19405       const url = '/product/' + encodeURIComponent(slug);
19406
19407        return `
19408          <div class="suggest-item" data-index="${idx}" data-url="${url}" data-id="${esc(id)}">
19409            <img src="${img}" alt="${name}" class="suggest-thumb">
19410            <div class="suggest-meta">
19411              <div class="suggest-name">${name}</div>
19412              <div class="suggest-price">${price}</div>
19413            </div>
19414          </div>
19415        `;
19416      }).join('');
19417
19418      // Footer hint for more results - only show if 10 items (means there might be more)
19419      if (items.length >= 10) {
19420        html += `
19421          <div class="suggest-footer-hint">
19422            <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="vertical-align: -3px; margin-right: 6px;">
19423              <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"/>
19424            </svg>
19425            <span>Lihat hasil lengkap dengan menekan <kbd>Enter</kbd> atau <a href="#" class="suggest-footer-link" id="footerSearchLink">klik di sini</a></span>
19426          </div>
19427        `;
19428      }
19429
19430      suggest.innerHTML = html;
19431      suggest.hidden = false;
19432      suggest.classList.add('open');
19433      suggest.style.display = 'block';
19434      suggest.style.visibility = 'visible';
19435      suggest.style.opacity = '1';
19436    }
19437
19438    function fetchRemote(q, shouldLog = false){
19439      // Cancel previous request if any
19440      if (currentAbortController) {
19441        currentAbortController.abort();
19442      }
19443      currentAbortController = new AbortController();
19444
19445      // Always use standard search for normal mode
19446      // Only add log=1 parameter when shouldLog is true (intentional search)
19447      const logParam = shouldLog ? '&log=1' : '';
19448      const url = '/api/search_products.php?q=' + encodeURIComponent(q) + logParam;
19449      const fetchOptions = {
19450        headers: {'Accept':'application/json'},
19451        signal: currentAbortController.signal
19452      };
19453
19454      return fetch(url, fetchOptions)
19455        .then(r=>r.json())
19456        .then(j=>{
19457          if (j.ok && Array.isArray(j.data)) {
19458            return j;
19459          }
19460          return { ok: false, data: [] };
19461        })
19462        .catch(err=>{
19463          if (err.name === 'AbortError') {
19464            return { ok: false, data: [] };
19465          }
19466          return { ok:false, data:[] };
19467        });
19468    }
19469
19470    function showNoResults(q, aiInsights) {
19471      suggest.innerHTML = `
19472        <div style="padding: 32px 24px; text-align: center; color: #64748b;">
19473          <div style="font-size: 56px; margin-bottom: 16px; opacity: 0.6;">🔍</div>
19474          <div style="font-size: 16px; font-weight: 600; color: #334155; margin-bottom: 8px;">
19475            Tidak ada hasil untuk "${esc(q)}"
19476          </div>
19477          ${aiInsights && aiInsights.explanation ? `
19478            <div style="font-size: 13px; margin: 16px auto; padding: 12px 16px; background: #f1f5f9; border-radius: 8px; max-width: 400px; color: #475569;">
19479              <strong style="color: #667eea;">🤖 AI memahami:</strong><br>
19480              ${esc(aiInsights.explanation)}
19481            </div>
19482          ` : ''}
19483          <div style="font-size: 13px; margin-top: 16px; opacity: 0.8;">
19484            Coba kata kunci lain atau hubungi kami untuk bantuan
19485          </div>
19486        </div>
19487      `;
19488      suggest.hidden = false;
19489      suggest.classList.add('open');
19490      suggest.style.display = 'block';
19491    }
19492
19493    function showErrorMessage() {
19494      suggest.innerHTML = `
19495        <div style="padding: 32px 24px; text-align: center; color: #dc2626;">
19496          <div style="font-size: 48px; margin-bottom: 12px;">⚠️</div>
19497          <div style="font-size: 14px; font-weight: 600; margin-bottom: 8px;">
19498            Pencarian tidak tersedia
19499          </div>
19500          <div style="font-size: 12px; opacity: 0.8;">
19501            Silakan coba lagi dalam beberapa saat
19502          </div>
19503        </div>
19504      `;
19505      suggest.hidden = false;
19506      suggest.classList.add('open');
19507      suggest.style.display = 'block';
19508    }
19509
19510    function showAIErrorState(q) {
19511      suggest.innerHTML = `
19512        <div class="ai-progress-container">
19513          <div class="ai-progress-header">
19514            <div class="ai-progress-icon" style="background: linear-gradient(135deg, #f59e0b 0%, #f97316 100%);">⚡</div>
19515            <div class="ai-progress-text">
19516              <div class="ai-progress-title">AI sedang memproses...</div>
19517              <div class="ai-progress-subtitle">Menganalisis query: "${esc(q)}"</div>
19518            </div>
19519          </div>
19520          <div style="padding: 16px; margin-top: 12px; background: #fef3c7; border-radius: 8px; border-left: 3px solid #f59e0b;">
19521            <div style="font-size: 13px; color: #92400e; line-height: 1.6;">
19522              <strong style="display: block; margin-bottom: 4px;">💡 Sedang mencari produk terbaik untuk Anda</strong>
19523              AI kami sedang menganalisis database untuk menemukan hasil yang paling relevan.
19524              Mohon tunggu sebentar...
19525            </div>
19526          </div>
19527          <div style="padding: 12px 16px; margin-top: 12px; text-align: center;">
19528            <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;">
19529              🔄 Refresh & Coba Lagi
19530            </button>
19531          </div>
19532        </div>
19533      `;
19534      suggest.hidden = false;
19535      suggest.classList.add('open');
19536      suggest.style.display = 'block';
19537    }
19538
19539    function triggerAISearch(q) {
19540      lastQuery = q; // Update lastQuery to prevent re-triggering
19541      isAIProcessing = true; // Set flag to prevent suggest box from closing
19542
19543      // Show AI progress UI - keep suggest box visible
19544      suggest.innerHTML = showAIProgressSteps();
19545      suggest.hidden = false;
19546      suggest.classList.add('open');
19547      suggest.style.display = 'block';
19548
19549      setTimeout(() => updateAIProgressStep(1), 1000);
19550      setTimeout(() => updateAIProgressStep(2), 2500);
19551
19552      // Cancel previous request if any
19553      if (currentAbortController) {
19554        currentAbortController.abort();
19555      }
19556      currentAbortController = new AbortController();
19557
19558      const url = '/api/search_ai.php?q=' + encodeURIComponent(q);
19559      const fetchOptions = {
19560        headers: {'Accept':'application/json'},
19561        signal: currentAbortController.signal
19562      };
19563
19564      // Add timeout
19565      const timeoutId = setTimeout(() => {
19566        currentAbortController.abort();
19567
19568        // Show fallback loading state
19569        suggest.innerHTML = `
19570          <div class="ai-progress-container">
19571            <div class="ai-progress-header">
19572              <div class="ai-progress-icon">⏳</div>
19573              <div class="ai-progress-text">
19574                <div class="ai-progress-title">AI timeout - switching to standard search</div>
19575                <div class="ai-progress-subtitle">Mencari dengan metode tradisional...</div>
19576              </div>
19577            </div>
19578          </div>
19579        `;
19580
19581        // Fallback to traditional search
19582        fetch('/api/search_products.php?q=' + encodeURIComponent(q), {
19583          headers: { 'Accept': 'application/json' }
19584        })
19585          .then(r => r.json())
19586          .then(j => {
19587            isAIProcessing = false; // Reset flag
19588            if (j.ok && Array.isArray(j.data) && j.data.length) {
19589              renderSuggest(j.data, null);
19590            } else {
19591              showNoResults(q, null);
19592            }
19593          })
19594          .catch(() => {
19595            isAIProcessing = false; // Reset flag
19596            showAIErrorState(q);
19597          });
19598      }, AI_TIMEOUT);
19599
19600      fetch(url, fetchOptions)
19601        .then(r => r.json())
19602        .then(j => {
19603          clearTimeout(timeoutId);
19604          isAIProcessing = false; // Reset flag when done
19605
19606          if (j.ok && Array.isArray(j.data)) {
19607            if (j.data.length > 0) {
19608              renderSuggest(j.data, j.ai_insights || null);
19609            } else {
19610              showNoResults(q, j.ai_insights || null);
19611            }
19612          } else {
19613            showAIErrorState(q);
19614          }
19615        })
19616        .catch(err => {
19617          clearTimeout(timeoutId);
19618          isAIProcessing = false; // Reset flag on error
19619          if (err.name !== 'AbortError') {
19620            showAIErrorState(q);
19621          }
19622        });
19623    }
19624
19625    function performSearch(q){
19626      if (q.length < MIN_LEN){
19627        clearSuggest();
19628        return;
19629      }
19630      if (q === lastQuery) return;
19631      lastQuery = q;
19632
19633      // Normal mode only - AI mode uses triggerAISearch()
19634      fetchRemote(q).then(j=>{
19635        if (j.ok && Array.isArray(j.data)){
19636          if (j.data.length > 0) {
19637            renderSuggest(j.data, null);
19638          } else {
19639            showNoResults(q, null);
19640          }
19641        } else {
19642          clearSuggest();
19643        }
19644      }).catch(() => {
19645        clearSuggest();
19646      });
19647    }
19648
19649    function debounce(){
19650      const q = searchInput.value.trim();
19651      clearTimeout(debounceTimer);
19652
19653      // If empty, show trending suggestions and products
19654      if (q.length === 0) {
19655        debounceTimer = setTimeout(async () => {
19656          const trends = trendingCache || await fetchTrending();
19657          const products = trendingProductsCache || await fetchTrendingProducts();
19658          if ((trends && trends.length > 0) || (products && products.length > 0)) {
19659            renderTrendingSuggestions(trends, products);
19660          } else {
19661            clearSuggest();
19662          }
19663        }, 100);
19664        return;
19665      }
19666
19667      // For AI mode, just show search prompt (no auto search)
19668      if (isAIModeEnabled && q.length >= MIN_LEN) {
19669        showAISearchPrompt(q);
19670      } else if (!isAIModeEnabled && q.length >= MIN_LEN) {
19671        // Normal mode: auto search with debounce
19672        debounceTimer = setTimeout(()=> performSearch(q), DEBOUNCE);
19673      } else {
19674        clearSuggest();
19675      }
19676    }
19677
19678    function showAISearchPrompt(q) {
19679      suggest.innerHTML = `
19680        <div class="ai-search-prompt">
19681          <div class="ai-prompt-header">
19682            <div class="ai-prompt-icon">🤖</div>
19683            <div class="ai-prompt-text">
19684              <div class="ai-prompt-title">Pencarian AI Siap!</div>
19685              <div class="ai-prompt-subtitle">Klik tombol di bawah untuk mencari dengan AI</div>
19686            </div>
19687          </div>
19688          <div class="ai-prompt-query">
19689            <div class="ai-query-label">
19689Query Anda:</div>
19690            <div class="ai-query-text">"${esc(q)}"</div>
19691          </div>
19692          <button class="ai-search-trigger-btn" data-query="${esc(q)}">
19693            <span class="ai-btn-icon">✨</span>
19694            <span class="ai-btn-text">Cari dengan AI</span>
19695          </button>
19696          <div class="ai-prompt-footer">
19697            AI akan menganalisis query Anda untuk hasil yang lebih relevan
19698          </div>
19699        </div>
19700      `;
19701      suggest.hidden = false;
19702      suggest.classList.add('open');
19703      suggest.style.display = 'block';
19704    }
19705
19706    function moveHighlight(dir){
19707      if (suggest.hidden) return;
19708      const items = suggest.querySelectorAll('.suggest-item');
19709      if (!items.length) return;
19710      currentIndex += dir;
19711      if (currentIndex < 0) currentIndex = items.length - 1;
19712      if (currentIndex >= items.length) currentIndex = 0;
19713      items.forEach(it=> it.classList.remove('active'));
19714      const active = items[currentIndex];
19715      if (active){
19716        active.classList.add('active');
19717        active.scrollIntoView({block:'nearest'});
19718      }
19719    }
19720
19721    function activate(){
19722      const active = suggest.querySelector('.suggest-item.active');
19723      if (!active) return;
19724      window.location.href = active.getAttribute('data-url');
19725    }
19726
19727    searchInput.addEventListener('input', debounce);
19728
19729    // Stop typing animation when focused & load trending if empty
19730    searchInput.addEventListener('focus', async () => {
19731      stopTypingAnimation();
19732
19733      const q = searchInput.value.trim();
19734
19735      // If empty, show trending suggestions and products
19736      if (q.length === 0) {
19737        const trends = trendingCache || await fetchTrending();
19738        const products = trendingProductsCache || await fetchTrendingProducts();
19739        if ((trends && trends.length > 0) || (products && products.length > 0)) {
19740          renderTrendingSuggestions(trends, products);
19741        }
19742        return;
19743      }
19744
19745      // If AI mode and has query, show AI prompt
19746      if (isAIModeEnabled && q.length >= MIN_LEN && suggest.hidden) {
19747        showAISearchPrompt(q);
19748      }
19749    });
19750
19751    // Resume typing animation when blurred (if suggest is closed)
19752    searchInput.addEventListener('blur', () => {
19753      // Add delay to allow click events to fire first
19754      setTimeout(() => {
19755        if (!isAIModeEnabled && suggest.hidden && searchInput.value.trim() === '') {
19756          startTypingAnimation();
19757        }
19758      }, 200);
19759    });
19760
19761    searchInput.addEventListener('keydown', e=>{
19762      switch(e.key){
19763        case 'ArrowDown': moveHighlight(1); e.preventDefault(); break;
19764        case 'ArrowUp': moveHighlight(-1); e.preventDefault(); break;
19765        case 'Enter':
19766          if (!suggest.hidden){
19767            activate();
19768            e.preventDefault();
19769          } else {
19770            // Direct enter without suggestion - track search submit and log final search
19771            const q = searchInput.value.trim();
19772            if (q.length >= MIN_LEN) {
19773              trackSearchSubmit(q);
19774              logFinalSearch(q);
19775            }
19776          }
19777          break;
19778        case 'Escape': clearSuggest(); break;
19779      }
19780    });
19781
19782    suggest.addEventListener('click', e=>{
19783      // Check if footer link clicked
19784      const footerLink = e.target.closest('#footerSearchLink');
19785      if (footerLink) {
19786        e.preventDefault();
19787        const q = searchInput.value.trim();
19788        if (q.length >= MIN_LEN) {
19789          trackSearchSubmit(q);
19790          logFinalSearch(q);
19791          window.location.href = '/product/search/?q=' + encodeURIComponent(q);
19792        }
19793        return;
19794      }
19795
19796      // Check if AI search button clicked
19797      const aiBtn = e.target.closest('.ai-search-trigger-btn');
19798      if (aiBtn) {
19799        const query = aiBtn.getAttribute('data-query');
19800        if (query) {
19801          triggerAISearch(query);
19802        }
19803        return;
19804      }
19805
19806      // Handle trending item click
19807      const trendingItem = e.target.closest('.trending-item');
19808      if (trendingItem) {
19809        const query = trendingItem.getAttribute('data-query');
19810        if (query) {
19811          searchInput.value = query;
19812          trackTrendingClick(query);
19813          logFinalSearch(query); // Log final search when user clicks trending
19814          performSearch(query);
19815        }
19816        return;
19817      }
19818
19819      // Handle trending product click
19820      const trendingProduct = e.target.closest('.trending-product-item');
19821      if (trendingProduct) {
19822        const productId = trendingProduct.getAttribute('data-product-id');
19823        const slug = trendingProduct.getAttribute('data-slug');
19824        const query = trendingProduct.getAttribute('data-query');
19825
19826        if (productId && slug) {
19827          trackTrendingProductClick(productId, query);
19828          window.location.href = '/product/' + encodeURIComponent(slug);
19829        }
19830        return;
19831      }
19832
19833      // Handle product item click
19834      const it = e.target.closest('.suggest-item');
19835      if (!it) return;
19836
19837      const productId = it.getAttribute('data-id');
19838      trackSearchClick(lastQuery, productId);
19839      logFinalSearch(lastQuery); // Log final search when user clicks on suggestion
19840
19841      window.location.href = it.getAttribute('data-url');
19842    });
19843
19844    document.addEventListener('click', e=>{
19845      // Don't close suggest box if AI is processing
19846      if (isAIProcessing) return;
19847
19848      if (e.target === searchInput || e.target === aiToggleBtn ||
19849          suggest.contains(e.target) || aiToggleBtn.contains(e.target)) return;
19850      clearSuggest();
19851    });
19852
19853    if (searchBtn){
19854      searchBtn.addEventListener('click', ()=>{
19855        const q = searchInput.value.trim();
19856        if (q.length >= MIN_LEN){
19857          // Track search submit and log final search
19858          trackSearchSubmit(q);
19859          logFinalSearch(q);
19860          window.location.href = '/product/search/?q=' + encodeURIComponent(q);
19861        }
19862      });
19863    }
19864
19865    // Pastikan style dasar muncul
19866    if (!suggest.classList.contains('wired')) {
19867      suggest.classList.add('wired');
19868      suggest.style.position = 'absolute';
19869      suggest.style.zIndex = '9999';
19870      suggest.style.background = '#fff';
19871      suggest.style.border = '1px solid #ccc';
19872      suggest.style.boxShadow = '0 4px 12px rgba(0,0,0,0.12)';
19873      suggest.style.maxHeight = '360px';
19874      suggest.style.overflowY = 'auto';
19875    }
19876
19877    return true;
19878  }
19879
19880  if (!setup()) {
19881    if (document.readyState === 'loading') {
19882      document.addEventListener('DOMContentLoaded', setup);
19883    } else {
19884      setTimeout(setup, 80);
19885    }
19886  }
19887})();
19888</script>
vendor: 2 bytes, line 19888
19888
19889<script data-inlined="site-notice.js">
19890/**
19891 * Site Notice Banner - Interactive Behavior
19892 * Handles dismiss functionality with localStorage persistence
19893 * Supports dynamic notice IDs for different campaigns
19894 */
19895
19896(function() {
19897    'use strict';
19898
19899    const STORAGE_KEY = 'plazait_site_notice_dismissed';
19900
19901    const banner = document.getElementById('holidayNotice');
19902    const closeBtn = document.getElementById('holidayNoticeClose');
19903
19904    if (!banner || !closeBtn) return;
19905
19906    // Get notice ID from data attribute
19907    const noticeId = banner.dataset.noticeId;
19908    if (!noticeId) {
19909        console.warn('Notice ID not found. Banner will not persist dismiss state.');
19910    }
19911
19912    /**
19913     * Check if banner was previously dismissed
19914     */
19915    function wasDismissed() {
19916        if (!noticeId) return false;
19917
19918        try {
19919            const dismissed = localStorage.getItem(STORAGE_KEY);
19920            return dismissed === noticeId;
19921        } catch (e) {
19922            console.warn('localStorage not available:', e);
19923            return false;
19924        }
19925    }
19926
19927    /**
19928     * Mark banner as dismissed
19929     */
19930    function markDismissed() {
19931        if (!noticeId) return;
19932
19933        try {
19934            localStorage.setItem(STORAGE_KEY, noticeId);
19935        } catch (e) {
19936            console.warn('Could not save to localStorage:', e);
19937        }
19938    }
19939
19940    /**
19941     * Hide banner with animation
19942     */
19943    function hideBanner() {
19944        banner.classList.add('hidden');
19945        document.body.classList.remove('has-holiday-notice');
19946
19947        // Remove from DOM after animation
19948        setTimeout(() => {
19949            banner.remove();
19950        }, 300);
19951
19952        markDismissed();
19953
19954        // Analytics tracking (optional)
19955        if (window.gtag) {
19956            window.gtag('event', 'banner_dismiss', {
19957                'event_category': 'Site Notice',
19958                'event_label': noticeId || 'unknown',
19959            });
19960        }
19961    }
19962
19963    /**
19964     * Initialize banner
19965     */
19966    function init() {
19967        // Check if already dismissed
19968        if (wasDismissed()) {
19969            banner.remove();
19970            return;
19971        }
19972
19973        // Add body class for layout adjustment
19974        document.body.classList.add('has-holiday-notice');
19975
19976        // Close button click
19977        closeBtn.addEventListener('click', hideBanner);
19978
19979        // Keyboard support for close button
19980        closeBtn.addEventListener('keydown', (e) => {
19981            if (e.key === 'Enter' || e.key === ' ') {
19982                e.preventDefault();
19983                hideBanner();
19984            }
19985        });
19986
19987        // Optional: Auto-hide after certain time (disabled by default)
19988        // setTimeout(hideBanner, 30000); // 30 seconds
19989    }
19990
19991    // Initialize when DOM is ready
19992    if (document.readyState === 'loading') {
19993        document.addEventListener('DOMContentLoaded', init);
19994    } else {
19995        init();
19996    }
19997
19998})();
19999
20000</script>
vendor: 2 bytes, line 20000
20000
20001<script data-inlined="guest-cart.js">
20002/**
20003 * Guest Cart Manager - localStorage based cart for guest users
20004 *
20005 * Manages shopping cart items in browser localStorage for users who are not logged in.
20006 * Provides seamless transition to database when user logs in.
20007 */
20008
20009class GuestCart {
20010    constructor() {
20011        this.storageKey = 'plazait_guest_cart';
20012        this.apiBase = '/api/cart';
20013
20014        // Initialize cart structure
20015        this.cart = this.loadFromStorage();
20016
20017        // Bind methods
20018        this.add = this.add.bind(this);
20019        this.remove = this.remove.bind(this);
20020        this.update = this.update.bind(this);
20021        this.clear = this.clear.bind(this);
20022        this.getItems = this.getItems.bind(this);
20023        this.mergeToDatabase = this.mergeToDatabase.bind(this);
20024    }
20025
20026    /**
20027     * Load cart from localStorage
20028     */
20029    loadFromStorage() {
20030        try {
20031            const stored = localStorage.getItem(this.storageKey);
20032            if (stored) {
20033                const parsed = JSON.parse(stored);
20034                if (parsed && typeof parsed === 'object' && parsed.items) {
20035                    return parsed;
20036                }
20037            }
20038        } catch (error) {
20039            // Silent error handling
20040        }
20041
20042        return {
20043            items: {},
20044            updated_at: Date.now(),
20045            version: '1.0'
20046        };
20047    }
20048
20049    /**
20050     * Save cart to localStorage
20051     */
20052    saveToStorage() {
20053        try {
20054            this.cart.updated_at = Date.now();
20055            localStorage.setItem(this.storageKey, JSON.stringify(this.cart));
20056
20057            // Dispatch custom event for cart updates
20058            window.dispatchEvent(new CustomEvent('guestCartUpdated', {
20059                detail: {
20060                    itemsCount: this.getItemsCount(),
20061                    linesCount: this.getLinesCount(),
20062                    items: this.cart.items
20063                }
20064            }));
20065
20066            return true;
20067        } catch (error) {
20068            return false;
20069        }
20070    }
20071
20072    /**
20073     * Generate cart item key
20074     */
20075    generateKey(productId, variantId = 0) {
20076        return `p${productId}v${variantId}`;
20077    }
20078
20079    /**
20080     * Add item to guest cart
20081     */
20082    add(productId, qty = 1, variantId = 0, meta = {}) {
20083        const key = this.generateKey(productId, variantId);
20084
20085        // Validate inputs
20086        if (!productId || productId <= 0 || !qty || qty <= 0) {
20087            throw new Error('Invalid product ID or quantity');
20088        }
20089
20090        qty = Math.max(1, Math.min(999, parseInt(qty)));
20091
20092        // Initialize item if not exists
20093        if (!this.cart.items[key]) {
20094            this.cart.items[key] = {
20095                pid: parseInt(productId),
20096                vid: parseInt(variantId) || 0,
20097                qty: 0,
20098                name: meta.name || '',
20099                sku: meta.sku || '',
20100                price: parseInt(meta.price) || 0,
20101                image_url: meta.image_url || '',
20102                product_url: meta.product_url || '/product/' + parseInt(productId),
20103                added_at: Date.now()
20104            };
20105        }
20106
20107        // Update quantity
20108        this.cart.items[key].qty = Math.max(1, Math.min(999, this.cart.items[key].qty + qty));
20109
20110        // Update metadata if provided
20111        if (meta.name) this.cart.items[key].name = meta.name;
20112        if (meta.sku) this.cart.items[key].sku = meta.sku;
20113        if (meta.price) this.cart.items[key].price = parseInt(meta.price);
20114        if (meta.image_url) this.cart.items[key].image_url = meta.image_url;
20115        if (meta.product_url) this.cart.items[key].product_url = meta.product_url;
20116
20117        this.saveToStorage();
20118
20119        return {
20120            key,
20121            item: this.cart.items[key],
20122            itemsCount: this.getItemsCount(),
20123            linesCount: this.getLinesCount()
20124        };
20125    }
20126
20127    /**
20128     * Remove item from guest cart
20129     */
20130    remove(productId, variantId = 0) {
20131        const key = this.generateKey(productId, variantId);
20132
20133        if (this.cart.items[key]) {
20134            delete this.cart.items[key];
20135            this.saveToStorage();
20136
20137            return true;
20138        }
20139
20140        return false;
20141    }
20142
20143    /**
20144     * Update item quantity
20145     */
20146    update(productId, qty, variantId = 0) {
20147        const key = this.generateKey(productId, variantId);
20148
20149        if (!this.cart.items[key]) {
20150            return false;
20151        }
20152
20153        qty = parseInt(qty);
20154        if (qty <= 0) {
20155            return this.remove(productId, variantId);
20156        }
20157
20158        this.cart.items[key].qty = Math.max(1, Math.min(999, qty));
20159        this.saveToStorage();
20160
20161        return true;
20162    }
20163
20164    /**
20165     * Clear entire cart
20166     */
20167    clear() {
20168        this.cart = {
20169            items: {},
20170            updated_at: Date.now(),
20171            version: '1.0'
20172        };
20173
20174        this.saveToStorage();
20175
20176        return true;
20177    }
20178
20179    /**
20180     * Get all cart items
20181     */
20182    getItems() {
20183        return this.cart.items;
20184    }
20185
20186    /**
20187     * Get cart items as array
20188     */
20189    getItemsArray() {
20190        return Object.values(this.cart.items);
20191    }
20192
20193    /**
20194     * Get total items count (sum of quantities)
20195     */
20196    getItemsCount() {
20197        return Object.values(this.cart.items).reduce((total, item) => total + (item.qty || 0), 0);
20198    }
20199
20200    /**
20201     * Get lines count (number of different items)
20202     */
20203    getLinesCount() {
20204        return Object.keys(this.cart.items).length;
20205    }
20206
20207    /**
20208     * Get cart summary
20209     */
20210    getSummary() {
20211        const itemsCount = this.getItemsCount();
20212        const linesCount = this.getLinesCount();
20213        const totalValue = Object.values(this.cart.items).reduce(
20214            (total, item) => total + ((item.qty || 0) * (item.price || 0)), 0
20215        );
20216
20217        return {
20218            itemsCount,
20219            linesCount,
20220            totalValue,
20221            updatedAt: this.cart.updated_at
20222        };
20223    }
20224
20225    /**
20226     * Check if cart has items
20227     */
20228    isEmpty() {
20229        return this.getLinesCount() === 0;
20230    }
20231
20232    /**
20233     * Merge guest cart to database after user login
20234     */
20235    async mergeToDatabase() {
20236        const items = this.cart.items;
20237
20238        if (Object.keys(items).length === 0) {
20239            return { ok: true, merged_count: 0, message: 'No items to merge' };
20240        }
20241
20242        try {
20243            // Use new endpoint that handles localStorage format
20244            const response = await fetch(`${this.apiBase}/merge-localStorage.php`, {
20245                method: 'POST',
20246                headers: {
20247                    'Content-Type': 'application/json',
20248                },
20249                credentials: 'same-origin',
20250                body: JSON.stringify({
20251                    items: items  // Send localStorage format directly
20252                })
20253            });
20254
20255            const result = await response.json();
20256
20257            if (result.ok) {
20258                // Clear localStorage cart after successful merge
20259                this.clear();
20260
20261                // Update cart counter
20262                if (window.updateCartCounter) {
20263                    try {
20264                        await window.updateCartCounter();
20265                    } catch (e) {
20266                        // Silent error handling
20267                    }
20268                }
20269
20270                // Dispatch merge completion event
20271                window.dispatchEvent(new CustomEvent('guestCartMerged', {
20272                    detail: result
20273                }));
20274
20275                return result;
20276            } else {
20277                throw new Error(result.error || 'Failed to merge cart');
20278            }
20279
20280        } catch (error) {
20281            throw error;
20282        }
20283    }
20284
20285    /**
20286     * Export cart data (for debugging or backup)
20287     */
20288    export() {
20289        return {
20290            ...this.cart,
20291            summary: this.getSummary()
20292        };
20293    }
20294
20295    /**
20296     * Import cart data (for restore or testing)
20297     */
20298    import(cartData) {
20299        if (cartData && typeof cartData === 'object' && cartData.items) {
20300            this.cart = {
20301                items: cartData.items,
20302                updated_at: cartData.updated_at || Date.now(),
20303                version: cartData.version || '1.0'
20304            };
20305
20306            this.saveToStorage();
20307            return true;
20308        }
20309
20310        return false;
20311    }
20312}
20313
20314// Create global instance
20315window.guestCart = new GuestCart();
20316
20317// Auto-merge on page load if user is logged in
20318document.addEventListener('DOMContentLoaded', async function() {
20319    // Multiple ways to check if user is logged in
20320    const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true' ||
20321                      document.body.classList.contains('logged-in') ||
20322                      window.userLoggedIn === true ||
20323                      document.body.dataset.userLoggedIn === 'true';
20324
20325    if (isLoggedIn && !window.guestCart.isEmpty()) {
20326        // Small delay to ensure page is fully loaded
20327        setTimeout(async () => {
20328            try {
20329                const result = await window.guestCart.mergeToDatabase();
20330            } catch (error) {
20331                // Silent error handling
20332            }
20333        }, 500);
20334    }
20335});
20336
20337// Also listen for login events (if login happens via AJAX)
20338window.addEventListener('userLoggedIn', async function() {
20339    if (!window.guestCart.isEmpty()) {
20340        try {
20341            await window.guestCart.mergeToDatabase();
20342        } catch (error) {
20343            // Silent error handling
20344        }
20345    }
20346});
20347</script>
vendor: 2 bytes, line 20347
20347
20348<script data-inlined="cart.js">
20349(function(){
20350  function $(s,ctx=document){ return ctx.querySelector(s); }
20351  function esc(s){ return String(s||'').replace(/[&<>"']/g, m=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;' }[m])); }
20352
20353  // CSRF token helper
20354  function getCsrfToken() {
20355    const metaTag = document.querySelector('meta[name="csrf-token"]');
20356    return metaTag ? metaTag.getAttribute('content') : '';
20357  }
20358
20359  function guessPdMeta(){
20360    const name = (window.PD_NAME !== undefined) ? String(window.PD_NAME) : ($('#pd-title')?.textContent || '');
20361    const sku  = (function(){ const el=$('#pd-sku'); return el ? el.textContent.trim() : (window.PD_SKU||''); })();
20362    // SECURITY FIX: Remove price from client-side metadata (will be validated server-side)
20363    const imgEl = $('#pd-main-img');
20364    const image_url = imgEl ? (imgEl.getAttribute('src') || '') : '';
20365    return { name, sku, image_url }; // price removed for security
20366  }
20367
20368  async function addToCart(productId, qty=1, variantId=0, metaOpt){
20369    const meta = Object.assign(guessPdMeta(), metaOpt||{});
20370    const payload = {
20371      product_id: parseInt(productId||0,10) || 0,
20372      qty: parseInt(qty||1,10) || 1,
20373      variant_id: parseInt(variantId||0,10) || 0,
20374      meta
20375    };
20376
20377    // Check if user is logged in
20378    const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true' ||
20379                      document.body.classList.contains('logged-in') ||
20380                      window.userLoggedIn === true ||
20381                      document.body.dataset.userLoggedIn === 'true';
20382
20383    // Skip localStorage untuk logged user - mereka pakai database
20384    if (!isLoggedIn && window.guestCart) {
20385      try {
20386        window.guestCart.add(payload.product_id, payload.qty, payload.variant_id, meta);
20387      } catch (e) {
20388        console.warn('Failed to add to localStorage cart:', e);
20389      }
20390    }
20391
20392    const res = await fetch('/api/cart/add.php', {
20393      method: 'POST',
20394      headers: {
20395        'Content-Type':'application/json',
20396        'X-CSRF-Token': getCsrfToken()
20397      },
20398      credentials: 'same-origin',
20399      body: JSON.stringify({
20400        ...payload,
20401        csrf_token: getCsrfToken()
20402      }),
20403    });
20404    const data = await res.json().catch(()=>({ ok:false, error:'Gagal menambahkan ke keranjang' }));
20405    if (!res.ok || !data.ok) {
20406      const msg = data && data.error ? data.error : 'Gagal menambahkan ke keranjang';
20407      throw new Error(msg);
20408    }
20409
20410    // Update badge di navbar dengan data dari response
20411    try {
20412      const el = document.querySelector('[data-role="cart-badge"]') || document.getElementById('cartCount');
20413      if (el && data.cart && typeof data.cart.items_count === 'number') {
20414        el.textContent = String(data.cart.items_count);
20415        el.classList.toggle('is-empty', data.cart.items_count <= 0);
20416      }
20417    } catch(_){}
20418
20419    // Siarkan event global (dipakai cart-badge.js dan halaman lain)
20420    try {
20421      document.dispatchEvent(new CustomEvent('cart:updated', { detail: data.cart || {} }));
20422    } catch(_){}
20423
20424    // Update CartBadge dengan data dari response (lebih reliable)
20425    try {
20426      if (window.CartBadge && window.CartBadge.set) {
20427        window.CartBadge.set(data.cart.items_count||0);
20428      }
20429    } catch(_){}
20430
20431    // Tambahan: refresh cart counter setelah delay kecil untuk memastikan data tersinkronisasi
20432    setTimeout(() => {
20433      if (window.updateCartCounter) {
20434        window.updateCartCounter();
20435      }
20436    }, 100);
20437
20438    return data;
20439  }
20440
20441  // Function to update cart counter from server
20442  async function updateCartCounter(){
20443    try {
20444      const res = await fetch('/api/cart/get.php', { credentials:'same-origin' });
20445      const data = await res.json().catch(()=>({ok:false}));
20446      if (data.ok && typeof data.items_count === 'number') {
20447        const el = document.querySelector('[data-role="cart-badge"]') || document.getElementById('cartCount');
20448        if (el) {
20449          el.textContent = String(data.items_count);
20450          el.classList.toggle('is-empty', data.items_count <= 0);
20451        }
20452        // Dispatch event
20453        try {
20454          document.dispatchEvent(new CustomEvent('cart:updated', { detail: { items_count: data.items_count } }));
20455        } catch(_){}
20456      }
20457    } catch (err) {
20458      console.log('Could not update cart counter:', err);
20459    }
20460  }
20461
20462  // Function to manually merge guest cart (if needed)
20463  async function mergeGuestCart(){
20464    try {
20465      const res = await fetch('/api/cart/merge-guest.php', {
20466        method: 'POST',
20467        headers: {
20468          'Content-Type': 'application/json',
20469          'X-CSRF-Token': getCsrfToken()
20470        },
20471        credentials: 'same-origin',
20472        body: JSON.stringify({ csrf_token: getCsrfToken() })
20473      });
20474      const data = await res.json().catch(()=>({ok:false}));
20475
20476      if (data.ok && data.merged_items > 0) {
20477        console.log(`Guest cart merged: ${data.merged_items} items`);
20478        // Update cart counter after merge
20479        await updateCartCounter();
20480        return data;
20481      }
20482      return data;
20483    } catch (err) {
20484      console.log('Could not merge guest cart:', err);
20485      return { ok: false, error: err.message };
20486    }
20487  }
20488
20489  window.addToCart = addToCart;
20490  window.updateCartCounter = updateCartCounter;
20491  window.mergeGuestCart = mergeGuestCart;
20492
20493  // Optional: auto-bind untuk tombol apapun yang punya data-addtocart
20494  document.addEventListener('click', async (e)=>{
20495    const btn = e.target.closest('[data-addtocart]');
20496    if (!btn) return;
20497    e.preventDefault();
20498    if (btn.disabled) return;
20499
20500    const pid = parseInt(btn.getAttribute('data-pid')||'0',10);
20501    const vid = parseInt(btn.getAttribute('data-vid')||'0',10);
20502    const qty = parseInt(btn.getAttribute('data-qty')||'1',10) || 1;
20503
20504    try {
20505      const data = await addToCart(pid, qty, vid);
20506      if (window.showToast) {
20507        const meta = guessPdMeta();
20508        window.showToast({
20509          type:'success',
20510          title:'Berhasil ditambahkan',
20511          message:`<strong>${esc(meta.name)}</strong>${meta.sku?` · <em>SKU: ${esc(meta.sku)}</em>`:''}<br>Qty: ${qty}`,
20512          duration: 3800,
20513          actions: [
20514            { label:'Lihat Keranjang', onClick: ()=> { window.location.href='/keranjang'; } },
20515            { label:'Lanjut Belanja', variant:'alt', onClick: ()=>{} }
20516          ]
20517        });
20518      }
20519    } catch (err) {
20520      if (window.showToast) {
20521        window.showToast({ type:'warn', title:'Gagal', message: String(err?.message||'Gagal menambahkan'), duration: 4200 });
20522      } else {
20523        console.error(err);
20524      }
20525    }
20526  });
20527})();
20528</script>
vendor: 2 bytes, line 20528
20528
20529<script data-inlined="mega-menu-tiles.js">
20530// Tile Mega Menu — Enhanced 2025 (Stagger animations, product counts, loading states)
20531// Includes safe-area hover handling (no backdrop flicker)
20532
20533(function(){
20534  const trigger   = document.querySelector('[data-mm2="trigger"]');
20535  const panel     = document.querySelector('[data-mm2="panel"]');
20536  const topView   = document.querySelector('[data-mm2="top"]');
20537  const detailView= document.querySelector('[data-mm2="detail"]');
20538  const backdrop  = document.querySelector('[data-mm2="backdrop"]');
20539  if (!trigger || !panel || !topView || !detailView) return;
20540
20541  const apiUrl = '/api/categories_tree.php?top=16&child=18&grand=14';
20542  const isDesktop = () => window.matchMedia('(min-width: 961px)').matches;
20543
20544  let data = [];
20545  let hoverTimer, closeTimer;
20546  let isLoading = false;
20547
20548  const esc = s => (s+'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
20549
20550  // Mobile close button HTML
20551  const mobileCloseBtn = () => `
20552    <button type="button" class="mm2-mobile-close" data-mm2="close" aria-label="Tutup menu">
20553      <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">
20554        <line x1="18" y1="6" x2="6" y2="18"></line>
20555        <line x1="6" y1="6" x2="18" y2="18"></line>
20556      </svg>
20557    </button>
20558  `;
20559
20560  // Category-specific gradient colors (16 unique combinations)
20561  const categoryGradients = [
20562    { from: '#e0e7ff', to: '#fff', border: '#c7d2fe', color: '#4f46e5' },  // Indigo
20563    { from: '#dbeafe', to: '#fff', border: '#bfdbfe', color: '#1e40af' },  // Blue
20564    { from: '#d1fae5', to: '#fff', border: '#a7f3d0', color: '#059669' },  // Green
20565    { from: '#fef3c7', to: '#fff', border: '#fde68a', color: '#d97706' },  // Amber
20566    { from: '#fce7f3', to: '#fff', border: '#fbcfe8', color: '#db2777' },  // Pink
20567    { from: '#e9d5ff', to: '#fff', border: '#d8b4fe', color: '#9333ea' },  // Purple
20568    { from: '#ffedd5', to: '#fff', border: '#fed7aa', color: '#ea580c' },  // Orange
20569    { from: '#ccfbf1', to: '#fff', border: '#99f6e4', color: '#0d9488' },  // Teal
20570    { from: '#fef9c3', to: '#fff', border: '#fef08a', color: '#ca8a04' },  // Yellow
20571    { from: '#ffe4e6', to: '#fff', border: '#fecdd3', color: '#e11d48' },  // Rose
20572    { from: '#e0f2fe', to: '#fff', border: '#bae6fd', color: '#0284c7' },  // Sky
20573    { from: '#f3e8ff', to: '#fff', border: '#e9d5ff', color: '#a855f7' },  // Violet
20574    { from: '#dcfce7', to: '#fff', border: '#bbf7d0', color: '#16a34a' },  // Emerald
20575    { from: '#fef2f2', to: '#fff', border: '#fecaca', color: '#dc2626' },  // Red
20576    { from: '#eff6ff', to: '#fff', border: '#dbeafe', color: '#2563eb' },  // Blue-600
20577    { from: '#f5f3ff', to: '#fff', border: '#ede9fe', color: '#7c3aed' },  // Violet-600
20578  ];
20579
20580  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>`;
20581
20582  // Recent (optional future use)
20583  const REC_KEY='mm2_recent';
20584  const saveRecent=(path,label)=>{ try{
20585    let list=JSON.parse(localStorage.getItem(REC_KEY)||'[]').filter(x=>x&&x.path!==path);
20586    list.unshift({path,label,t:Date.now()}); localStorage.setItem(REC_KEY, JSON.stringify(list.slice(0,8)));
20587  }catch{} };
20588
20589  function isToSafeArea(e){
20590    const t = e && e.relatedTarget ? e.relatedTarget : null;
20591    if (!t) return false;
20592    return (trigger.contains(t) || panel.contains(t) || (backdrop && backdrop.contains(t)));
20593  }
20594
20595  function open(){
20596    document.body.classList.add('mm2-open');
20597    showTop();
20598    // Add entrance animation class
20599    setTimeout(() => panel.classList.add('mm2-animated'), 10);
20600  }
20601  function close(){
20602    document.body.classList.remove('mm2-open');
20603    panel.classList.remove('mm2-animated');
20604  }
20605  function showTop(){
20606    topView.classList.add('is-active');
20607    detailView.classList.remove('is-active');
20608  }
20609
20610  // Loading skeleton
20611  function showLoading() {
20612    const skeletons = Array.from({length: 12}, (_, i) => `
20613      <div class="mm2-card mm2-skeleton" style="animation-delay: ${i * 0.02}s">
20614        <span class="mm2-ico skeleton-box"></span>
20615        <span style="flex: 1;">
20616          <div class="skeleton-line" style="width: 60%; height: 16px; margin-bottom: 6px;"></div>
20617          <div class="skeleton-line" style="width: 40%; height: 12px;"></div>
20618        </span>
20619      </div>
20620    `).join('');
20621    topView.innerHTML = `<div class="mm2-top"><div class="mm2-grid">${skeletons}</div></div>`;
20622  }
20623
20624  // Count total products in a category tree
20625  function countProducts(node) {
20626    let count = node.product_count || 0;
20627    if (Array.isArray(node.children)) {
20628      node.children.forEach(child => {
20629        count += countProducts(child);
20630      });
20631    }
20632    return count;
20633  }
20634
20635  // Cache for fetched thumbnails to avoid duplicate requests
20636  const thumbnailCache = new Map();
20637
20638  // Fetch product thumbnails for a category
20639  function fetchCategoryThumbnails(categoryPath) {
20640    // Check cache first
20641    if (thumbnailCache.has(categoryPath)) {
20642      injectThumbnails(categoryPath, thumbnailCache.get(categoryPath));
20643      return;
20644    }
20645
20646    const url = `/api/category_products_preview.php?path=${encodeURIComponent(categoryPath)}&limit=2`;
20647
20648    fetch(url)
20649      .then(r => {
20650        if (!r.ok) throw new Error('Network error');
20651        return r.json();
20652      })
20653      .then(data => {
20654        if (data.success && data.products && data.products.length > 0) {
20655          thumbnailCache.set(categoryPath, data.products);
20656          injectThumbnails(categoryPath, data.products);
20657        } else {
20658          // No products, remove skeleton
20659          removeThumbSkeletons(categoryPath);
20660        }
20661      })
20662      .catch(err => {
20663        console.warn('Failed to load thumbnails for', categoryPath, err);
20664        removeThumbSkeletons(categoryPath);
20665      });
20666  }
20667
20668  // Inject thumbnails into the DOM
20669  function injectThumbnails(categoryPath, products) {
20670    const links = detailView.querySelectorAll(`[data-cat-path="${categoryPath}"]`);
20671    links.forEach(link => {
20672      const thumbsContainer = link.querySelector('.mm2-thumbs');
20673      if (!thumbsContainer) return;
20674
20675      const thumbsHtml = products.slice(0, 2).map(p =>
20676        `<img src="${esc(p.img)}" alt="${esc(p.name)}" class="mm2-thumb" title="${esc(p.name)}" loading="lazy">`
20677      ).join('');
20678
20679      thumbsContainer.innerHTML = thumbsHtml;
20680      thumbsContainer.removeAttribute('data-loading');
20681    });
20682  }
20683
20684  // Remove skeleton loaders when no products
20685  function removeThumbSkeletons(categoryPath) {
20686    const links = detailView.querySelectorAll(`[data-cat-path="${categoryPath}"]`);
20687    links.forEach(link => {
20688      const thumbsContainer = link.querySelector('.mm2-thumbs');
20689      if (thumbsContainer) {
20690        thumbsContainer.remove();
20691      }
20692    });
20693  }
20694
20695  function showDetail(node){
20696    const title = node.label.split('/')[0];
20697    const children = Array.isArray(node.children) ? node.children : [];
20698    const hasChildren = children.length > 0;
20699
20700    const subnav = hasChildren ? `
20701      <nav class="mm2-subnav" role="tablist">
20702        ${children.map((c2,i)=>{
20703          const t = c2.label.split('/').slice(-1)[0];
20704          return `<button class="mm2-subitem${i===0?' active':''}" role="tab" data-idx="${i}" aria-selected="${i===0?'true':'false'}">${esc(t)}</button>`;
20705        }).join('')}
20706      </nav>` : '';
20707
20708    const subcontent = `<div class="mm2-subcontent" data-mm2="subcontent"></div>`;
20709
20710    detailView.innerHTML = `
20711      <div class="mm2-detail">
20712        <div class="mm2-detail-header">
20713          <button class="mm2-back" type="button" data-mm2="back" aria-label="Kembali">
20714            <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>
20715          </button>
20716          <div class="mm2-detail-title">${esc(title)}</div>
20717        </div>
20718
20719        ${hasChildren ? `
20720          <div class="mm2-detail-layout">
20721            ${subnav}
20722            ${subcontent}
20723          </div>` : `
20724          <div class="mm2-links">
20725            <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)}">
20726              <strong>Lihat semua ${esc(title)}</strong>
20727            </a>
20728          </div>
20729        `}
20730      </div>
20731    `;
20732
20733    topView.classList.remove('is-active');
20734    detailView.classList.add('is-active');
20735
20736    if (hasChildren) {
20737      const subEl  = detailView.querySelector('.mm2-subnav');
20738      const contEl = detailView.querySelector('[data-mm2="subcontent"]');
20739      let activeIdx = 0;
20740
20741      const renderSubgrid = (idx) => {
20742        const c2 = children[idx]; if (!c2) { contEl.innerHTML=''; return; }
20743        const title2 = c2.label.split('/').slice(-1)[0];
20744        const l3 = Array.isArray(c2.children) ? c2.children : [];
20745        const l3Html = l3.map(c3 => `
20746          <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)}">
20747            <span class="mm2-link-text">${esc(c3.label.split('/').slice(-1)[0])}</span>
20748            <span class="mm2-thumbs" data-loading="true">
20749              <span class="mm2-thumb-skeleton"></span>
20750              <span class="mm2-thumb-skeleton"></span>
20751            </span>
20752          </a>`).join('');
20753
20754        contEl.innerHTML = `
20755          <div class="mm2-links">
20756            <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)}">
20757              <strong>Lihat semua ${esc(title2)}</strong>
20758            </a>
20759            ${l3Html}
20760          </div>
20761        `;
20762
20763        // Fetch product thumbnails for each level-3 category (desktop only)
20764        if (isDesktop()) {
20765          setTimeout(() => {
20766            l3.forEach(c3 => {
20767              fetchCategoryThumbnails(c3.path);
20768            });
20769          }, 100);
20770        }
20771      };
20772
20773      renderSubgrid(activeIdx);
20774
20775      // Desktop: hover ganti konten; Mobile & Desktop: click pilih
20776      subEl.addEventListener('mousemove', (e)=>{
20777        if (!isDesktop()) return;
20778        const it = e.target.closest('.mm2-subitem'); if (!it) return;
20779        const idx = +it.dataset.idx; if (!Number.isInteger(idx) || idx===activeIdx) return;
20780        subEl.querySelectorAll('.mm2-subitem').forEach(el=>el.classList.remove('active'));
20781        it.classList.add('active');
20782        subEl.querySelectorAll('.mm2-subitem').forEach(el=>el.setAttribute('aria-selected', el===it ? 'true':'false'));
20783        activeIdx = idx;
20784        renderSubgrid(activeIdx);
20785      });
20786
20787      subEl.addEventListener('click', (e)=>{
20788        const it = e.target.closest('.mm2-subitem'); if (!it) return;
20789        const idx = +it.dataset.idx; if (!Number.isInteger(idx)) return;
20790        subEl.querySelectorAll('.mm2-subitem').forEach(el=>el.classList.remove('active'));
20791        it.classList.add('active');
20792        subEl.querySelectorAll('.mm2-subitem').forEach(el=>el.setAttribute('aria-selected', el===it ? 'true':'false'));
20793        activeIdx = idx;
20794        renderSubgrid(activeIdx);
20795      });
20796
20797      // Delegate link clicks to add recent
20798      detailView.addEventListener('click', (e)=>{
20799        const nav = e.target.closest('[data-mm2="nav"]');
20800        if (nav) { saveRecent(nav.dataset.path, nav.dataset.label); }
20801      });
20802    }
20803  }
20804
20805  function buildTop(){
20806    const html = data.map((n, idx) => {
20807      const title = n.label.split('/')[0];
20808      const childCount = (n.children||[]).length;
20809      const productCount = countProducts(n);
20810      const gradient = categoryGradients[idx % categoryGradients.length];
20811
20812      // Dynamic subtitle based on content - shorter for mobile
20813      let sub = '';
20814      if (productCount > 0) {
20815        sub = `${productCount} produk`;
20816        if (childCount > 0) sub += ` · ${childCount} kat`;
20817      } else if (childCount > 0) {
20818        sub = `${childCount} kategori`;
20819      } else {
20820        sub = 'Lihat semua';
20821      }
20822
20823      const iconStyle = `background: linear-gradient(135deg, ${gradient.from}, ${gradient.to}); border-color: ${gradient.border}; color: ${gradient.color};`;
20824
20825      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)}">
20826        <span class="mm2-ico" style="${iconStyle}">${icon()}</span>
20827        <span style="flex: 1;">
20828          <div class="mm2-title">${esc(title)}</div>
20829          <div class="mm2-sub">${esc(sub)}</div>
20830        </span>
20831      </a>`;
20832    }).join('');
20833    const closeBtnHtml = !isDesktop() ? mobileCloseBtn() : '';
20834    topView.innerHTML = `<div class="mm2-top">${closeBtnHtml}<div class="mm2-grid">${html}</div></div>`;
20835  }
20836
20837  // Open/close with safe-area
20838  trigger.addEventListener('click', (e)=>{ e.preventDefault(); document.body.classList.contains('mm2-open') ? close() : open(); });
20839  trigger.addEventListener('pointerenter', ()=>{ if (isDesktop()) { clearTimeout(closeTimer); hoverTimer=setTimeout(open, 80); }});
20840  trigger.addEventListener('pointerleave', (e)=>{ if (isDesktop() && !isToSafeArea(e)) { clearTimeout(hoverTimer); closeTimer=setTimeout(close, 200); }});
20841  panel.addEventListener('pointerenter', ()=>{ if (isDesktop()) clearTimeout(closeTimer); });
20842  panel.addEventListener('pointerleave', (e)=>{ if (isDesktop() && !isToSafeArea(e)) closeTimer=setTimeout(close, 200); });
20843  backdrop?.addEventListener('pointerenter', ()=>{ clearTimeout(closeTimer); });
20844  backdrop?.addEventListener('click', close);
20845  document.addEventListener('keydown', (e)=>{ if (e.key==='Escape') close(); });
20846
20847  // Card click → open detail (unless cmd/ctrl to open link)
20848  panel.addEventListener('click', (e)=>{
20849    const closeBtn = e.target.closest('[data-mm2="close"]'); if (closeBtn) { e.preventDefault(); close(); return; }
20850    const back = e.target.closest('[data-mm2="back"]'); if (back) { e.preventDefault(); showTop(); return; }
20851    const card = e.target.closest('[data-mm2="card"]');
20852    if (card && !e.metaKey && !e.ctrlKey) {
20853      e.preventDefault();
20854      const n = data.find(d => d.path === card.dataset.path);
20855      if (n) showDetail(n);
20856    }
20857    const nav = e.target.closest('[data-mm2="nav"]');
20858    if (nav) { saveRecent(nav.dataset.path, nav.dataset.label); }
20859  });
20860
20861  // Swipe-down gesture for mobile (bottom sheet close)
20862  if (!isDesktop()) {
20863    let startY = 0, currentY = 0, isDragging = false, startTime = 0;
20864    const SWIPE_THRESHOLD = 80;
20865    const VELOCITY_THRESHOLD = 0.3;
20866
20867    panel.addEventListener('touchstart', (e) => {
20868      if (!document.body.classList.contains('mm2-open')) return;
20869      const touch = e.touches[0];
20870      const scrollableContent = e.target.closest('.mm2-top, .mm2-detail');
20871
20872      // Prevent drag if content is scrolled
20873      if (scrollableContent && scrollableContent.scrollTop > 5) return;
20874
20875      // Only start drag from top area or handle
20876      const rect = panel.getBoundingClientRect();
20877      if (touch.clientY - rect.top > 60) return;
20878
20879      startY = touch.clientY;
20880      currentY = startY;
20881      startTime = Date.now();
20882      isDragging = true;
20883    }, { passive: true });
20884
20885    panel.addEventListener('touchmove', (e) => {
20886      if (!isDragging) return;
20887      const touch = e.touches[0];
20888      currentY = touch.clientY;
20889      const deltaY = currentY - startY;
20890
20891      // Only allow downward swipe
20892      if (deltaY > 0) {
20893        const opacity = Math.max(0.4, 1 - (deltaY / 300));
20894        panel.style.transform = `translateX(0) translateY(${deltaY}px)`;
20895        panel.style.opacity = opacity;
20896      }
20897    }, { passive: true });
20898
20899    const endDrag = () => {
20900      if (!isDragging) return;
20901      const deltaY = currentY - startY;
20902      const deltaTime = Date.now() - startTime;
20903      const velocity = deltaY / deltaTime;
20904
20905      // Close if swiped down enough or fast swipe
20906      if (deltaY > SWIPE_THRESHOLD || velocity > VELOCITY_THRESHOLD) {
20907        close();
20908      }
20909
20910      // Reset position
20911      panel.style.transform = '';
20912      panel.style.opacity = '';
20913      isDragging = false;
20914    };
20915
20916    panel.addEventListener('touchend', endDrag, { passive: true });
20917    panel.addEventListener('touchcancel', endDrag, { passive: true });
20918  }
20919
20920  // Fetch data with loading state
20921  if (!isLoading) {
20922    isLoading = true;
20923    showLoading();
20924
20925    fetch(apiUrl, { headers: { 'Accept': 'application/json' }})
20926      .then(r => {
20927        if (!r.ok) throw new Error('Network response was not ok');
20928        return r.json();
20929      })
20930      .then(j => {
20931        data = Array.isArray(j.data) ? j.data : [];
20932        if (!data.length) {
20933          topView.innerHTML = `<div class="mm2-top"><div style="text-align:center;padding:40px;color:var(--mm2-fg-muted);">
20934            <p>Tidak ada kategori tersedia</p>
20935          </div></div>`;
20936          return;
20937        }
20938        buildTop();
20939        isLoading = false;
20940      })
20941      .catch(err => {
20942        console.error('Failed to load categories:', err);
20943        topView.innerHTML = `<div class="mm2-top"><div style="text-align:center;padding:40px;color:var(--mm2-fg-muted);">
20944          <p>Gagal memuat kategori. Silakan coba lagi.</p>
20945        </div></div>`;
20946        isLoading = false;
20947      });
20948  }
20949})();
20950
20951// Add skeleton animation styles
20952const style = document.createElement('style');
20953style.textContent = `
20954.mm2-skeleton {
20955  pointer-events: none;
20956  animation: pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite;
20957}
20958.skeleton-box, .skeleton-line {
20959  background: linear-gradient(90deg, #f1f5f9 25%, #e2e8f0 50%, #f1f5f9 75%);
20960  background-size: 200% 100%;
20961  animation: shimmer 1.5s infinite;
20962  border-radius: 8px;
20963}
20964@keyframes pulse {
20965  0%, 100% { opacity: 1; }
20966  50% { opacity: 0.6; }
20967}
20968@keyframes shimmer {
20969  0% { background-position: 200% 0; }
20970  100% { background-position: -200% 0; }
20971}
20972`;
20973document.head.appendChild(style);
20974</script>
20974
20975    
20975<script data-inlined="benchmark.js">
20976/**
20977 * Benchmark Data Loader
20978 * Handles AJAX fetch and display of benchmark data
20979 */
20980
20981(function() {
20982    'use strict';
20983
20984    // Wait for DOM ready
20985    if (document.readyState === 'loading') {
20986        document.addEventListener('DOMContentLoaded', initBenchmark);
20987    } else {
20988        initBenchmark();
20989    }
20990
20991    function initBenchmark() {
20992        const section = document.querySelector('.pd-benchmark-section');
20993        if (!section) return;
20994
20995        const button = document.getElementById('btn-load-benchmark');
20996        const content = document.getElementById('benchmark-content');
20997        const productId = section.dataset.productId;
20998
20999        if (!button || !content || !productId) return;
21000
21001        let isLoading = false;
21002        let dataLoaded = false;
21003
21004        button.addEventListener('click', function() {
21005            if (isLoading || dataLoaded) return;
21006
21007            loadBenchmarkData(productId, button, content);
21008        });
21009    }
21010
21011    function loadBenchmarkData(productId, button, container) {
21012        // Update button state
21013        button.disabled = true;
21014        button.classList.add('loading');
21015        const originalText = button.querySelector('.btn-text').textContent;
21016        button.querySelector('.btn-text').textContent = 'Memuat data...';
21017
21018        // Show loading state
21019        container.style.display = 'block';
21020        container.innerHTML = `
21021            <div class="benchmark-loading">
21022                <div class="benchmark-loading-spinner"></div>
21023                <p>Mengambil data benchmark...</p>
21024            </div>
21025        `;
21026
21027        // Fetch data
21028        fetch('/api/benchmark/fetch.php?product_id=' + productId)
21029            .then(function(response) {
21030                if (!response.ok) {
21031                    throw new Error('HTTP error ' + response.status);
21032                }
21033                return response.json();
21034            })
21035            .then(function(result) {
21036                if (result.success && result.data) {
21037                    renderBenchmarkData(result.data, container);
21038                    button.style.display = 'none'; // Hide button after successful load
21039                } else {
21040                    throw new Error(result.error || 'Data tidak tersedia');
21041                }
21042            })
21043            .catch(function(error) {
21044                console.error('Benchmark fetch error:', error);
21045                container.innerHTML = `
21046                    <div class="benchmark-error">
21047                        <p><strong>⚠️ Data Benchmark Tidak Tersedia</strong></p>
21048                        <p style="font-size: 14px; margin-top: 8px;">
21049                            ${error.message || 'Terjadi kesalahan saat memuat data'}
21050                        </p>
21051                        <button type="button" onclick="location.reload()"
21052                                style="margin-top: 16px; padding: 8px 20px; background: #667eea; color: #fff; border: none; border-radius: 6px; cursor: pointer;">
21053                            Coba Lagi
21054                        </button>
21055                    </div>
21056                `;
21057
21058                // Reset button
21059                button.disabled = false;
21060                button.classList.remove('loading');
21061                button.querySelector('.btn-text').textContent = originalText;
21062            });
21063    }
21064
21065    function renderBenchmarkData(data, container) {
21066        let html = '<div class="benchmark-data-wrapper">';
21067
21068        // Chipset info if available
21069        if (data.chipset) {
21070            html += `
21071                <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 10px 16px; border-radius: 8px; margin-bottom: 12px;">
21072                    <div style="font-size: 11px; opacity: 0.85; margin-bottom: 2px;">Chipset</div>
21073                    <div style="font-size: 15px; font-weight: 600;">${escapeHtml(data.chipset)}</div>
21074                </div>
21075            `;
21076        }
21077
21078        // Main benchmark scores
21079        if (data.antutu || data.geekbench_multi || data.overall_score) {
21080            html += '<div class="benchmark-data-grid">';
21081
21082            if (data.antutu) {
21083                html += `
21084                    <div class="benchmark-card">
21085                        <div class="benchmark-card-label">ANTUTU</div>
21086                        <div class="benchmark-card-value large">${formatNumber(data.antutu)}</div>
21087                        ${getScoreBadge(data.antutu, 'antutu')}
21088                    </div>
21089                `;
21090            }
21091
21092            if (data.geekbench_multi) {
21093                html += `
21094                    <div class="benchmark-card">
21095                        <div class="benchmark-card-label">GEEKBENCH MULTI</div>
21096                        <div class="benchmark-card-value">${formatNumber(data.geekbench_multi)}</div>
21097                        ${data.geekbench_single ? '<div class="benchmark-card-subtitle">Single: ' + formatNumber(data.geekbench_single) + '</div>' : ''}
21098                    </div>
21099                `;
21100            }
21101
21102            if (data.gaming_score) {
21103                html += `
21104                    <div class="benchmark-card">
21105                        <div class="benchmark-card-label">GAMING</div>
21106                        <div class="benchmark-card-value">${data.gaming_score}<span style="font-size: 14px; opacity: 0.6;">/100</span></div>
21107                    </div>
21108                `;
21109            }
21110
21111            if (data.battery_score) {
21112                html += `
21113                    <div class="benchmark-card">
21114                        <div class="benchmark-card-label">BATTERY</div>
21115                        <div class="benchmark-card-value">${data.battery_score}<span style="font-size: 14px; opacity: 0.6;">/100</span></div>
21116                    </div>
21117                `;
21118            }
21119
21120            if (data.overall_score) {
21121                html += `
21122                    <div class="benchmark-card">
21123                        <div class="benchmark-card-label">OVERALL</div>
21124                        <div class="benchmark-card-value">${data.overall_score}<span style="font-size: 14px; opacity: 0.6;">/100</span></div>
21125                    </div>
21126                `;
21127            }
21128
21129            html += '</div>';
21130        }
21131
21132        // AI Performance Description
21133        if (data.notes) {
21134            html += `
21135                <div style="background: #f8f9fa; border-left: 3px solid #667eea; padding: 10px 14px; border-radius: 6px; margin-top: 12px;">
21136                    <div style="font-size: 11px; font-weight: 600; color: #667eea; margin-bottom: 6px;">💡 Analisis Performa</div>
21137                    <div style="font-size: 13px; color: #4a5568; line-height: 1.5;">${escapeHtml(data.notes)}</div>
21138                </div>
21139            `;
21140        }
21141
21142        // Attribution
21143        html += `
21144            <div class="benchmark-attribution">
21145                <p style="font-size: 11px; margin: 8px 0 0 0; color: #888;">Data benchmark dianalisis menggunakan AI dari spesifikasi produk</p>
21146                ${data.fetched_at ? '<p style="font-size: 10px; margin: 4px 0 0 0; color: #aaa;">Terakhir diperbarui: ' + formatDate(data.fetched_at) + '</p>' : ''}
21147            </div>
21148        `;
21149
21150        html += '</div>';
21151
21152        container.innerHTML = html;
21153    }
21154
21155    function formatNumber(num) {
21156        if (!num) return '-';
21157        return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.');
21158    }
21159
21160    function escapeHtml(text) {
21161        if (!text) return '';
21162        const div = document.createElement('div');
21163        div.textContent = text;
21164        return div.innerHTML;
21165    }
21166
21167    function formatDate(dateStr) {
21168        try {
21169            const date = new Date(dateStr);
21170            const options = { year: 'numeric', month: 'long', day: 'numeric' };
21171            return date.toLocaleDateString('id-ID', options);
21172        } catch (e) {
21173            return dateStr;
21174        }
21175    }
21176
21177    function getScoreBadge(score, type) {
21178        let category = '';
21179        let label = '';
21180
21181        if (type === 'antutu') {
21182            if (score >= 500000) {
21183                category = 'excellent';
21184                label = 'Flagship';
21185            } else if (score >= 350000) {
21186                category = 'good';
21187                label = 'High-End';
21188            } else if (score >= 200000) {
21189                category = 'average';
21190                label = 'Mid-Range';
21191            } else {
21192                category = 'average';
21193                label = 'Entry-Level';
21194            }
21195        }
21196
21197        if (label) {
21198            return '<div class="benchmark-score-badge ' + category + '">' + label + '</div>';
21199        }
21200
21201        return '';
21202    }
21203})();
21204
21205</script>
21205
21206  
21206<script data-inlined="product-compare.js">
21207(function() {
21208  'use strict';
21209
21210  // State management
21211  let compareList = [];
21212  let isAnalyzing = false;
21213  let currentAnalysis = null;
21214
21215  // DOM elements
21216  const compareBtn = document.getElementById('compareBtn');
21217  const compareSidebar = document.getElementById('compareSidebar');
21218  const compareOverlay = document.getElementById('compareOverlay');
21219  const compareClose = document.getElementById('compareClose');
21220  const compareModal = document.getElementById('aiCompareModal');
21221  const compareModalClose = document.getElementById('aiCompareModalClose');
21222
21223  // Initialize
21224  document.addEventListener('DOMContentLoaded', async function() {
21225    await initializeCompare();
21226    setupEventListeners();
21227  });
21228
21229  /**
21230   * Initialize compare functionality
21231   */
21232  async function initializeCompare() {
21233    // Load compare list first before setting up buttons
21234    await loadCompareList();
21235
21236    // Setup product detail compare button if exists
21237    const productCompareBtn = document.getElementById('pd-compare-btn');
21238
21239    if (productCompareBtn) {
21240      setupProductCompareButton(productCompareBtn);
21241    }
21242
21243    // Check for existing compare list and update UI
21244    if (compareList.length > 0) {
21245      // Only update badge and buttons, no sticky bar
21246    }
21247  }
21248
21249  /**
21250   * Setup event listeners
21251   */
21252  function setupEventListeners() {
21253    // Navigation compare button
21254    if (compareBtn) {
21255      compareBtn.addEventListener('click', toggleCompareSidebar);
21256    }
21257
21258    // Sidebar controls
21259    if (compareClose) {
21260      compareClose.addEventListener('click', closeCompareSidebar);
21261    }
21262
21263    if (compareOverlay) {
21264      compareOverlay.addEventListener('click', closeCompareSidebar);
21265    }
21266
21267    // Sidebar actions
21268    const clearAllBtn = document.getElementById('compareClearAll');
21269    if (clearAllBtn) {
21270      clearAllBtn.addEventListener('click', clearAllProducts);
21271    }
21272
21273    const analyzeBtn = document.getElementById('compareAnalyze');
21274    if (analyzeBtn) {
21275      analyzeBtn.addEventListener('click', startAIAnalysis);
21276    }
21277
21278    // Modal controls
21279    if (compareModalClose) {
21280      compareModalClose.addEventListener('click', closeModal);
21281    }
21282
21283    const modalBackdrop = compareModal?.querySelector('.modal-backdrop');
21284    if (modalBackdrop) {
21285      modalBackdrop.addEventListener('click', closeModal);
21286    }
21287
21288    // Modal actions
21289    const retryBtn = document.getElementById('aiRetry');
21290    if (retryBtn) {
21291      retryBtn.addEventListener('click', startAIAnalysis);
21292    }
21293
21294    // Removed share and add to cart buttons - replaced with AI disclaimer
21295  }
21296
21297  /**
21298   * Setup product detail compare button
21299   */
21300  function setupProductCompareButton(button) {
21301    const productId = parseInt(button.dataset.productId);
21302    if (!productId) return;
21303
21304    // Check if product is already in compare list
21305    updateProductCompareButtonState(button, productId);
21306
21307    button.addEventListener('click', async function() {
21308      // Get current variant ID (if any)
21309      const variantIdElement = document.getElementById('pd-variant-id');
21310      const variantId = variantIdElement ? parseInt(variantIdElement.value) || 0 : 0;
21311
21312      // Update button's variant data for current selection
21313      button.dataset.variantId = variantId.toString();
21314
21315      await toggleProductInCompare(productId, button, variantId);
21316    });
21317  }
21318
21319  /**
21320   * Toggle product in compare list
21321   */
21322  async function toggleProductInCompare(productId, button, variantId = 0) {
21323    // Check by product ID (variants of same product treated as same item)
21324    const isInCompare = compareList.some(p => parseInt(p.id) === parseInt(productId));
21325
21326    try {
21327      if (isInCompare) {
21328        await removeFromCompare(productId);
21329        showToast('Produk dihapus dari perbandingan', 'success');
21330      } else {
21331        await addToCompare(productId, variantId);
21332        showToast('Produk ditambahkan ke perbandingan', 'success');
21333
21334        // Trigger fly-to-compare animation
21335        triggerFlyToCompareAnimation(button);
21336      }
21337
21338      updateProductCompareButtonState(button, productId);
21339      updateCompareBadge();
21340      updateAllCompareButtons();
21341
21342    } catch (error) {
21343      showToast(error.message || 'Terjadi kesalahan', 'error');
21344    }
21345  }
21346
21347  /**
21348   * Update product compare button state
21349   */
21350  function updateProductCompareButtonState(button, productId) {
21351    // Ensure both IDs are compared as integers
21352    const isInCompare = compareList.some(p => parseInt(p.id) === parseInt(productId));
21353    button.classList.toggle('added', isInCompare);
21354
21355    // Handle visibility of text elements
21356    const compareText = button.querySelector('.compare-text');
21357    const compareRemove = button.querySelector('.compare-remove');
21358    const compareNote = document.getElementById('compare-note');
21359
21360    if (compareText) {
21361      compareText.style.display = isInCompare ? 'none' : 'inline';
21362    }
21363
21364    if (compareRemove) {
21365      if (isInCompare) {
21366        compareRemove.removeAttribute('hidden');
21367        compareRemove.style.display = 'flex';
21368      } else {
21369        compareRemove.setAttribute('hidden', '');
21370        compareRemove.style.display = 'none';
21371      }
21372    }
21373
21374    // Update info note
21375    if (compareNote) {
21376      if (isInCompare) {
21377        const currentCount = compareList.length;
21378        compareNote.textContent = `Produk sudah ada di daftar perbandingan (${currentCount}/5). Klik tombol untuk menghapus.`;
21379      } else {
21380        compareNote.textContent = 'Bandingkan hingga 5 produk dengan bantuan AI';
21381      }
21382    }
21383
21384    // Inline compare preview removed - keeping it simple
21385  }
21386
21387  /**
21388   * Add product to compare list
21389   */
21390  async function addToCompare(productId, variantId = 0) {
21391    const requestData = { product_id: productId };
21392    if (variantId > 0) {
21393      requestData.variant_id = variantId;
21394    }
21395
21396    const response = await fetch('/api/compare/add.php', {
21397      method: 'POST',
21398      headers: {
21399        'Content-Type': 'application/json',
21400      },
21401      body: JSON.stringify(requestData),
21402      credentials: 'same-origin'
21403    });
21404
21405    const result = await response.json();
21406
21407    if (!result.success) {
21408      throw new Error(result.error || 'Gagal menambahkan produk');
21409    }
21410
21411    // Send Google Analytics event for add to compare
21412    if (typeof gtag === 'function') {
21413      gtag('event', 'add_to_compare', {
21414        event_category: 'Product Compare',
21415        event_label: 'Product Added to Compare',
21416        product_id: productId,
21417        variant_id: variantId || 0,
21418        custom_map: {
21419          custom_parameter_1: 'compare_add_product'
21420        }
21421      });
21422    }
21423
21424    await loadCompareList();
21425    return result;
21426  }
21427
21428  /**
21429   * Remove product from compare list
21430   */
21431  async function removeFromCompare(productId) {
21432    const response = await fetch('/api/compare/remove.php', {
21433      method: 'POST',
21434      headers: {
21435        'Content-Type': 'application/json',
21436      },
21437      body: JSON.stringify({ product_id: productId }),
21438      credentials: 'same-origin'
21439    });
21440
21441    const result = await response.json();
21442
21443    if (!result.success) {
21444      throw new Error(result.error || 'Gagal menghapus produk');
21445    }
21446
21447    // Send Google Analytics event for remove from compare
21448    if (typeof gtag === 'function') {
21449      gtag('event', 'remove_from_compare', {
21450        event_category: 'Product Compare',
21451        event_label: 'Product Removed from Compare',
21452        product_id: productId,
21453        custom_map: {
21454          custom_parameter_1: 'compare_remove_product'
21455        }
21456      });
21457    }
21458
21459    await loadCompareList();
21460    return result;
21461  }
21462
21463  /**
21464   * Clear all products from compare list
21465   */
21466  async function clearAllProducts() {
21467    if (compareList.length === 0) return;
21468
21469    // Show confirmation modal
21470    const confirmed = await showConfirmModal({
21471      title: 'Hapus Semua Produk',
21472      message: 'Apakah Anda yakin ingin menghapus semua produk dari daftar perbandingan?',
21473      confirmText: 'Hapus Semua',
21474      cancelText: 'Batal',
21475      type: 'danger'
21476    });
21477
21478    if (!confirmed) return;
21479
21480    try {
21481      const response = await fetch('/api/compare/clear.php', {
21482        method: 'POST',
21483        credentials: 'same-origin'
21484      });
21485
21486      const result = await response.json();
21487
21488      if (!result.success) {
21489        throw new Error(result.error || 'Gagal menghapus produk');
21490      }
21491
21492      // Send Google Analytics event for clear all products
21493      if (typeof gtag === 'function') {
21494        gtag('event', 'compare_clear_all', {
21495          event_category: 'Product Compare',
21496          event_label: 'Clear All Products',
21497          products_count: compareList.length,
21498          custom_map: {
21499            custom_parameter_1: 'compare_clear_action'
21500          }
21501        });
21502      }
21503
21504      await loadCompareList();
21505      showToast('Semua produk dihapus dari perbandingan', 'success');
21506
21507    } catch (error) {
21508      showToast(error.message || 'Terjadi kesalahan', 'error');
21509    }
21510  }
21511
21512  /**
21513   * Load compare list from server
21514   */
21515  async function loadCompareList() {
21516    try {
21517      const response = await fetch('/api/compare/get.php', {
21518        credentials: 'same-origin'
21519      });
21520
21521      const result = await response.json();
21522
21523      if (result.success) {
21524        compareList = result.products || [];
21525        updateCompareSidebar();
21526        updateCompareBadge();
21527        updateAllCompareButtons();
21528      }
21529
21530    } catch (error) {
21531      // Silently handle error - compare functionality will work with empty list
21532    }
21533  }
21534
21535  /**
21536   * Update compare sidebar content
21537   */
21538  function updateCompareSidebar() {
21539    const emptyState = document.getElementById('compareEmpty');
21540    const listContainer = document.getElementById('compareList');
21541    const actionsContainer = document.getElementById('compareActions');
21542    const countElement = document.getElementById('compareCountSidebar');
21543
21544    if (!emptyState || !listContainer || !actionsContainer || !countElement) return;
21545
21546    // Update count
21547    countElement.textContent = `(${compareList.length})`;
21548
21549    if (compareList.length === 0) {
21550      emptyState.style.display = 'block';
21551      listContainer.style.display = 'none';
21552      actionsContainer.style.display = 'none';
21553    } else {
21554      emptyState.style.display = 'none';
21555      listContainer.style.display = 'block';
21556      actionsContainer.style.display = 'flex';
21557
21558      // Render product list
21559      listContainer.innerHTML = compareList.map(product => {
21560        // Display variant name if available
21561        let displayName = product.name;
21562        if (product.combination_name) {
21563          displayName += ` (${product.combination_name})`;
21564        }
21565
21566        // Use variant price if available
21567        let displayPrice = product.price;
21568        if (product.variant_price !== undefined && product.variant_price !== null) {
21569          displayPrice = product.variant_price;
21570        }
21571
21572        return `
21573          <div class="compare-item" data-product-id="${product.id}">
21574            <div class="compare-item-image">
21575              <img src="${product.image_url}" alt="${escapeHtml(displayName)}" loading="lazy">
21576            </div>
21577            <div class="compare-item-info">
21578              <div class="compare-item-name">${escapeHtml(displayName)}</div>
21579              <div class="compare-item-price">${formatPrice(displayPrice)}</div>
21580            </div>
21581            <button class="compare-item-remove" data-product-id="${product.id}" aria-label="Hapus">
21582              <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
21583                <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"/>
21584              </svg>
21585            </button>
21586          </div>
21587        `;
21588      }).join('');
21589
21590      // Setup event listeners for remove buttons
21591      setupRemoveButtons();
21592
21593      // Update analyze button state
21594      const analyzeBtn = document.getElementById('compareAnalyze');
21595      if (analyzeBtn) {
21596        analyzeBtn.disabled = compareList.length < 2;
21597      }
21598    }
21599  }
21600
21601  /**
21602   * Setup remove buttons event listeners
21603   */
21604  function setupRemoveButtons() {
21605    const removeButtons = document.querySelectorAll('.compare-item-remove');
21606    removeButtons.forEach(button => {
21607      button.addEventListener('click', async (e) => {
21608        e.preventDefault();
21609        const productId = parseInt(button.dataset.productId);
21610        if (productId) {
21611          await removeProduct(productId);
21612        }
21613      });
21614    });
21615  }
21616
21617  /**
21618   * Update all compare buttons on the page
21619   */
21620  function updateAllCompareButtons() {
21621    // Update product detail compare button
21622    const productBtn = document.getElementById('pd-compare-btn');
21623    if (productBtn) {
21624      const productId = parseInt(productBtn.dataset.productId);
21625      if (productId) {
21626        updateProductCompareButtonState(productBtn, productId);
21627      }
21628    }
21629
21630    // Update any other compare buttons on the page
21631    const allCompareButtons = document.querySelectorAll('[data-product-id][class*="compare"]');
21632    allCompareButtons.forEach(button => {
21633      const productId = parseInt(button.dataset.productId);
21634      if (productId && button.id !== 'pd-compare-btn') {
21635        updateProductCompareButtonState(button, productId);
21636      }
21637    });
21638  }
21639
21640  /**
21641   * Update compare badge
21642   */
21643  function updateCompareBadge() {
21644    const badge = document.getElementById('compareCount');
21645    if (!badge) return;
21646
21647    const oldCount = parseInt(badge.textContent) || 0;
21648    const newCount = compareList.length;
21649
21650    badge.textContent = newCount;
21651    badge.classList.toggle('active', newCount > 0);
21652
21653    // Animate badge if count increased
21654    if (newCount > oldCount) {
21655      animateCompareBadge();
21656      highlightCompareButton();
21657    }
21658
21659    // Sticky bar removed - keeping only fly-to-navbar animation
21660  }
21661
21662  /**
21663   * Animate compare badge when new product added
21664   */
21665  function animateCompareBadge() {
21666    const badge = document.getElementById('compareCount');
21667    if (!badge) return;
21668
21669    badge.style.transform = 'scale(1.3)';
21670    badge.style.transition = 'transform 0.3s ease';
21671
21672    setTimeout(() => {
21673      badge.style.transform = 'scale(1)';
21674    }, 300);
21675  }
21676
21677  /**
21678   * Highlight compare button temporarily
21679   */
21680  function highlightCompareButton() {
21681    const compareBtn = document.getElementById('compareBtn');
21682    if (!compareBtn) return;
21683
21684    compareBtn.classList.add('pulse-highlight');
21685
21686    setTimeout(() => {
21687      compareBtn.classList.remove('pulse-highlight');
21688    }, 3000);
21689  }
21690
21691  /**
21692   * Trigger fly-to-compare animation
21693   */
21694  function triggerFlyToCompareAnimation(sourceButton) {
21695    if (!sourceButton) {
21696      return;
21697    }
21698
21699    // Check if mobile view (bottom nav) or desktop (top nav)
21700    const isMobile = window.innerWidth <= 768;
21701
21702    // Get source position
21703    const sourceRect = sourceButton.getBoundingClientRect();
21704
21705    // Calculate target position
21706    let targetTop, targetLeft;
21707
21708    if (isMobile) {
21709      // Mobile: fly to fixed bottom position (bottom nav area)
21710      // Use fixed coordinates regardless of bottom nav visibility
21711      targetTop = window.innerHeight - 80; // Just above bottom nav area
21712      targetLeft = window.innerWidth / 2 - 30; // Center horizontally
21713    } else {
21714      // Desktop: fly to top navbar compare button
21715      const targetElement = document.getElementById('compareBtn');
21716      if (!targetElement) {
21717        return;
21718      }
21719      const targetRect = targetElement.getBoundingClientRect();
21720      targetTop = targetRect.top + 10;
21721      targetLeft = targetRect.left + 10;
21722    }
21723
21724    // Get product image for animation - try multiple selectors
21725    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"]');
21726
21727    // Create animated element
21728    const flyingElement = document.createElement('div');
21729    flyingElement.className = 'flying-product';
21730
21731    if (productImage) {
21732      flyingElement.innerHTML = `<img src="${productImage.src}" alt="Flying product">`;
21733      flyingElement.querySelector('img').style.cssText = `
21734        width: 100%;
21735        height: 100%;
21736        object-fit: cover;
21737        border-radius: 8px;
21738        box-shadow: 0 4px 12px rgba(0,0,0,0.3);
21739      `;
21740    } else {
21741      // Fallback: colored div with icon
21742      flyingElement.innerHTML = `<div class="flying-fallback">📦</div>`;
21743      flyingElement.querySelector('.flying-fallback').style.cssText = `
21744        width: 100%;
21745        height: 100%;
21746        background: #3b82f6;
21747        border-radius: 8px;
21748        display: flex;
21749        align-items: center;
21750        justify-content: center;
21751        font-size: 24px;
21752        box-shadow: 0 4px 12px rgba(0,0,0,0.3);
21753      `;
21754    }
21755
21756    flyingElement.style.cssText = `
21757      position: fixed;
21758      top: ${sourceRect.top}px;
21759      left: ${sourceRect.left}px;
21760      width: 60px;
21761      height: 60px;
21762      z-index: 9999;
21763      pointer-events: none;
21764      opacity: 1;
21765      transform: scale(1);
21766    `;
21767
21768    document.body.appendChild(flyingElement);
21769
21770    // Use Web Animations API for more reliable animation
21771    flyingElement.animate([
21772      // Keyframe 1: Initial state
21773      {
21774        top: `${sourceRect.top}px`,
21775        left: `${sourceRect.left}px`,
21776        transform: 'scale(1)',
21777        opacity: 1
21778      },
21779      // Keyframe 2: Final state
21780      {
21781        top: `${targetTop}px`,
21782        left: `${targetLeft}px`,
21783        transform: 'scale(0.3)',
21784        opacity: 0
21785      }
21786    ], {
21787      duration: 800,
21788      easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
21789      fill: 'forwards'
21790    });
21791
21792    // Clean up after animation completes
21793    setTimeout(() => {
21794      if (flyingElement.parentNode) {
21795        flyingElement.parentNode.removeChild(flyingElement);
21796      }
21797    }, 850);
21798  }
21799
21800  // Sticky compare bar functions removed - using only fly-to-navbar animation
21801
21802  /**
21803   * Toggle compare sidebar
21804   */
21805  function toggleCompareSidebar() {
21806    if (!compareSidebar) return;
21807
21808    const isOpen = compareSidebar.classList.contains('open');
21809
21810    if (isOpen) {
21811      closeCompareSidebar();
21812    } else {
21813      openCompareSidebar();
21814    }
21815  }
21816
21817  /**
21818   * Open compare sidebar
21819   */
21820  function openCompareSidebar() {
21821    if (!compareSidebar || !compareOverlay) return;
21822
21823    compareSidebar.classList.add('open');
21824    compareOverlay.classList.add('active');
21825    document.body.style.overflow = 'hidden';
21826
21827    // Send Google Analytics event for compare sidebar open
21828    if (typeof gtag === 'function') {
21829      gtag('event', 'compare_sidebar_open', {
21830        event_category: 'Product Compare',
21831        event_label: 'Compare Sidebar Opened',
21832        products_count: compareList.length,
21833        custom_map: {
21834          custom_parameter_1: 'compare_sidebar_view'
21835        }
21836      });
21837    }
21838  }
21839
21840  /**
21841   * Close compare sidebar
21842   */
21843  function closeCompareSidebar() {
21844    if (!compareSidebar || !compareOverlay) return;
21845
21846    compareSidebar.classList.remove('open');
21847    compareOverlay.classList.remove('active');
21848    document.body.style.overflow = '';
21849  }
21850
21851  /**
21852   * Start AI analysis
21853   */
21854  async function startAIAnalysis() {
21855    if (compareList.length < 2) {
21856      showToast('Minimal 2 produk diperlukan untuk analisis AI', 'warning');
21857      return;
21858    }
21859
21860    if (isAnalyzing) return;
21861
21862    // Send Google Analytics event for AI Analysis start
21863    if (typeof gtag === 'function') {
21864      gtag('event', 'ai_analysis_start', {
21865        event_category: 'Product Compare',
21866        event_label: 'AI Analysis Button Click',
21867        products_count: compareList.length,
21868        product_ids: compareList.map(p => p.id).join(','),
21869        custom_map: {
21870          custom_parameter_1: 'compare_ai_analysis'
21871        }
21872      });
21873    }
21874
21875    // Check if products are in similar categories
21876    const mismatchInfo = await checkProductCategoryMismatch();
21877    if (mismatchInfo.shouldWarn) {
21878      const categoryList = Array.from(mismatchInfo.categories).join(', ');
21879      const confirmed = await showConfirmModal({
21880        title: 'Produk Berbeda Kategori',
21881        message: `Produk yang Anda pilih berasal dari kategori yang berbeda: ${categoryList}. AI tetap bisa menganalisis, tapi perbandingan mungkin kurang relevan. Lanjutkan analisis?`,
21882        confirmText: 'Ya, Analisis Tetap',
21883        cancelText: 'Batal',
21884        type: 'warning'
21885      });
21886
21887      if (!confirmed) return;
21888    }
21889
21890    openModal();
21891    showLoadingState();
21892    isAnalyzing = true;
21893
21894    try {
21895      const response = await fetch('/api/compare/ai-analysis.php', {
21896        method: 'POST',
21897        credentials: 'same-origin'
21898      });
21899
21900      const result = await response.json();
21901
21902      if (!result.success) {
21903        throw new Error(result.error || 'Analisis AI gagal');
21904      }
21905
21906      currentAnalysis = result;
21907      showAnalysisResults(result);
21908
21909      // Send Google Analytics event for successful AI Analysis
21910      if (typeof gtag === 'function') {
21911        gtag('event', 'ai_analysis_success', {
21912          event_category: 'Product Compare',
21913          event_label: 'AI Analysis Completed',
21914          products_count: compareList.length,
21915          analysis_type: 'product_comparison',
21916          custom_map: {
21917            custom_parameter_1: 'compare_ai_success'
21918          }
21919        });
21920      }
21921
21922    } catch (error) {
21923      showErrorState(error.message);
21924
21925      // Send Google Analytics event for failed AI Analysis
21926      if (typeof gtag === 'function') {
21927        gtag('event', 'ai_analysis_error', {
21928          event_category: 'Product Compare',
21929          event_label: 'AI Analysis Failed',
21930          products_count: compareList.length,
21931          error_message: error.message,
21932          custom_map: {
21933            custom_parameter_1: 'compare_ai_error'
21934          }
21935        });
21936      }
21937    } finally {
21938      isAnalyzing = false;
21939    }
21940  }
21941
21942  /**
21943   * Show modal
21944   */
21945  function openModal() {
21946    if (!compareModal) return;
21947    compareModal.classList.add('open');
21948    document.body.style.overflow = 'hidden';
21949  }
21950
21951  /**
21952   * Close modal
21953   */
21954  function closeModal() {
21955    if (!compareModal) return;
21956    compareModal.classList.remove('open');
21957    document.body.style.overflow = '';
21958  }
21959
21960  /**
21961   * Show loading state
21962   */
21963  function showLoadingState() {
21964    const loading = document.getElementById('aiLoading');
21965    const error = document.getElementById('aiError');
21966    const results = document.getElementById('aiResults');
21967
21968    if (loading) loading.style.display = 'block';
21969    if (error) error.hidden = true;
21970    if (results) results.hidden = true;
21971  }
21972
21973  /**
21974   * Show error state
21975   */
21976  function showErrorState(message) {
21977    const loading = document.getElementById('aiLoading');
21978    const error = document.getElementById('aiError');
21979    const results = document.getElementById('aiResults');
21980    const errorMessage = document.getElementById('aiErrorMessage');
21981
21982    if (loading) loading.style.display = 'none';
21983    if (error) error.hidden = false;
21984    if (results) results.hidden = true;
21985    if (errorMessage) errorMessage.textContent = message;
21986  }
21987
21988  /**
21989   * Show analysis results
21990   */
21991  function showAnalysisResults(analysis) {
21992    const loading = document.getElementById('aiLoading');
21993    const error = document.getElementById('aiError');
21994    const results = document.getElementById('aiResults');
21995
21996    if (loading) loading.style.display = 'none';
21997    if (error) error.hidden = true;
21998    if (results) results.hidden = false;
21999
22000    const data = analysis.data;
22001
22002    // Update summary
22003    const summaryElement = document.getElementById('aiSummary');
22004    if (summaryElement) summaryElement.textContent = data.summary || '';
22005
22006    const winnerProduct = document.getElementById('winnerProduct');
22007    const winnerReason = document.getElementById('winnerReason');
22008    if (winnerProduct && winnerReason && data.winner_overall) {
22009      const winner = compareList.find(p => parseInt(p.id) === parseInt(data.winner_overall));
22010      winnerProduct.textContent = winner ? winner.name : 'Tidak diketa
22010hui';
22011      winnerReason.textContent = data.winner_reason || '';
22012    }
22013
22014    // Update comparison table
22015    updateComparisonTable(data.comparison_table);
22016
22017    // Update detailed analysis
22018    updateDetailedAnalysis(data.detailed_analysis);
22019
22020    // Update recommendations
22021    updateRecommendations(data.recommendations);
22022
22023    // Update buying tips
22024    updateBuyingTips(data.buying_tips);
22025  }
22026
22027  /**
22028   * Update comparison table
22029   */
22030  function updateComparisonTable(comparisonData) {
22031    const container = document.getElementById('comparisonTable');
22032    if (!container || !comparisonData?.categories) return;
22033
22034    // First, create product header row with names
22035    const productHeaderHtml = `
22036      <div class="comparison-category">
22037        <div class="category-header">Produk</div>
22038        <div class="category-products">
22039          ${compareList.map(product => `
22040            <div class="product-score">
22041              <div class="product-name-header">
22042                <strong>${escapeHtml(product.name)}</strong>
22043              </div>
22044              <div class="product-price">
22045                ${formatPrice(product.price)}
22046              </div>
22047            </div>
22048          `).join('')}
22049        </div>
22050      </div>
22051    `;
22052
22053    // Then create comparison categories
22054    const categoriesHtml = comparisonData.categories.map(category => `
22055      <div class="comparison-category">
22056        <div class="category-header">${escapeHtml(category.name)}</div>
22057        <div class="category-products">
22058          ${compareList.map(product => {
22059            const productData = category.products?.[product.id];
22060            return `
22061              <div class="product-score">
22062                <div class="score-value">
22063                  <span class="score-stars">${'★'.repeat(productData?.score || 0)}${'☆'.repeat(5 - (productData?.score || 0))}</span>
22064                  <span>${productData?.score || 0}/5</span>
22065                </div>
22066                <div class="score-note">${escapeHtml(productData?.note || '')}</div>
22067              </div>
22068            `;
22069          }).join('')}
22070        </div>
22071      </div>
22072    `).join('');
22073
22074    container.innerHTML = productHeaderHtml + categoriesHtml;
22075  }
22076
22077  /**
22078   * Update detailed analysis
22079   */
22080  function updateDetailedAnalysis(analysisData) {
22081    const container = document.getElementById('detailedAnalysis');
22082    if (!container || !analysisData) return;
22083
22084    container.innerHTML = compareList.map(product => {
22085      const productAnalysis = analysisData[product.id];
22086      if (!productAnalysis) return '';
22087
22088      // Use product name from API response if available, fallback to local name
22089      const productName = productAnalysis.product_name || product.name;
22090
22091      return `
22092        <div class="product-analysis">
22093          <h5>${escapeHtml(productName)}</h5>
22094
22095          ${productAnalysis.strengths ? `
22096            <div class="analysis-section">
22097              <h6 class="strengths">✅ Kelebihan</h6>
22098              <ul>
22099                ${productAnalysis.strengths.map(strength =>
22100                  `<li>${escapeHtml(strength)}</li>`
22101                ).join('')}
22102              </ul>
22103            </div>
22104          ` : ''}
22105
22106          ${productAnalysis.weaknesses ? `
22107            <div class="analysis-section">
22108              <h6 class="weaknesses">❌ Kekurangan</h6>
22109              <ul>
22110                ${productAnalysis.weaknesses.map(weakness =>
22111                  `<li>${escapeHtml(weakness)}</li>`
22112                ).join('')}
22113              </ul>
22114            </div>
22115          ` : ''}
22116
22117          ${productAnalysis.best_for ? `
22118            <div class="analysis-section">
22119              <h6 class="best-for">🎯 Cocok Untuk</h6>
22120              <p class="best-for-text">${escapeHtml(productAnalysis.best_for)}</p>
22121            </div>
22122          ` : ''}
22123        </div>
22124      `;
22125    }).filter(Boolean).join('');
22126  }
22127
22128  /**
22129   * Update recommendations
22130   */
22131  function updateRecommendations(recommendations) {
22132    if (!recommendations) return;
22133
22134    const budgetEl = document.getElementById('budgetRecommendation');
22135    const performanceEl = document.getElementById('performanceRecommendation');
22136    const balancedEl = document.getElementById('balancedRecommendation');
22137
22138    if (budgetEl && recommendations.budget_conscious) {
22139      const product = compareList.find(p => parseInt(p.id) === parseInt(recommendations.budget_conscious));
22140      budgetEl.textContent = product ? product.name : 'Tidak tersedia';
22141    }
22142
22143    if (performanceEl && recommendations.performance_focused) {
22144      const product = compareList.find(p => parseInt(p.id) === parseInt(recommendations.performance_focused));
22145      performanceEl.textContent = product ? product.name : 'Tidak tersedia';
22146    }
22147
22148    if (balancedEl && recommendations.balanced) {
22149      const product = compareList.find(p => parseInt(p.id) === parseInt(recommendations.balanced));
22150      balancedEl.textContent = product ? product.name : 'Tidak tersedia';
22151    }
22152  }
22153
22154  /**
22155   * Check if products are from different categories
22156   */
22157  async function checkProductCategoryMismatch() {
22158    if (compareList.length < 2) {
22159      return { shouldWarn: false, categories: new Set() };
22160    }
22161
22162    // Get the root categories from each product
22163    const rootCategories = new Set();
22164    const productCategories = [];
22165
22166    for (const product of compareList) {
22167      // Extract root category from the product's category_path if available
22168      const rootCategory = await getProductRootCategory(product);
22169      if (rootCategory) {
22170        rootCategories.add(rootCategory);
22171        productCategories.push({
22172          product: product.name,
22173          category: rootCategory
22174        });
22175      }
22176    }
22177
22178    // If we have more than 1 different root categories, show warning
22179    const shouldWarn = rootCategories.size > 1 && compareList.length >= 2;
22180
22181    return {
22182      shouldWarn: shouldWarn,
22183      categories: rootCategories,
22184      productCategories: productCategories
22185    };
22186  }
22187
22188  /**
22189   * Get product root category from category_path or fallback to name detection
22190   */
22191  async function getProductRootCategory(product) {
22192    // First try to get from category_path if available
22193    if (product.category_path) {
22194      const rootCategory = product.category_path.split('/')[0];
22195      return rootCategory;
22196    }
22197
22198    // If no category_path, try to fetch from product detail or use name detection
22199    const detectedCategory = detectProductCategory(product.name);
22200    return detectedCategory;
22201  }
22202
22203  /**
22204   * Detect product category from name (fallback method)
22205   */
22206  function detectProductCategory(productName) {
22207    const name = productName.toLowerCase();
22208
22209    // Map keywords to actual root categories from the API structure
22210    const categoryMappings = {
22211      // Komputer & Laptop
22212      'Komputer & Laptop': ['laptop', 'notebook', 'macbook', 'thinkpad', 'gaming laptop', 'desktop', 'pc', 'mini pc', 'all-in-one', 'server'],
22213
22214      // Printer & Scanner
22215      'Printer & Scanner': ['printer', 'epson printer', 'canon printer', 'hp printer', 'brother printer', 'scanner', 'laser printer', 'inkjet', 'dot matrix'],
22216
22217      // Gadget
22218      'Gadget': ['smartphone', 'iphone', 'samsung phone', 'xiaomi', 'oppo', 'vivo', 'realme', 'tablet', 'ipad', 'android tablet'],
22219
22220      // Periferal
22221      'Periferal': ['monitor', 'lcd monitor', 'led monitor', 'gaming monitor', 'keyboard', 'mouse', 'gaming keyboard', 'mechanical keyboard', 'webcam'],
22222
22223      // Audio
22224      'Audio': ['headset', 'headphone', 'earphone', 'gaming headset', 'speaker', 'mikrofon', 'sound card'],
22225
22226      // Jaringan
22227      'Jaringan': ['router', 'wifi', 'access point', 'switch', 'modem', 'network'],
22228
22229      // Penyimpanan
22230      'Penyimpanan': ['ssd', 'hdd', 'nvme', 'solid state', 'hard disk', 'flash drive', 'usb drive', 'memory card'],
22231
22232      // Komponen PC
22233      'Komponen PC': ['ram', 'memory', 'ddr4', 'ddr5', 'processor', 'cpu', 'intel', 'amd', 'motherboard', 'graphics card', 'gpu', 'psu', 'power supply', 'casing'],
22234
22235      // UPS & Power
22236      'UPS & Power': ['ups', 'uninterruptible', 'stabilizer', 'power supply'],
22237
22238      // Proyektor
22239      'Proyektor': ['proyektor', 'projector', 'layar proyektor'],
22240
22241      // Security System
22242      'Security System': ['cctv', 'security camera', 'surveillance'],
22243
22244      // Aksesoris
22245      'Aksesoris': ['charger laptop', 'adaptor', 'converter', 'dock', 'hub', 'kabel', 'cable', 'power bank', 'tas laptop'],
22246
22247      // Software
22248      'Software': ['software', 'lisensi', 'license', 'antivirus'],
22249
22250      // Perlengkapan Kantor
22251      'Perlengkapan Kantor': ['fingerprint', 'finger print', 'atk', 'alat tulis']
22252    };
22253
22254    // Check which root category matches
22255    for (const [rootCategory, keywords] of Object.entries(categoryMappings)) {
22256      for (const keyword of keywords) {
22257        if (name.includes(keyword)) {
22258          return rootCategory;
22259        }
22260      }
22261    }
22262
22263    return null;
22264  }
22265
22266  /**
22267   * Update buying tips
22268   */
22269  function updateBuyingTips(tips) {
22270    const container = document.getElementById('buyingTips');
22271    if (!container || !Array.isArray(tips)) return;
22272
22273    container.innerHTML = tips.map(tip =>
22274      `<li>${escapeHtml(tip)}</li>`
22275    ).join('');
22276  }
22277
22278  // Removed share and cart functions - replaced with AI disclaimer in UI
22279
22280  /**
22281   * Remove product from compare (public method)
22282   */
22283  async function removeProduct(productId) {
22284    try {
22285      await removeFromCompare(productId);
22286      showToast('Produk dihapus dari perbandingan', 'success');
22287
22288      // Update all compare buttons
22289      updateAllCompareButtons();
22290
22291    } catch (error) {
22292      showToast(error.message || 'Terjadi kesalahan', 'error');
22293    }
22294  }
22295
22296  /**
22297   * Utility functions
22298   */
22299  function escapeHtml(text) {
22300    const div = document.createElement('div');
22301    div.textContent = text;
22302    return div.innerHTML;
22303  }
22304
22305  function formatPrice(price) {
22306    return 'Rp ' + parseInt(price).toLocaleString('id-ID');
22307  }
22308
22309  /**
22310   * Show confirmation modal using the existing modal.css design
22311   */
22312  function showConfirmModal(options = {}) {
22313    return new Promise((resolve) => {
22314      const {
22315        title = 'Konfirmasi',
22316        message = 'Apakah Anda yakin?',
22317        confirmText = 'Ya',
22318        cancelText = 'Tidak',
22319        type = 'warning'
22320      } = options;
22321
22322      // Create modal HTML
22323      const modalHTML = `
22324        <div class="modal-overlay" id="confirmModal">
22325          <div class="modal-dialog">
22326            <div class="modal-header">
22327              <h3 class="modal-title">
22328                <div class="modal-icon ${type}">
22329                  ${getModalIcon(type)}
22330                </div>
22331                ${escapeHtml(title)}
22332              </h3>
22333            </div>
22334            <div class="modal-body">
22335              <p class="modal-message">${escapeHtml(message)}</p>
22336              <div class="modal-actions">
22337                <button type="button" class="modal-btn modal-btn-secondary" id="confirmCancel">
22338                  ${escapeHtml(cancelText)}
22339                </button>
22340                <button type="button" class="modal-btn modal-btn-${type === 'danger' ? 'danger' : 'primary'}" id="confirmOk">
22341                  ${escapeHtml(confirmText)}
22342                </button>
22343              </div>
22344            </div>
22345          </div>
22346        </div>
22347      `;
22348
22349      // Add to DOM
22350      document.body.insertAdjacentHTML('beforeend', modalHTML);
22351      const modal = document.getElementById('confirmModal');
22352      const cancelBtn = document.getElementById('confirmCancel');
22353      const okBtn = document.getElementById('confirmOk');
22354
22355      // Show modal
22356      requestAnimationFrame(() => {
22357        modal.classList.add('show');
22358      });
22359
22360      // Handle events
22361      function cleanup() {
22362        modal.remove();
22363      }
22364
22365      function handleCancel() {
22366        modal.classList.remove('show');
22367        setTimeout(() => {
22368          cleanup();
22369          resolve(false);
22370        }, 200);
22371      }
22372
22373      function handleConfirm() {
22374        modal.classList.remove('show');
22375        setTimeout(() => {
22376          cleanup();
22377          resolve(true);
22378        }, 200);
22379      }
22380
22381      cancelBtn.addEventListener('click', handleCancel);
22382      okBtn.addEventListener('click', handleConfirm);
22383
22384      // Close on overlay click
22385      modal.addEventListener('click', (e) => {
22386        if (e.target === modal) {
22387          handleCancel();
22388        }
22389      });
22390
22391      // Close on escape
22392      document.addEventListener('keydown', function escapeHandler(e) {
22393        if (e.key === 'Escape') {
22394          document.removeEventListener('keydown', escapeHandler);
22395          handleCancel();
22396        }
22397      });
22398
22399      // Focus on confirm button
22400      setTimeout(() => okBtn.focus(), 100);
22401    });
22402  }
22403
22404  /**
22405   * Get icon for modal type
22406   */
22407  function getModalIcon(type) {
22408      const icons = {
22409        danger: '🚨 ',
22410        warning: '⚠️',
22411        success: '✅',
22412        info: 'ℹ️'
22413      };
22414      return icons[type] || icons.info;
22415    }
22416
22417
22418  function showToast(message, type = 'info') {
22419    // Integration with existing toast system or create simple one
22420
22421    // Simple alert fallback - replace with proper toast system
22422    if (type === 'error') {
22423      alert('Error: ' + message);
22424    }
22425  }
22426
22427  // Inline compare preview functions removed - keeping it simple and clean
22428
22429  /**
22430   * Get current product ID from page
22431   */
22432  function getCurrentProductId() {
22433    const button = document.getElementById('pd-compare-btn');
22434    return button ? parseInt(button.dataset.productId) : null;
22435  }
22436
22437  // hideStickyCompareBar function removed - no longer using sticky bar
22438
22439  // Public API
22440  window.productCompare = {
22441    removeProduct: removeProduct,
22442    addToCompare: addToCompare,
22443    loadCompareList: loadCompareList,
22444    getCompareList: function() { return compareList; },
22445    openSidebar: openCompareSidebar,
22446    startAnalysis: startAIAnalysis,
22447    // Helper function to add from external sources with variant support
22448    addProductWithVariant: async function(productId, variantId = 0) {
22449      return await addToCompare(productId, variantId);
22450    }
22451  };
22452
22453})();
22454
22455
22456
22457</script>
vendor: 2 bytes, line 22457
22457
22458<script data-inlined="product-card-actions.js">
22459/**
22460 * Product Card Quick Actions
22461 * Bottom sheet menu for quick add to cart & compare
22462 * Integrates with existing product-compare.js and cart.js
22463 */
22464
22465(function() {
22466  'use strict';
22467
22468  let activeSheet = null;
22469  let activeProductData = null;
22470
22471  // DOM elements (will be created dynamically)
22472  let overlay = null;
22473  let bottomSheet = null;
22474
22475  /**
22476   * Initialize on DOM ready
22477   */
22478  document.addEventListener('DOMContentLoaded', function() {
22479    createBottomSheet();
22480    attachEventListeners();
22481  });
22482
22483  /**
22484   * Create bottom sheet HTML structure
22485   */
22486  function createBottomSheet() {
22487    // Create overlay
22488    overlay = document.createElement('div');
22489    overlay.className = 'pca-overlay';
22490    overlay.addEventListener('click', closeBottomSheet);
22491
22492    // Create bottom sheet
22493    bottomSheet = document.createElement('div');
22494    bottomSheet.className = 'pca-bottom-sheet';
22495    bottomSheet.innerHTML = `
22496      <div class="pca-sheet-handle"></div>
22497      <div class="pca-sheet-product" id="pca-product-preview">
22498        <!-- Product info will be inserted here -->
22499      </div>
22500      <div class="pca-sheet-actions">
22501        <button type="button" class="pca-action-item cart" id="pca-add-cart">
22502          <svg class="pca-action-icon" viewBox="0 0 24 24" fill="currentColor">
22503            <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"/>
22504          </svg>
22505          <div class="pca-action-content">
22506            <div class="pca-action-label">Tambah ke Keranjang</div>
22507            <div class="pca-action-desc">Lanjut belanja produk lain</div>
22508          </div>
22509        </button>
22510
22511        <button type="button" class="pca-action-item compare" id="pca-add-compare">
22512          <svg class="pca-action-icon" viewBox="0 0 24 24" fill="currentColor">
22513            <path d="M21,9L17,5V8H10V10H17V13M7,11L3,15L7,19V16H14V14H7V11Z"/>
22514          </svg>
22515          <div class="pca-action-content">
22516            <div class="pca-action-label">Bandingkan Produk</div>
22517            <div class="pca-action-desc">Analisis dengan AI</div>
22518          </div>
22519          <span class="pca-action-badge compare" id="pca-compare-count" style="display:none;">0</span>
22520        </button>
22521      </div>
22522    `;
22523
22524    // Append to body
22525    document.body.appendChild(overlay);
22526    document.body.appendChild(bottomSheet);
22527
22528    // Setup action buttons
22529    document.getElementById('pca-add-cart').addEventListener('click', handleAddToCart);
22530    document.getElementById('pca-add-compare').addEventListener('click', handleAddToCompare);
22531
22532    // Prevent clicks inside bottom sheet from closing it
22533    bottomSheet.addEventListener('click', function(e) {
22534      e.stopPropagation();
22535    });
22536  }
22537
22538  /**
22539   * Attach event listeners to all 3-dot buttons
22540   */
22541  function attachEventListeners() {
22542    // Use event delegation for dynamically loaded product cards
22543    document.addEventListener('click', function(e) {
22544      const btn = e.target.closest('.prod-card-actions-btn');
22545      if (btn) {
22546        e.preventDefault();
22547        e.stopPropagation();
22548        handleActionsButtonClick(btn);
22549      }
22550    });
22551
22552    // Handle ESC key
22553    document.addEventListener('keydown', function(e) {
22554      if (e.key === 'Escape' && activeSheet) {
22555        closeBottomSheet();
22556      }
22557    });
22558  }
22559
22560  /**
22561   * Handle 3-dot button click
22562   */
22563  function handleActionsButtonClick(button) {
22564    // Get product ID directly from button
22565    const productId = parseInt(button.dataset.productId) || 0;
22566    if (!productId) {
22567      console.error('Product ID not found on button:', button);
22568      showToast('Data produk tidak ditemukan', 'error');
22569      return;
22570    }
22571
22572    // Find parent card for additional data
22573    const card = button.closest('.prod-card, .pa-card');
22574    if (!card) {
22575      console.error('Product card not found');
22576      showToast('Data produk tidak ditemukan', 'error');
22577      return;
22578    }
22579
22580    // Extract product data from card
22581    const productData = extractProductData(card, productId);
22582    if (!productData) {
22583      showToast('Data produk tidak ditemukan', 'error');
22584      return;
22585    }
22586
22587    activeProductData = productData;
22588    openBottomSheet(productData);
22589  }
22590
22591  /**
22592   * Extract product data from card element
22593   */
22594  function extractProductData(card, productId) {
22595    try {
22596      // Product ID already passed from button
22597      if (!productId) {
22598        console.error('Product ID not provided');
22599        return null;
22600      }
22601      // Product ID already passed from button
22602      if (!productId) {
22603        console.error('Product ID not provided');
22604        return null;
22605      }
22606
22607      const id = productId;
22608
22609      // Get product name
22610      const nameEl = card.querySelector('.prod-name, .title');
22611      const name = nameEl ? nameEl.textContent.trim() : 'Produk';
22612
22613      // Get product image
22614      const imgEl = card.querySelector('.thumb img, img');
22615      const image = imgEl ? imgEl.src : '/assets/img/placeholder.png';
22616
22617      // Get product price and detect variants
22618      const priceEl = card.querySelector('.prod-price-final, .final, .prod-price');
22619      let price = 0;
22620      let hasVariants = false;
22621      let priceDisplay = '';
22622
22623      if (priceEl) {
22624        // Get text content and clean it
22625        let priceText = priceEl.textContent.trim();
22626        priceDisplay = priceText;
22627
22628        // Check if it's a range (indicates product has variants)
22629        if (priceText.includes('-')) {
22630          hasVariants = true;
22631          // Take the first price for calculation
22632          priceText = priceText.split('-')[0].trim();
22633        }
22634
22635        // Remove all non-numeric characters
22636        priceText = priceText.replace(/[^0-9]/g, '');
22637        price = parseInt(priceText) || 0;
22638      }
22639
22640      // Get stock status
22641      const stockDot = card.querySelector('.stock-dot');
22642      let stockStatus = 'unknown';
22643      let stockLabel = 'Cek stok';
22644
22645      if (stockDot) {
22646        if (stockDot.classList.contains('ready')) {
22647          stockStatus = 'in';
22648          stockLabel = 'Stok tersedia';
22649        } else if (stockDot.classList.contains('low')) {
22650          stockStatus = 'low';
22651          stockLabel = 'Stok terbatas';
22652        } else if (stockDot.classList.contains('out')) {
22653          stockStatus = 'out';
22654          stockLabel = 'Stok habis';
22655        }
22656      }
22657
22658      // Get category path (if available)
22659      const categoryPath = card.dataset.category || '';
22660
22661      // Get URL
22662      let url = '#';
22663      if (card.tagName === 'A') {
22664        url = card.href;
22665      } else {
22666        const link = card.querySelector('a[href*="/product/"]');
22667        if (link) url = link.href;
22668      }
22669
22670      return {
22671        id: id,
22672        name: name,
22673        image: image,
22674        price: price,
22675        priceDisplay: priceDisplay,
22676        hasVariants: hasVariants,
22677        stockStatus: stockStatus,
22678        stockLabel: stockLabel,
22679        categoryPath: categoryPath,
22680        url: url
22681      };
22682    } catch (error) {
22683      console.error('Error extracting product data:', error);
22684      return null;
22685    }
22686  }
22687
22688  /**
22689   * Open bottom sheet
22690   */
22691  function openBottomSheet(productData) {
22692    if (!overlay || !bottomSheet) return;
22693
22694    // Update product preview
22695    updateProductPreview(productData);
22696
22697    // Update cart button based on variant status
22698    updateCartButtonForVariant(productData);
22699
22700    // Update compare button state
22701    updateCompareButtonState(productData.id);
22702
22703    // Show overlay and sheet
22704    overlay.classList.add('active');
22705    bottomSheet.classList.add('active');
22706    document.body.style.overflow = 'hidden';
22707
22708    activeSheet = true;
22709
22710    // Google Analytics
22711    if (typeof gtag === 'function') {
22712      gtag('event', 'quick_actions_open', {
22713        event_category: 'Product Card',
22714        event_label: 'Quick Actions Menu Opened',
22715        product_id: productData.id,
22716        product_name: productData.name
22717      });
22718    }
22719  }
22720
22721  /**
22722   * Close bottom sheet
22723   */
22724  function closeBottomSheet() {
22725    if (!overlay || !bottomSheet) return;
22726
22727    overlay.classList.remove('active');
22728    bottomSheet.classList.remove('active');
22729    document.body.style.overflow = '';
22730
22731    activeSheet = false;
22732    activeProductData = null;
22733  }
22734
22735  /**
22736   * Update product preview in bottom sheet
22737   */
22738  function updateProductPreview(productData) {
22739    const preview = document.getElementById('pca-product-preview');
22740    if (!preview) return;
22741
22742    // Use full price display (shows range for variants)
22743    const priceDisplay = productData.priceDisplay || formatPrice(productData.price);
22744
22745    preview.innerHTML = `
22746      <img src="${escapeHtml(productData.image)}"
22747           alt="${escapeHtml(productData.name)}"
22748           class="pca-product-image"
22749           loading="lazy">
22750      <div class="pca-product-info">
22751        <h4 class="pca-product-name">${escapeHtml(productData.name)}</h4>
22752        <p class="pca-product-price">${escapeHtml(priceDisplay)}</p>
22753        <p class="pca-product-stock ${productData.stockStatus}">${productData.stockLabel}</p>
22754      </div>
22755    `;
22756  }
22757
22758  /**
22759   * Update cart button for variant products
22760   */
22761  function updateCartButtonForVariant(productData) {
22762    const cartBtn = document.getElementById('pca-add-cart');
22763    if (!cartBtn) return;
22764
22765    if (productData.hasVariants) {
22766      // Product has variants - change to "View Detail"
22767      cartBtn.dataset.hasVariants = 'true';
22768      cartBtn.dataset.productUrl = productData.url;
22769
22770      // Update icon to eye/external
22771      const iconSvg = cartBtn.querySelector('svg');
22772      if (iconSvg) {
22773        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"/>';
22774      }
22775
22776      // Update text
22777      const label = cartBtn.querySelector('.pca-action-label');
22778      const desc = cartBtn.querySelector('.pca-action-desc');
22779      if (label) label.textContent = 'Lihat Detail Produk';
22780      if (desc) desc.textContent = 'Pilih varian terlebih dahulu';
22781
22782    } else {
22783      // Single product - normal cart button
22784      cartBtn.dataset.hasVariants = 'false';
22785
22786      // Restore cart icon
22787      const iconSvg = cartBtn.querySelector('svg');
22788      if (iconSvg) {
22789        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"/>';
22790      }
22791
22792      // Restore text
22793      const label = cartBtn.querySelector('.pca-action-label');
22794      const desc = cartBtn.querySelector('.pca-action-desc');
22795      if (label) label.textContent = 'Tambah ke Keranjang';
22796      if (desc) desc.textContent = 'Lanjut belanja produk lain';
22797    }
22798  }
22799
22800  /**
22801   * Update compare button state
22802   */
22803  function updateCompareButtonState(productId) {
22804    const compareBtn = document.getElementById('pca-add-compare');
22805    const compareBadge = document.getElementById('pca-compare-count');
22806
22807    if (!compareBtn) return;
22808
22809    // Check if product is in compare list (using global compareList if available)
22810    let isInCompare = false;
22811    let compareCount = 0;
22812
22813    // Try to get compare list from product-compare.js via window.productCompare
22814    if (typeof window.productCompare !== 'undefined' && typeof window.productCompare.getCompareList === 'function') {
22815      const compareList = window.productCompare.getCompareList();
22816      compareCount = compareList.length;
22817      isInCompare = compareList.some(p => parseInt(p.id) === parseInt(productId));
22818    } else {
22819      // Fallback: check badge count
22820      const mainBadge = document.getElementById('compareCount');
22821      if (mainBadge) {
22822        compareCount = parseInt(mainBadge.textContent) || 0;
22823      }
22824    }
22825
22826    // Update button state
22827    if (isInCompare) {
22828      compareBtn.classList.add('active');
22829      compareBtn.querySelector('.pca-action-label').textContent = 'Hapus dari Perbandingan';
22830      compareBtn.querySelector('.pca-action-desc').textContent = 'Sudah ada di daftar';
22831    } else {
22832      compareBtn.classList.remove('active');
22833      compareBtn.querySelector('.pca-action-label').textContent = 'Bandingkan Produk';
22834      compareBtn.querySelector('.pca-action-desc').textContent = 'Analisis dengan AI';
22835    }
22836
22837    // Update badge
22838    if (compareBadge && compareCount > 0) {
22839      compareBadge.textContent = compareCount;
22840      compareBadge.style.display = 'inline-flex';
22841    } else if (compareBadge) {
22842      compareBadge.style.display = 'none';
22843    }
22844  }
22845
22846  /**
22847   * Handle add to cart
22848   */
22849  async function handleAddToCart() {
22850    if (!activeProductData) return;
22851
22852    const button = document.getElementById('pca-add-cart');
22853    if (!button) return;
22854
22855    // If product has variants, redirect to detail page
22856    if (activeProductData.hasVariants) {
22857      window.location.href = activeProductData.url;
22858      return;
22859    }
22860
22861    // Check stock
22862    if (activeProductData.stockStatus === 'out') {
22863      showToast('Produk sedang habis', 'warning');
22864      return;
22865    }
22866
22867    // Show loading state
22868    button.classList.add('loading');
22869    button.disabled = true;
22870
22871    // CSRF Token Helper
22872    function getCsrfToken() {
22873      return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
22874    }
22875
22876    try {
22877      // Call cart API with proper format
22878      const response = await fetch('/api/cart/add.php', {
22879        method: 'POST',
22880        headers: {
22881          'Content-Type': 'application/json',
22882          'X-CSRF-Token': getCsrfToken()
22883        },
22884        body: JSON.stringify({
22885          csrf_token: getCsrfToken(),
22886          product_id: activeProductData.id,
22887          qty: 1, // Use 'qty' not 'quantity'
22888          variant_id: 0, // Default variant
22889          meta: {
22890            name: activeProductData.name,
22891            image_url: activeProductData.image
22892          }
22893        }),
22894        credentials: 'same-origin'
22895      });
22896
22897      const result = await response.json();
22898
22899      // Check both response status and result.ok
22900      if (!response.ok || !result.ok) {
22901        throw new Error(result.error || result.message || 'Gagal menambahkan ke keranjang');
22902      }
22903
22904      // Success animation
22905      button.classList.remove('loading');
22906      button.classList.add('success');
22907
22908      // Update cart badge
22909      updateCartBadge();
22910
22911      // PlazaAnalytics tracking
22912      if (window.PlazaAnalytics) {
22913        const _slug = (activeProductData.url || '').replace(/.*\/product\//, '');
22914        window.PlazaAnalytics.track('add_to_cart', { slug: _slug });
22915      }
22916
22917      // Show success toast
22918      showToast('Produk ditambahkan ke keranjang', 'success');
22919
22920      // Google Analytics
22921      if (typeof gtag === 'function') {
22922        gtag('event', 'add_to_cart', {
22923          event_category: 'Ecommerce',
22924          event_label: 'Quick Add to Cart',
22925          items: [{
22926            item_id: activeProductData.id,
22927            item_name: activeProductData.name,
22928            price: activeProductData.price,
22929            quantity: 1
22930          }]
22931        });
22932      }
22933
22934      // Close sheet after short delay
22935      setTimeout(() => {
22936        closeBottomSheet();
22937        button.classList.remove('success');
22938        button.disabled = false;
22939      }, 1000);
22940
22941    } catch (error) {
22942      console.error('Add to cart error:', error);
22943      button.classList.remove('loading');
22944      button.disabled = false;
22945      showToast(error.message || 'Terjadi kesalahan', 'error');
22946    }
22947  }
22948
22949  /**
22950   * Handle add to compare
22951   */
22952  async function handleAddToCompare() {
22953    if (!activeProductData) return;
22954
22955    const button = document.getElementById('pca-add-compare');
22956    if (!button) return;
22957
22958    // Check if already in compare
22959    const isActive = button.classList.contains('active');
22960
22961    // Check 5-product limit before adding
22962    if (!isActive) {
22963      const mainBadge = document.getElementById('compareCount');
22964      const currentCount = mainBadge ? parseInt(mainBadge.textContent) || 0 : 0;
22965
22966      if (currentCount >= 5) {
22967        showToast('Maksimal 5 produk untuk perbandingan', 'warning');
22968        return;
22969      }
22970    }
22971
22972    // Show loading state
22973    button.classList.add('loading');
22974    button.disabled = true;
22975
22976    try {
22977      if (isActive) {
22978        // Remove from compare
22979        await removeFromCompare(activeProductData.id);
22980        showToast('Produk dihapus dari perbandingan', 'success');
22981      } else {
22982        // Add to compare
22983        await addToCompare(activeProductData.id);
22984        showToast('Produk ditambahkan ke perbandingan', 'success');
22985
22986        // Trigger fly animation
22987        triggerFlyToCompareAnimation();
22988      }
22989
22990      // Success animation
22991      button.classList.remove('loading');
22992      button.classList.add('success');
22993
22994      // Update compare button state
22995      updateCompareButtonState(activeProductData.id);
22996
22997      // Close sheet after short delay
22998      setTimeout(() => {
22999        closeBottomSheet();
23000        button.classList.remove('success');
23001        button.disabled = false;
23002      }, 800);
23003
23004    } catch (error) {
23005      console.error('Compare error:', error);
23006      button.classList.remove('loading');
23007      button.disabled = false;
23008      showToast(error.message || 'Terjadi kesalahan', 'error');
23009    }
23010  }
23011
23012  /**
23013   * Add to compare (calls existing API)
23014   */
23015  async function addToCompare(productId) {
23016    const response = await fetch('/api/compare/add.php', {
23017      method: 'POST',
23018      headers: {
23019        'Content-Type': 'application/json',
23020      },
23021      body: JSON.stringify({
23022        product_id: productId,
23023        variant_id: 0
23024      }),
23025      credentials: 'same-origin'
23026    });
23027
23028    const result = await response.json();
23029
23030    if (!result.success) {
23031      throw new Error(result.error || 'Gagal menambahkan produk');
23032    }
23033
23034    // Trigger compare list reload if function exists
23035    if (typeof window.productCompare !== 'undefined' && typeof window.productCompare.loadCompareList === 'function') {
23036      await window.productCompare.loadCompareList();
23037    }
23038
23039    return result;
23040  }
23041
23042  /**
23043   * Remove from compare (calls existing API)
23044   */
23045  async function removeFromCompare(productId) {
23046    const response = await fetch('/api/compare/remove.php', {
23047      method: 'POST',
23048      headers: {
23049        'Content-Type': 'application/json',
23050      },
23051      body: JSON.stringify({
23052        product_id: productId
23053      }),
23054      credentials: 'same-origin'
23055    });
23056
23057    const result = await response.json();
23058
23059    if (!result.success) {
23060      throw new Error(result.error || 'Gagal menghapus produk');
23061    }
23062
23063    // Trigger compare list reload if function exists
23064    if (typeof window.productCompare !== 'undefined' && typeof window.productCompare.loadCompareList === 'function') {
23065      await window.productCompare.loadCompareList();
23066    }
23067
23068    return result;
23069  }
23070
23071  /**
23072   * Update cart badge
23073   */
23074  function updateCartBadge() {
23075    // Try to call global cart update function if exists
23076    if (typeof window.updateCartBadge === 'function') {
23077      window.updateCartBadge();
23078    } else {
23079      // Fallback: increment badge manually
23080      const badge = document.getElementById('cartCount');
23081      if (badge) {
23082        const currentCount = parseInt(badge.textContent) || 0;
23083        badge.textContent = currentCount + 1;
23084        badge.classList.add('active');
23085      }
23086    }
23087  }
23088
23089  /**
23090   * Trigger fly to compare animation
23091   */
23092  function triggerFlyToCompareAnimation() {
23093    const compareBtn = document.getElementById('compareBtn');
23094    if (!compareBtn || !activeProductData) return;
23095
23096    // Create flying element
23097    const flyingElement = document.createElement('div');
23098    flyingElement.className = 'flying-product';
23099    flyingElement.style.cssText = `
23100      position: fixed;
23101      width: 60px;
23102      height: 60px;
23103      z-index: 99999;
23104      pointer-events: none;
23105    `;
23106
23107    flyingElement.innerHTML = `
23108      <img src="${activeProductData.image}"
23109           style="width:100%;height:100%;object-fit:cover;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.3);">
23110    `;
23111
23112    // Position at bottom sheet
23113    const sheetRect = bottomSheet.getBoundingClientRect();
23114    const targetRect = compareBtn.getBoundingClientRect();
23115
23116    flyingElement.style.top = `${sheetRect.top + 20}px`;
23117    flyingElement.style.left = `${sheetRect.left + 20}px`;
23118
23119    document.body.appendChild(flyingElement);
23120
23121    // Animate to target
23122    requestAnimationFrame(() => {
23123      flyingElement.style.transition = 'all 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
23124      flyingElement.style.top = `${targetRect.top + 10}px`;
23125      flyingElement.style.left = `${targetRect.left + 10}px`;
23126      flyingElement.style.transform = 'scale(0.3)';
23127      flyingElement.style.opacity = '0';
23128    });
23129
23130    // Clean up
23131    setTimeout(() => {
23132      if (flyingElement.parentNode) {
23133        flyingElement.parentNode.removeChild(flyingElement);
23134      }
23135    }, 800);
23136  }
23137
23138  /**
23139   * Format price as Rupiah
23140   */
23141  function formatPrice(price) {
23142    if (!price || price === 0) return 'Rp 0';
23143    return 'Rp ' + parseInt(price).toLocaleString('id-ID');
23144  }
23145
23146  /**
23147   * Escape HTML
23148   */
23149  function escapeHtml(text) {
23150    const div = document.createElement('div');
23151    div.textContent = text;
23152    return div.innerHTML;
23153  }
23154
23155  /**
23156   * Show toast notification (uses global showToast if available)
23157   */
23158  function showToast(message, type = 'info') {
23159    if (typeof window.showToast === 'function') {
23160      window.showToast({
23161        title: type === 'success' ? 'Berhasil' : type === 'error' ? 'Gagal' : 'Info',
23162        message: message,
23163        type: type,
23164        duration: 3000
23165      });
23166    } else {
23167      // Fallback: console log
23168      console.log(`[${type.toUpperCase()}] ${message}`);
23169    }
23170  }
23171
23172  /**
23173   * Expose functions for external access
23174   */
23175  window.ProductCardActions = {
23176    openSheet: openBottomSheet,
23177    closeSheet: closeBottomSheet,
23178    updateCompareState: updateCompareButtonState
23179  };
23180
23181})();
23182
23183</script>
23183
23184    
23184<script data-inlined="product-reviews.js">
23185/**
23186 * Product Reviews - Frontend JavaScript
23187 * Handles review display, submission, and helpful voting
23188 */
23189
23190const ProductReviews = (function() {
23191  'use strict';
23192
23193  let currentPage = 1;
23194  let currentSort = 'helpful';
23195  let productId = null;
23196  let isLoading = false;
23197
23198  /**
23199   * Public API: Initialize reviews for a product
23200   */
23201  function init(pid) {
23202    productId = pid;
23203
23204    if (!productId) {
23205      console.warn('Product ID not provided to ProductReviews.init()');
23206      return;
23207    }
23208
23209    initializeReviewTab();
23210    loadReviews();
23211  }
23212
23213  /**
23214   * Initialize review tab functionality
23215   */
23216  function initializeReviewTab() {
23217    // Tab switching
23218    const tabButtons = document.querySelectorAll('.tab-btn');
23219    tabButtons.forEach(btn => {
23220      btn.addEventListener('click', function() {
23221        const tabName = this.dataset.tab;
23222        switchTab(tabName);
23223      });
23224    });
23225
23226    // Sort dropdown
23227    const sortSelect = document.getElementById('review-sort');
23228    if (sortSelect) {
23229      sortSelect.addEventListener('change', function() {
23230        currentSort = this.value;
23231        currentPage = 1;
23232        loadReviews();
23233      });
23234    }
23235
23236    // Load more button
23237    const loadMoreBtn = document.getElementById('load-more-reviews');
23238    if (loadMoreBtn) {
23239      loadMoreBtn.addEventListener('click', function() {
23240        currentPage++;
23241        loadReviews(true); // Append mode
23242      });
23243    }
23244
23245    // Write review button
23246    const writeReviewBtn = document.getElementById('write-review-btn');
23247    if (writeReviewBtn) {
23248      writeReviewBtn.addEventListener('click', showReviewModal);
23249    }
23250  }
23251
23252  /**
23253   * Switch between product tabs
23254   */
23255  function switchTab(tabName) {
23256    // Update button states
23257    document.querySelectorAll('.tab-btn').forEach(btn => {
23258      btn.classList.toggle('active', btn.dataset.tab === tabName);
23259    });
23260
23261    // Update content visibility
23262    document.querySelectorAll('.tab-content').forEach(content => {
23263      content.style.display = content.id === `${tabName}-tab` ? 'block' : 'none';
23264    });
23265  }
23266
23267  /**
23268   * Load reviews from API
23269   */
23270  async function loadReviews(append = false) {
23271    if (isLoading) return;
23272    isLoading = true;
23273
23274    const container = document.getElementById('reviews-list');
23275    const loadMoreBtn = document.getElementById('load-more-reviews');
23276
23277    // Check if review section exists (product might not have reviews section)
23278    if (!container) {
23279      console.log('Review section not found - product may not have reviews yet');
23280      isLoading = false;
23281      return;
23282    }
23283
23284    try {
23285      if (!append) {
23286        container.innerHTML = '<div class="review-loading">Memuat ulasan...</div>';
23287      } else {
23288        loadMoreBtn.disabled = true;
23289        loadMoreBtn.textContent = 'Memuat...';
23290      }
23291
23292      const response = await fetch(`/api/reviews/list.php?product_id=${productId}&sort=${currentSort}&page=${currentPage}&limit=10`);
23293      const data = await response.json();
23294
23295      if (!data.success) {
23296        throw new Error(data.message || 'Gagal memuat ulasan');
23297      }
23298
23299      // Update statistics (only on initial load)
23300      if (!append && data.stats) {
23301        updateReviewStats(data.stats);
23302      }
23303
23304      // Render reviews
23305      if (!append) {
23306        container.innerHTML = '';
23307      }
23308
23309      const reviews = data.reviews || [];
23310
23311      if (reviews.length === 0 && !append) {
23312        container.innerHTML = '<div class="no-reviews">Belum ada ulasan untuk produk ini. Jadilah yang pertama!</div>';
23313      } else {
23314        reviews.forEach(review => {
23315          container.appendChild(createReviewElement(review));
23316        });
23317      }
23318
23319      // Update load more button
23320      if (loadMoreBtn) {
23321        if (data.pagination.has_more) {
23322          loadMoreBtn.style.display = 'block';
23323          loadMoreBtn.disabled = false;
23324          loadMoreBtn.textContent = 'Muat Lebih Banyak';
23325        } else {
23326          loadMoreBtn.style.display = 'none';
23327        }
23328      }
23329
23330    } catch (error) {
23331      console.error('Load reviews error:', error);
23332      container.innerHTML = '<div class="review-error">Gagal memuat ulasan. Silakan coba lagi.</div>';
23333    } finally {
23334      isLoading = false;
23335    }
23336  }
23337
23338  /**
23339   * Update review statistics display
23340   */
23341  function updateReviewStats(stats) {
23342    const avgRatingEl = document.getElementById('avg-rating');
23343    const totalReviewsEl = document.getElementById('total-reviews');
23344    const ratingBarsContainer = document.getElementById('rating-bars');
23345
23346    if (avgRatingEl) {
23347      avgRatingEl.textContent = stats.average_rating.toFixed(1);
23348    }
23349
23350    if (totalReviewsEl) {
23351      totalReviewsEl.textContent = `${stats.total_reviews} ulasan`;
23352    }
23353
23354    // Update rating breakdown bars
23355    if (ratingBarsContainer) {
23356      ratingBarsContainer.innerHTML = '';
23357      stats.rating_breakdown.forEach(item => {
23358        const bar = document.createElement('div');
23359        bar.className = 'rating-bar';
23360        bar.innerHTML = `
23361          <span class="rating-label">${item.rating}★</span>
23362          <div class="bar-container">
23363            <div class="bar-fill" style="width: ${item.percentage}%"></div>
23364          </div>
23365          <span class="rating-count">${item.count}</span>
23366        `;
23367        ratingBarsContainer.appendChild(bar);
23368      });
23369    }
23370
23371    // Update tab count
23372    const reviewTab = document.querySelector('[data-tab="reviews"]');
23373    if (reviewTab && stats.total_reviews > 0) {
23374      reviewTab.textContent = `Ulasan (${stats.total_reviews})`;
23375    }
23376  }
23377
23378  /**
23379   * Create review element
23380   */
23381  function createReviewElement(review) {
23382    const div = document.createElement('div');
23383    div.className = 'review-item';
23384    div.dataset.reviewId = review.ID;
23385
23386    // Build stars
23387    const stars = '★'.repeat(review.RATING) + '☆'.repeat(5 - review.RATING);
23388
23389    // Build images if available
23390    let imagesHtml = '';
23391    if (review.images && review.images.length > 0) {
23392      imagesHtml = '<div class="review-images">';
23393      review.images.forEach(img => {
23394        imagesHtml += `<img src="${escapeHtml(img)}" alt="Review image" class="review-img" loading="lazy">`;
23395      });
23396      imagesHtml += '</div>';
23397    }
23398
23399    // Verified purchase badge
23400    const verifiedBadge = review.IS_VERIFIED_PURCHASE === 'Y'
23401      ? '<span class="verified-badge">✓ Pembelian Terverifikasi</span>'
23402      : '';
23403
23404    // Use DISPLAY_NAME if available (for anonymous reviews), otherwise use FULL_NAME
23405    const displayName = review.DISPLAY_NAME || review.FULL_NAME || 'Anonymous';
23406    const anonymousIndicator = review.IS_ANONYMOUS === 'Y'
23407      ? '<span class="anonymous-badge" title="Nama pengguna disamark
23407an untuk privasi">🔒</span>'
23408      : '';
23409
23410    div.innerHTML = `
23411      <div class="review-header">
23412        <div class="review-author">
23413          <strong>${escapeHtml(displayName)}</strong>
23414          ${anonymousIndicator}
23415          ${verifiedBadge}
23416        </div>
23417        <div class="review-rating">
23418          <span class="stars">${stars}</span>
23419        </div>
23420      </div>
23421      <div class="review-date">${review.created_ago}</div>
23422      <div class="review-text">${escapeHtml(review.REVIEW_TEXT).replace(/\n/g, '<br>')}</div>
23423      ${imagesHtml}
23424      <div class="review-actions">
23425        <button class="btn-helpful" data-review-id="${review.ID}">
23426          <span class="helpful-icon">👍</span>
23427          <span class="helpful-text">Membantu (${review.HELPFUL_COUNT || 0})</span>
23428        </button>
23429      </div>
23430    `;
23431
23432    // Add helpful button listener
23433    const helpfulBtn = div.querySelector('.btn-helpful');
23434    helpfulBtn.addEventListener('click', () => markAsHelpful(review.ID));
23435
23436    return div;
23437  }
23438
23439  /**
23440   * Get CSRF token from meta tag
23441   */
23442  function getCsrfToken() {
23443    const metaTag = document.querySelector('meta[name="csrf-token"]');
23444    return metaTag ? metaTag.getAttribute('content') : '';
23445  }
23446
23447  /**
23448   * Mark review as helpful
23449   */
23450  async function markAsHelpful(reviewId) {
23451    // Check if logged in
23452    const isLoggedIn = document.body.classList.contains('logged-in') ||
23453                      document.querySelector('meta[name="user-logged-in"]')?.content === 'true';
23454
23455    if (!isLoggedIn) {
23456      showModal({
23457        title: 'Login Diperlukan',
23458        message: 'Silakan login terlebih dahulu untuk memberikan vote pada ulasan ini.',
23459        icon: 'info',
23460        buttons: [
23461          { text: 'Batal', className: 'modal-btn-secondary' },
23462          { text: 'Login', className: 'modal-btn-primary', callback: () => window.location.href = '/login' }
23463        ]
23464      });
23465      return;
23466    }
23467
23468    // Get current button element
23469    const reviewItem = document.querySelector(`[data-review-id="${reviewId}"]`);
23470    const helpfulBtn = reviewItem?.querySelector('.btn-helpful');
23471
23472    if (!helpfulBtn) return;
23473
23474    // Disable button during request
23475    helpfulBtn.disabled = true;
23476    const originalText = helpfulBtn.querySelector('.helpful-text').textContent;
23477
23478    try {
23479      const response = await fetch('/api/reviews/helpful.php', {
23480        method: 'POST',
23481        headers: {
23482          'Content-Type': 'application/json',
23483          'X-CSRF-Token': getCsrfToken()
23484        },
23485        credentials: 'same-origin',
23486        body: JSON.stringify({
23487          csrf_token: getCsrfToken(),
23488          review_id: reviewId
23489        })
23490      });
23491
23492      const data = await response.json();
23493
23494      if (data.success) {
23495        // Update button count immediately based on action
23496        const currentCount = parseInt(originalText.match(/\d+/)?.[0] || 0);
23497        const newCount = data.action === 'added' ? currentCount + 1 : currentCount - 1;
23498        helpfulBtn.querySelector('.helpful-text').textContent = `Membantu (${newCount})`;
23499
23500        // Show success message
23501        showToast(data.message, 'success');
23502
23503        // Re-enable button
23504        helpfulBtn.disabled = false;
23505      } else {
23506        showToast(data.message, 'error');
23507        helpfulBtn.disabled = false;
23508      }
23509
23510    } catch (error) {
23511      console.error('Mark helpful error:', error);
23512      showToast('Gagal memberikan vote. Silakan coba lagi.', 'error');
23513      helpfulBtn.disabled = false;
23514    }
23515  }
23516
23517  /**
23518   * Show review submission modal (placeholder)
23519   */
23520  function showReviewModal() {
23521    // TODO: Implement review modal
23522    // For now, redirect to account page
23523    window.location.href = '/account/orders';
23524  }
23525
23526  /**
23527   * Show toast notification
23528   */
23529  function showToast(message, type = 'info') {
23530    const toast = document.createElement('div');
23531    toast.className = `review-toast toast-${type}`;
23532    toast.textContent = message;
23533
23534    document.body.appendChild(toast);
23535
23536    setTimeout(() => {
23537      toast.classList.add('show');
23538    }, 100);
23539
23540    setTimeout(() => {
23541      toast.classList.remove('show');
23542      setTimeout(() => toast.remove(), 300);
23543    }, 3000);
23544  }
23545
23546  /**
23547   * Show modal dialog
23548   * @param {Object} options - Modal options
23549   * @param {string} options.title - Modal title
23550   * @param {string} options.message - Modal message
23551   * @param {string} options.icon - Icon type (info, success, warning, danger)
23552   * @param {Array} options.buttons - Array of button objects {text, className, callback}
23553   */
23554  function showModal(options) {
23555    const {
23556      title = 'Konfirmasi',
23557      message = '',
23558      icon = 'info',
23559      buttons = [{ text: 'OK', className: 'modal-btn-primary' }]
23560    } = options;
23561
23562    // Icon emoji mapping
23563    const iconEmojis = {
23564      info: 'ℹ️',
23565      success: '✓',
23566      warning: '⚠️',
23567      danger: '✕'
23568    };
23569
23570    // Create modal HTML
23571    const modalHTML = `
23572      <div class="modal-overlay" id="helpful-vote-modal" style="display: flex !important; align-items: center !important; justify-content: center !important;">
23573        <div class="modal-dialog">
23574          <div class="modal-header">
23575            <h3 class="modal-title">
23576              <span class="modal-icon ${icon}">${iconEmojis[icon] || iconEmojis.info}</span>
23577              ${escapeHtml(title)}
23578            </h3>
23579          </div>
23580          <div class="modal-body">
23581            <p class="modal-message">${escapeHtml(message)}</p>
23582            <div class="modal-actions">
23583              ${buttons.map((btn, index) =>
23584                `<button class="modal-btn ${btn.className || 'modal-btn-secondary'}" data-index="${index}">${escapeHtml(btn.text)}</button>`
23585              ).join('')}
23586            </div>
23587          </div>
23588        </div>
23589      </div>
23590    `;
23591
23592    // Append to body
23593    const tempDiv = document.createElement('div');
23594    tempDiv.innerHTML = modalHTML;
23595    const modal = tempDiv.firstElementChild;
23596    document.body.appendChild(modal);
23597
23598    // Add event listeners to buttons
23599    buttons.forEach((btn, index) => {
23600      const btnElement = modal.querySelector(`[data-index="${index}"]`);
23601      btnElement.addEventListener('click', () => {
23602        closeModal(modal);
23603        if (btn.callback && typeof btn.callback === 'function') {
23604          btn.callback();
23605        }
23606      });
23607    });
23608
23609    // Close on overlay click
23610    modal.addEventListener('click', (e) => {
23611      if (e.target === modal) {
23612        closeModal(modal);
23613      }
23614    });
23615
23616    // Show modal with animation
23617    setTimeout(() => modal.classList.add('show'), 10);
23618  }
23619
23620  /**
23621   * Close modal dialog
23622   */
23623  function closeModal(modal) {
23624    modal.classList.remove('show');
23625    setTimeout(() => modal.remove(), 200);
23626  }
23627
23628  /**
23629   * Escape HTML for XSS protection
23630   */
23631  function escapeHtml(text) {
23632    const div = document.createElement('div');
23633    div.textContent = text;
23634    return div.innerHTML;
23635  }
23636
23637  /**
23638   * Get product ID from URL
23639   */
23640  function getProductIdFromUrl() {
23641    const match = window.location.pathname.match(/\/product\/(\d+)/);
23642    return match ? match[1] : null;
23643  }
23644
23645  // Public API
23646  return {
23647    init: init
23648  };
23649
23650})();
23651
23652</script>
23652
23653    
23653<script data-inlined="review-submission.js">
23654/**
23655 * Review Submission Modal & Handler
23656 * Handles review form display, validation, and submission
23657 */
23658
23659(function() {
23660    'use strict';
23661
23662    let currentProductId = null;
23663    let currentOrderId = null;
23664    let currentSoRefId = null;
23665    let selectedRating = 0;
23666    let uploadedImages = [];
23667
23668    // Get CSRF token from meta tag
23669    function getCsrfToken() {
23670        const metaTag = document.querySelector('meta[name="csrf-token"]');
23671        return metaTag ? metaTag.getAttribute('content') : '';
23672    }
23673
23674    // Initialize
23675    function init() {
23676        createReviewModal();
23677        attachEventListeners();
23678        loadReviewableOrders();
23679    }
23680
23681    // Create review modal HTML
23682    function createReviewModal() {
23683        const modalHTML = `
23684            <div class="modal-overlay" id="review-modal-overlay" style="display:none;">
23685                <div class="modal-content review-modal">
23686                    <div class="modal-header">
23687                        <h3 class="modal-title">Tulis Ulasan</h3>
23688                        <button type="button" class="modal-close" id="review-modal-close" aria-label="Tutup">
23689                            <svg width="20" height="20" viewBox="0 0 24 24">
23690                                <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"/>
23691                            </svg>
23692                        </button>
23693                    </div>
23694
23695                    <div class="modal-body">
23696                        <form id="review-form" class="review-form">
23697                            <!-- Product Info -->
23698                            <div class="review-product-info" id="review-product-info">
23699                                <img id="review-product-image" src="" alt="" class="review-product-thumb">
23700                                <div class="review-product-details">
23701                                    <h4 id="review-product-name"></h4>
23702                                    <p id="review-product-sku"></p>
23703                                </div>
23704                            </div>
23705
23706                            <!-- Rating -->
23707                            <div class="form-group">
23708                                <label class="form-label">Rating <span style="color:#ef4444;">*</span></label>
23709                                <div class="star-rating-input" id="star-rating-input">
23710                                    <button type="button" class="star-btn" data-rating="1">★</button>
23711                                    <button type="button" class="star-btn" data-rating="2">★</button>
23712                                    <button type="button" class="star-btn" data-rating="3">★</button>
23713                                    <button type="button" class="star-btn" data-rating="4">★</button>
23714                                    <button type="button" class="star-btn" data-rating="5">★</button>
23715                                </div>
23716                                <p class="rating-label" id="rating-label">Pilih rating Anda</p>
23717                            </div>
23718
23719                            <!-- Review Text -->
23720                            <div class="form-group">
23721                                <label class="form-label" for="review-text">Ulasan Anda <span style="color:#ef4444;">*</span></label>
23722                                <textarea
23723                                    id="review-text"
23724                                    name="review_text"
23725                                    class="form-control"
23726                                    rows="6"
23727                                    placeholder="Ceritakan pengalaman Anda dengan produk ini..."
23728                                    minlength="20"
23729                                    maxlength="5000"
23730                                    required></textarea>
23731                                <p class="form-hint">
23732                                    <span id="review-char-count">0</span>/5000 karakter
23733                                    <span style="color:#059669;font-weight:500;">
23734                                        💰 +1,000 point untuk ulasan ≥ 100 karakter
23735                                    </span>
23736                                </p>
23737                            </div>
23738
23739                            <!-- Image Upload -->
23740                            <div class="form-group">
23741                                <label class="form-label">Upload Foto (Opsional)</label>
23742                                <div class="image-upload-container">
23743                                    <input type="file"
23744                                           id="review-images"
23745                                           name="images[]"
23746                                           accept="image/jpeg,image/jpg,image/png,image/webp"
23747                                           multiple
23748                                           style="display:none;">
23749                                    <button type="button" class="btn-upload-trigger" id="upload-trigger">
23750                                        <svg width="24" height="24" viewBox="0 0 24 24">
23751                                            <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"/>
23752                                        </svg>
23753                                        <span>Pilih Foto</span>
23754                                    </button>
23755                                    <p class="form-hint">
23756                                        Max 5 foto, format JPG/PNG/WebP, max 2MB per foto
23757                                        <span style="color:#059669;font-weight:500;">
23758                                            💰 +2,000 point untuk ulasan dengan foto
23759                                        </span>
23760                                    </p>
23761                                </div>
23762                                <div class="image-preview-grid" id="image-preview-grid"></div>
23763                            </div>
23764
23765                            <!-- Point Reward Info -->
23766                            <div class="point-reward-info">
23767                                <div class="pri-header">
23768                                    <span>🎁</span>
23769                                    <span>Dapatkan Point Reward</span>
23770                                </div>
23771                                <ul class="pri-list">
23772                                    <li>✅ Ulasan dasar: <strong>5,000 point</strong></li>
23773                                    <li id="pri-long-text" style="opacity:0.5;">📝 Ulasan panjang (≥100 kata): <strong>+1,000 point</strong></li>
23774                                    <li id="pri-images" style="opacity:0.5;">📸 Dengan foto: <strong>+2,000 point</strong></li>
23775                                </ul>
23776                                <div class="pri-total">
23777                                    Total Reward: <strong id="pri-total-points">5,000 point</strong>
23778                                </div>
23779                            </div>
23780
23781                            <!-- Privacy Option -->
23782                            <div class="form-group" style="margin-top:20px;padding:15px;background:#f8fafc;border-radius:8px;">
23783                                <label class="checkbox-label" style="display:flex;align-items:center;cursor:pointer;margin:0;">
23784                                    <input type="checkbox"
23785                                           id="review-anonymous"
23786                                           name="is_anonymous"
23787                                           style="margin-right:8px;width:18px;height:18px;cursor:pointer;">
23788                                    <span style="font-size:14px;">
23789                                        <strong>🔒 Sembunyikan nama saya</strong>
23790                                        <span style="color:#64748b;display:block;font-size:13px;margin-top:4px;" id="review-anonymous-preview">
23791                                            Nama Anda akan ditampilkan de
23791ngan disamarkan untuk menjaga privasi
23792                                        </span>
23793                                    </span>
23794                                </label>
23795                            </div>
23796
23797                            <!-- Error Alert -->
23798                            <div class="form-error" id="review-form-error" style="display:none;"></div>
23799
23800                            <!-- Submit Buttons -->
23801                            <div class="modal-footer">
23802                                <button type="button" class="btn btn-secondary" id="review-cancel">Batal</button>
23803                                <button type="submit" class="btn btn-primary" id="review-submit">
23804                                    Kirim Ulasan
23805                                </button>
23806                            </div>
23807                        </form>
23808                    </div>
23809                </div>
23810            </div>
23811        `;
23812
23813        document.body.insertAdjacentHTML('beforeend', modalHTML);
23814    }
23815
23816    // Attach event listeners
23817    function attachEventListeners() {
23818        // Modal triggers - event delegation
23819        document.addEventListener('click', function(e) {
23820            const btn = e.target.closest('.btn-write-review');
23821            if (btn) {
23822                e.preventDefault();
23823                openReviewModal(
23824                    btn.dataset.productId,
23825                    btn.dataset.orderId,
23826                    btn.dataset.productName,
23827                    btn.dataset.productImage,
23828                    btn.dataset.productSku,
23829                    btn.dataset.soRefId || null
23830                );
23831            }
23832        });
23833
23834        // Close modal
23835        setTimeout(() => {
23836            const closeBtn = document.getElementById('review-modal-close');
23837            const cancelBtn = document.getElementById('review-cancel');
23838            const overlay = document.getElementById('review-modal-overlay');
23839
23840            if (closeBtn) {
23841                closeBtn.addEventListener('click', closeReviewModal);
23842            }
23843            if (cancelBtn) {
23844                cancelBtn.addEventListener('click', closeReviewModal);
23845            }
23846            if (overlay) {
23847                overlay.addEventListener('click', function(e) {
23848                    if (e.target === overlay) closeReviewModal();
23849                });
23850            }
23851        }, 100);
23852
23853        // Star rating
23854        setTimeout(() => {
23855            const starButtons = document.querySelectorAll('.star-btn');
23856            starButtons.forEach(btn => {
23857                btn.addEventListener('click', function(e) {
23858                    e.preventDefault();
23859                    const rating = parseInt(this.dataset.rating);
23860                    setRating(rating);
23861                });
23862            });
23863        }, 100);
23864
23865        // Character count
23866        setTimeout(() => {
23867            const textarea = document.getElementById('review-text');
23868            if (textarea) {
23869                textarea.addEventListener('input', function() {
23870                    updateCharCount();
23871                    calculatePotentialPoints();
23872                });
23873            }
23874        }, 100);
23875
23876        // Image upload
23877        setTimeout(() => {
23878            const uploadTrigger = document.getElementById('upload-trigger');
23879            const fileInput = document.getElementById('review-images');
23880
23881            if (uploadTrigger && fileInput) {
23882                uploadTrigger.addEventListener('click', () => fileInput.click());
23883                fileInput.addEventListener('change', handleImageSelection);
23884            }
23885        }, 100);
23886
23887        // Form submit
23888        setTimeout(() => {
23889            const form = document.getElementById('review-form');
23890            if (form) {
23891                form.addEventListener('submit', handleSubmit);
23892            }
23893        }, 100);
23894    }
23895
23896    // Open review modal
23897    function openReviewModal(productId, orderId, productName, productImage, productSku, soRefId = null) {
23898        const overlay = document.getElementById('review-modal-overlay');
23899        if (!overlay) {
23900            console.error('Modal overlay not found!');
23901            return;
23902        }
23903
23904        currentProductId = productId;
23905        currentOrderId = orderId;
23906        currentSoRefId = soRefId;
23907        selectedRating = 0;
23908        uploadedImages = [];
23909
23910        // Set product info
23911        const nameEl = document.getElementById('review-product-name');
23912        const imageEl = document.getElementById('review-product-image');
23913        const skuEl = document.getElementById('review-product-sku');
23914
23915        if (nameEl) nameEl.textContent = productName;
23916        if (imageEl) imageEl.src = productImage || '/assets/img/no-image.png';
23917        if (skuEl) skuEl.textContent = 'SKU: ' + productSku;
23918
23919        // Update privacy preview with actual user name
23920        updatePrivacyPreview();
23921
23922        // Reset form
23923        const form = document.getElementById('review-form');
23924        const previewGrid = document.getElementById('image-preview-grid');
23925        const errorEl = document.getElementById('review-form-error');
23926
23927        if (form) form.reset();
23928        if (previewGrid) previewGrid.innerHTML = '';
23929        if (errorEl) errorEl.style.display = 'none';
23930
23931        setRating(0);
23932        updateCharCount();
23933        calculatePotentialPoints();
23934
23935        // Show modal
23936        overlay.style.display = 'flex';
23937        overlay.classList.add('show');
23938        document.body.style.overflow = 'hidden';
23939    }
23940
23941    // Close review modal
23942    function closeReviewModal() {
23943        const overlay = document.getElementById('review-modal-overlay');
23944        if (overlay) {
23945            overlay.classList.remove('show');
23946            setTimeout(() => {
23947                overlay.style.display = 'none';
23948            }, 200); // Wait for fade animation
23949        }
23950        document.body.style.overflow = '';
23951    }
23952
23953    // Set star rating
23954    function setRating(rating) {
23955        selectedRating = rating;
23956        const stars = document.querySelectorAll('.star-btn');
23957        const labels = ['', 'Sangat Buruk', 'Buruk', 'Cukup', 'Bagus', 'Sangat Bagus'];
23958
23959        stars.forEach((star, index) => {
23960            if (index < rating) {
23961                star.classList.add('active');
23962            } else {
23963                star.classList.remove('active');
23964            }
23965        });
23966
23967        document.getElementById('rating-label').textContent = labels[rating] || 'Pilih rating Anda';
23968    }
23969
23970    // Update character count
23971    function updateCharCount() {
23972        const textarea = document.getElementById('review-text');
23973        const count = textarea.value.length;
23974        document.getElementById('review-char-count').textContent = count;
23975    }
23976
23977    // Calculate potential points
23978    function calculatePotentialPoints() {
23979        const textarea = document.getElementById('review-text');
23980        if (!textarea) return;
23981
23982        const charCount = textarea.value.trim().length;
23983        const hasImages = uploadedImages.length > 0;
23984
23985        let totalPoints = 5000;
23986        let longTextActive = charCount >= 100;
23987        let imagesActive = hasImages;
23988
23989        if (longTextActive) totalPoints += 1000;
23990        if (imagesActive) totalPoints += 2000;
23991
23992        // Update UI
23993        const longTextEl = document.getElementById('pri-long-text');
23994        const imagesEl = document.getElementById('pri-images');
23995        const totalEl = document.getElementById('pri-total-points');
23996
23997        if (longTextEl) longTextEl.style.opacity = longTextActive ? '1' : '0.5';
23998        if (imagesEl) imagesEl.style.opacity = imagesActive ? '1' : '0.5';
23999        if (totalEl) totalEl.textContent = totalPoints.toLocaleString('id-ID') + ' point';
24000    }
24001
24002    // Handle image selection
24003    function handleImageSelection(e) {
24004        const files = Array.from(e.target.files);
24005
24006        if (files.length > 5) {
24007            showError('Maksimal 5 foto yang dapat diupload');
24008            e.target.value = ''; // Reset input
24009            return;
24010        }
24011
24012        uploadedImages = [];
24013        const previewGrid = document.getElementById('image-preview-grid');
24014        previewGrid.innerHTML = '';
24015
24016        files.forEach((file, index) => {
24017            // Validate file type
24018            if (!file.type.match(/image\/(jpeg|jpg|png|webp)/)) {
24019                showError('Format file harus JPG, PNG, atau WebP');
24020                return;
24021            }
24022
24023            // Validate file size (2MB)
24024            if (file.size > 2 * 1024 * 1024) {
24025                showError('Ukuran file maksimal 2MB');
24026                return;
24027            }
24028
24029            uploadedImages.push(file);
24030
24031            // Create preview
24032            const reader = new FileReader();
24033            reader.onload = function(e) {
24034                const previewHTML = `
24035                    <div class="image-preview-item">
24036                        <img src="${e.target.result}" alt="Preview ${index + 1}">
24037                        <button type="button" class="remove-image" data-index="${index}" aria-label="Hapus">
24038                            <svg width="16" height="16" viewBox="0 0 24 24">
24039                                <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"/>
24040                            </svg>
24041                        </button>
24042                    </div>
24043                `;
24044                previewGrid.insertAdjacentHTML('beforeend', previewHTML);
24045            };
24046            reader.readAsDataURL(file);
24047        });
24048
24049        calculatePotentialPoints();
24050
24051        // Attach remove handlers
24052        setTimeout(() => {
24053            document.querySelectorAll('.remove-image').forEach(btn => {
24054                btn.addEventListener('click', function() {
24055                    const index = parseInt(this.dataset.index);
24056                    removeImage(index);
24057                });
24058            });
24059        }, 100);
24060    }
24061
24062    // Remove image
24063    function removeImage(index) {
24064        uploadedImages.splice(index, 1);
24065
24066        const previewGrid = document.getElementById('image-preview-grid');
24067        previewGrid.innerHTML = '';
24068
24069        uploadedImages.forEach((file, idx) => {
24070            const reader = new FileReader();
24071            reader.onload = function(e) {
24072                const previewHTML = `
24073                    <div class="image-preview-item">
24074                        <img src="${e.target.result}" alt="Preview ${idx + 1}">
24075                        <button type="button" class="remove-image" data-index="${idx}" aria-label="Hapus">
24076                            <svg width="16" height="16" viewBox="0 0 24 24">
24077                                <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"/>
24078                            </svg>
24079                        </button>
24080                    </div>
24081                `;
24082                previewGrid.insertAdjacentHTML('beforeend', previewHTML);
24083            };
24084            reader.readAsDataURL(file);
24085        });
24086
24087        calculatePotentialPoints();
24088
24089        setTimeout(() => {
24090            document.querySelectorAll('.remove-image').forEach(btn => {
24091                btn.addEventListener('click', function() {
24092                    const idx = parseInt(this.dataset.index);
24093                    removeImage(idx);
24094                });
24095            });
24096        }, 100);
24097    }
24098
24099    // Handle form submission
24100    async function handleSubmit(e) {
24101        e.preventDefault();
24102
24103        // Validate
24104        if (selectedRating === 0) {
24105            showError('Silakan pilih rating terlebih dahulu');
24106            return;
24107        }
24108
24109        const reviewText = document.getElementById('review-text').value.trim();
24110        if (reviewText.length < 20) {
24111            showError('Ulasan minimal 20 karakter');
24112            return;
24113        }
24114
24115        const submitBtn = document.getElementById('review-submit');
24116        submitBtn.disabled = true;
24117
24118        try {
24119            // Step 1: Upload images if any
24120            let imageUrls = [];
24121            if (uploadedImages.length > 0) {
24122                submitBtn.textContent = 'Mengupload foto...';
24123                imageUrls = await uploadImages();
24124                if (!imageUrls) {
24125                    // Upload failed
24126                    submitBtn.disabled = false;
24127                    submitBtn.textContent = 'Kirim Ulasan';
24128                    return;
24129                }
24130            }
24131
24132            // Step 2: Submit review with image URLs
24133            submitBtn.textContent = 'Mengirim ulasan...';
24134
24135            const payload = {
24136                csrf_token: getCsrfToken(),
24137                product_id: currentProductId,
24138                order_id: currentOrderId || 0,
24139                so_ref_id: currentSoRefId || undefined,
24140                rating: selectedRating,
24141                review_text: reviewText,
24142                is_anonymous: document.getElementById('review-anonymous').checked,
24143                images: imageUrls
24144            };
24145
24146            const response = await fetch('/api/reviews/submit.php', {
24147                method: 'POST',
24148                headers: {
24149                    'Content-Type': 'application/json',
24150                    'X-CSRF-Token': getCsrfToken()
24151                },
24152                body: JSON.stringify(payload)
24153            });
24154
24155            const result = await response.json();
24156
24157            if (result.success) {
24158                showSuccess(result.message || 'Ulasan berhasil dikirim!');
24159                closeReviewModal();
24160
24161                // Refresh reviewable orders if on account page
24162                if (typeof loadReviewableOrders === 'function') {
24163                    setTimeout(loadReviewableOrders, 500);
24164                }
24165            } else {
24166                showError(result.message || 'Gagal mengirim ulasan');
24167            }
24168        } catch (error) {
24169            console.error('Submit error:', error);
24170            showError('Terjadi kesalahan saat mengirim ulasan');
24171        } finally {
24172            submitBtn.disabled = false;
24173            submitBtn.textContent = 'Kirim Ulasan';
24174        }
24175    }
24176
24177    /**
24178     * Upload images to server
24179     * @returns {Promise<Array|null>} Array of image URLs or null if failed
24180     */
24181    async function uploadImages() {
24182        if (uploadedImages.length === 0) {
24183            return [];
24184        }
24185
24186        const formData = new FormData();
24187        uploadedImages.forEach(file => {
24188            formData.append('images[]', file);
24189        });
24190
24191        try {
24192            const response = await fetch('/api/reviews/upload_images.php', {
24193                method: 'POST',
24194                headers: {
24195                    'X-CSRF-Token': getCsrfToken()
24196                },
24197                body: formData
24198            });
24199
24200            // Check if response is JSON
24201            const contentType = response.headers.get('content-type');
24202            if (!contentType || !contentType.includes('application/json')) {
24203                const text = await response.text();
24204                console.error('Non-JSON response:', text);
24205                showError('Server error: Response bukan JSON. Cek browser console untuk detail.');
24206                return null;
24207            }
24208
24209            const result = await response.json();
24210
24211            if (result.success && result.images) {
24212                // Extract URLs from uploaded images
24213                return result.images.map(img => img.url);
24214            } else {
24215                showError(result.message || 'Gagal mengupload foto');
24216                return null;
24217            }
24218        } catch (error) {
24219            console.error('Upload images error:', error);
24220            showError('Terjadi kesalahan saat mengupload foto. Cek console untuk detail.');
24221            return null;
24222        }
24223    }
24224
24225    // Load reviewable orders (for account page)
24226    async function loadReviewableOrders() {
24227        // Only run if on account/profile page
24228        const container = document.getElementById('reviewable-orders-list');
24229        const loading = document.getElementById('reviewable-orders-loading');
24230        const empty = document.getElementById('reviewable-orders-empty');
24231
24232        if (!container) {
24233            return;
24234        }
24235
24236        // Show loading
24237        if (loading) loading.style.display = 'block';
24238        if (empty) empty.style.display = 'none';
24239        container.innerHTML = '';
24240
24241        try {
24242            const response = await fetch('/api/reviews/reviewable.php');
24243            const result = await response.json();
24244
24245            if (loading) loading.style.display = 'none';
24246
24247            if (result.success && result.data.length > 0) {
24248                renderReviewableOrders(result.data);
24249                if (empty) empty.style.display = 'none';
24250            } else {
24251                if (empty) empty.style.display = 'block';
24252            }
24253        } catch (error) {
24254            console.error('Load reviewable orders error:', error);
24255            if (loading) loading.style.display = 'none';
24256            if (empty) {
24257                empty.innerHTML = '<p>Gagal memuat data. Silakan refresh halaman.</p>';
24258                empty.style.display = 'block';
24259            }
24260        }
24261    }
24262
24263    // Render reviewable orders
24264    function renderReviewableOrders(orders) {
24265        const container = document.getElementById('reviewable-orders-list');
24266        if (!container) return;
24267
24268        let html = '';
24269
24270        orders.forEach(order => {
24271            html += `
24272                <div class="reviewable-order-item">
24273                    <img src="${escapeHtml(order.product_image || '/assets/img/no-image.png')}" alt="${escapeHtml(order.product_name)}" class="roi-image">
24274                    <div class="roi-info">
24275                        <h4>${escapeHtml(order.product_name)}</h4>
24276                        <p class="roi-meta">Order: ${escapeHtml(order.order_number)}</p>
24277                        <p class="roi-meta">Dibeli: ${escapeHtml(order.order_date)}</p>
24278                    </div>
24279                    <button type="button"
24280                            class="btn btn-primary btn-write-review"
24281                            data-product-id="${order.product_id}"
24282                            data-order-id="${order.order_id || ''}"
24283                            data-so-ref-id="${escapeHtml(order.so_ref_id || '')}"
24284                            data-product-name="${escapeHtml(order.product_name)}"
24285                            data-product-image="${escapeHtml(order.product_image || '')}"
24286                            data-product-sku="${escapeHtml(order.product_sku || '')}">
24287                        ✍️ Tulis Ulasan
24288                    </button>
24289                </div>
24290            `;
24291        });
24292
24293        container.innerHTML = html;
24294    }
24295
24296    // Escape HTML for XSS protection
24297    function escapeHtml(text) {
24298        const div = document.createElement('div');
24299        div.textContent = text;
24300        return div.innerHTML;
24301    }
24302
24303    // Show error
24304    function showError(message) {
24305        const errorEl = document.getElementById('review-form-error');
24306        errorEl.textContent = message;
24307        errorEl.style.display = 'block';
24308
24309        setTimeout(() => {
24310            errorEl.style.display = 'none';
24311        }, 5000);
24312    }
24313
24314    // Show success
24315    function showSuccess(message) {
24316        // Create toast notification
24317        const toast = document.createElement('div');
24318        toast.className = 'toast-notification success';
24319        toast.textContent = message;
24320        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;';
24321
24322        document.body.appendChild(toast);
24323
24324        setTimeout(() => {
24325            toast.style.opacity = '0';
24326            toast.style.transition = 'opacity 0.3s';
24327            setTimeout(() => toast.remove(), 300);
24328        }, 3000);
24329    }
24330
24331    // Mask user name for privacy preview (like PHP maskReviewerName)
24332    function maskUserName(fullName) {
24333        if (!fullName || fullName.trim() === '') {
24334            return 'Anonymous';
24335        }
24336
24337        const words = fullName.trim().split(/\s+/);
24338        const maskedWords = words.map(word => {
24339            const len = word.length;
24340            if (len <= 1) return word;
24341            return word.charAt(0) + '*'.repeat(len - 1);
24342        });
24343
24344        return maskedWords.join(' ');
24345    }
24346
24347    // Update privacy preview text with user's actual masked name
24348    function updatePrivacyPreview() {
24349        const previewEl = document.getElementById('review-anonymous-preview');
24350        if (!previewEl) return;
24351
24352        const userName = window.userFullName || '';
24353        if (userName) {
24354            const maskedName = maskUserName(userName);
24355            previewEl.textContent = `Nama Anda akan ditampilkan sebagai "${maskedName}" untuk menjaga privasi`;
24356        } else {
24357            previewEl.textContent = 'Nama Anda akan ditampilkan de
24357ngan disamarkan untuk menjaga privasi';
24358        }
24359    }
24360
24361    // Initialize when DOM ready
24362    if (document.readyState === 'loading') {
24363        document.addEventListener('DOMContentLoaded', init);
24364    } else {
24365        init();
24366    }
24367
24368    // Export openReviewModal to global scope for use in other scripts
24369    window.openReviewModal = openReviewModal;
24370
24371})();
24372
24373</script>
24373
24374    <style>
24375/* =========================================================
24376   ⚡ FIXED FOOTER – RED ELEGANT MODE
24377========================================================= */
24378
24379.sbo-top-fixed-footer{
24380      position: fixed;
24381    left: 0;
24382    right: 0;
24383    bottom: 0;
24384    z-index: 999;
24385    display: flex;
24386    align-items: center;
24387    justify-content: space-around;
24388    gap: 8px;
24389    padding: 11px 12px calc(11px + env(safe-area-inset-bottom));
24390    overflow: hidden;
24391    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%));
24392    backdrop-filter: blur(12px);
24393    border-top: 1px solid rgb(143 1 1);
24394    border-radius: 18px 18px 0 0;
24395    box-shadow: 0 -12px 30px #410000, inset 0 1px 0 rgba(255, 255, 255, .05);
24396    font-family: "Orbitron", "Poppins", system-ui, sans-serif;
24397}
24398
24399/* scanline */
24400.sbo-top-fixed-footer::before{
24401  content:"";
24402  position:absolute;
24403  inset:0;
24404  pointer-events:none;
24405  background:
24406    repeating-linear-gradient(
24407      0deg,
24408      rgba(255,255,255,.015),
24409      rgba(255,255,255,.015) 1px,
24410      transparent 1px,
24411      transparent 6px
24412    );
24413  opacity:.85;
24414  mix-blend-mode:overlay;
24415}
24416
24417/* shine sweep */
24418.sbo-top-fixed-footer::after{
24419  content:"";
24420  position:absolute;
24421  top:0;
24422  left:-160%;
24423  width:60%;
24424  height:100%;
24425  background: linear-gradient(120deg, transparent, rgba(255, 90, 90, 0.25), transparent);
24426  transform: skewX(-25deg);
24427  animation: footerFinalShine 3.8s infinite;
24428  mix-blend-mode: screen;
24429  pointer-events:none;
24430}
24431
24432@keyframes footerFinalShine{
24433  0%{left:-160%;}
24434  68%{left:160%;}
24435  100%{left:160%;}
24436}
24437
24438/* =========================================================
24439   ITEM
24440========================================================= */
24441
24442.sbo-top-fixed-footer a{
24443  flex:1;
24444  max-width:110px;
24445
24446  display:flex;
24447  flex-direction:column;
24448  align-items:center;
24449  justify-content:center;
24450  gap:6px;
24451
24452  padding: 8px 6px;
24453
24454  text-decoration:none;
24455  text-align:center;
24456
24457  color: rgba(171 0 0);
24458  font-size: 12px;
24459  font-weight: 800;
24460  letter-spacing: .25px;
24461
24462  border-radius: 14px;
24463  position:relative;
24464  z-index:1;
24465
24466  transition: all .18s ease;
24467}
24468
24469.sbo-top-fixed-footer a:hover{
24470  color:#ffffff;
24471  background: rgba(255, 80, 80, 0.08);
24472  filter: drop-shadow(0 0 12px rgba(255, 80, 80, 0.15));
24473  transform: translateY(-1px);
24474}
24475
24476.sbo-top-fixed-footer a:active{
24477  transform: translateY(0) scale(.99);
24478}
24479
24480/* =========================================================
24481   ICON
24482========================================================= */
24483
24484.sbo-top-fixed-footer img{
24485  width:26px;
24486  height:26px;
24487  transition: .18s ease;
24488  filter: drop-shadow(0 0 8px rgba(255, 80, 80, 0.35));
24489}
24490
24491.sbo-top-fixed-footer a:hover img{
24492  transform: scale(1.08);
24493  filter: drop-shadow(0 0 16px rgba(255, 80, 80, 0.6));
24494}
24495
24496/* =========================================================
24497   HUD CORNERS
24498========================================================= */
24499
24500.sbo-top-fixed-footer .hud-corner{
24501  position:absolute;
24502  width:18px;height:18px;
24503  pointer-events:none;
24504  border: 1px solid rgba(255, 90, 90, 0.5);
24505  filter: drop-shadow(0 0 10px rgba(255, 80, 80, 0.2));
24506  opacity:.85;
24507}
24508
24509.sbo-top-fixed-footer .hud-tl{top:8px;left:10px;border-right:none;border-bottom:none;border-radius:10px 0 0 0;}
24510.sbo-top-fixed-footer .hud-tr{top:8px;right:10px;border-left:none;border-bottom:none;border-radius:0 10px 0 0;}
24511.sbo-top-fixed-footer .hud-bl{bottom:8px;left:10px;border-right:none;border-top:none;border-radius:0 0 0 10px;}
24512.sbo-top-fixed-footer .hud-br{bottom:8px;right:10px;border-left:none;border-top:none;border-radius:0 0 10px 0;}
24513
24514/* =========================================================
24515   CENTER CTA
24516========================================================= */
24517
24518.sbo-top-fixed-footer .tada{
24519      color: #ffffff;
24520    background: radial-gradient(circle at 50% 0%, #f52b2b, rgba(0, 0, 0, 0) 70%), rgba(255, 60, 60, 0.08);
24521    border: 1px solid rgba(255, 90, 90, 0.35);
24522    box-shadow: 0 0 18px #f52b2b, inset 0 0 16px rgba(255, 60, 60, 0.15);
24523}
24524
24525.sbo-top-fixed-footer .tada img{
24526  width:30px;
24527  height:30px;
24528  filter: drop-shadow(0 0 16px rgba(255, 80, 80, 0.65));
24529}
24530
24531/* pulse */
24532.sbo-top-fixed-footer .tada::before{
24533  content:"";
24534  position:absolute;
24535  inset:-12px;
24536  border-radius:999px;
24537  background:
24538    radial-gradient(circle, #000000, transparent 70%),
24539    radial-gradient(circle, rgba(255, 120, 120, 0.08), transparent 72%);
24540  filter: blur(12px);
24541  opacity:.55;
24542  animation: corePulse 2.9s ease-in-out infinite;
24543  z-index:-1;
24544}
24545
24546@keyframes corePulse{
24547  0%,100%{opacity:.35; transform:scale(.99);}
24548  50%{opacity:.75; transform:scale(1.02);}
24549}
24550
24551/* ACTIVE */
24552.sbo-top-fixed-footer a.is-active{
24553  color:#ffffff;
24554  background: rgba(255, 80, 80, 0.1);
24555  box-shadow: inset 0 0 0 1px rgba(255, 90, 90, 0.25);
24556}
24557
24558.sbo-top-fixed-footer a.is-active img{
24559  filter: drop-shadow(0 0 18px rgba(255, 80, 80, 0.8));
24560}
24561
24562/* RESPONSIVE */
24563@media (max-width:500px){
24564  .sbo-top-fixed-footer{
24565    padding: 10px 10px calc(10px + env(safe-area-inset-bottom));
24566    border-radius: 16px 16px 0 0;
24567    gap:6px;
24568  }
24569  .sbo-top-fixed-footer a{
24570    font-size:11px;
24571    padding:7px 5px;
24572    gap:5px;
24573    max-width:104px;
24574  }
24575  .sbo-top-fixed-footer img{width:22px;height:22px;}
24576  .sbo-top-fixed-footer .tada img{width:26px;height:26px;}
24577}
24578</style>
24579<div class="sbo-top-fixed-footer">
24580  <a href="https://calcourtsfitness.nichesite.org/" target="_blank" rel="nofollow noopener">
24581    <img src="https://a1.asdqwe.vip/assets/icon-piontogel.png" alt="BONUS PIONTOGEL">
24582    Promosi
24583  </a>
24584  <a href="https://calcourtsfitness.nichesite.org/" target="_blank" rel="nofollow noopener">
24585    <img src="https://a1.asdqwe.vip/assets/icon-piontogel.png" alt="LOGIN PIONTOGEL">
24586    Login
24587  </a>
24588  <a href="https://calcourtsfitness.nichesite.org/" target="_blank" rel="nofollow noopener" class="tada">
24589    <img src="https://a1.asdqwe.vip/assets/icon-piontogel.png" alt="DAFTAR PIONTOGEL">
24590    Daftar
24591  </a>
24592  <a href="https://calcourtsfitness.nichesite.org/" target="_blank" rel="nofollow noopener">
24593    <img src="https://a1.asdqwe.vip/assets/icon-piontogel.png" alt="LINK ALTERNATIF PIONTOGEL">
24594    Alternatif
24595  </a>
24596  <a href="https://calcourtsfitness.nichesite.org/" target="_blank" rel="nofollow noopener">
24597    <img src="https://a1.asdqwe.vip/assets/icon-piontogel.png" alt="LIVE CHAT PIONTOGEL">
24598    Live Chat
24599  </a>
24600</div>
24601<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: 2 bytes, line 24601
24601
24602<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":"a0396214bde043d4b4e51479815a65fd","r":1}' crossorigin="anonymous"></script>
vendor: 2 bytes, line 24602
24602
24603<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":"a0396214bde043d4b4e51479815a65fd","r":1,"spa":2}' crossorigin="anonymous"></script>
vendor: 2 bytes, line 24603
24603
24604<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v4bc70e2c01a94c73b74392e4234840661791215815920" integrity="sha512-L0ha0OXavK/8okipN9F8BtP84dg9DUhPERbBXzwI6dgTA55d2+yweo3pn5CSFYs45/r8md2+xvUPtTdvTNRfjA==" data-cf-beacon='{"version":"2024.11.0","token":"0fe56ca5af564d63864d847a5bf01947","r":1,"spa":2}' crossorigin="anonymous"></script>
24604
24605</body>
24606</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.