<!DOCTYPE html> <html lang="id"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <title>CARLO XITERZ - Premium Store</title> <!-- Fonts: Orbitron (Headings) & Outfit (Body) --> <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;700;900&family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <!-- Tailwind CSS --> <script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.jsdelivr.net/npm/qrcode/build/qrcode.min.js"></script> <!-- Config & Custom Styles --> <script> tailwind.config = { theme: { extend: { fontFamily: { sans: ['Outfit', 'sans-serif'], display: ['Orbitron', 'sans-serif'], }, colors: { dark: '#000000', surface: '#0a0a0a', card: 'rgba(20, 20, 25, 0.6)', primary: '#00f2ff', // Neon Cyan secondary: '#7000ff', // Electric Violet accent: '#ff0055', // Neon Pink success: '#00ff9d', warning: '#ffae00' }, backgroundImage: { 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', }, animation: { 'blob': 'blob 10s infinite', 'spin-slow': 'spin 3s linear infinite', 'pulse-glow': 'pulseGlow 2s cubic-bezier(0.4, 0, 0.6, 1) infinite', 'float': 'float 6s ease-in-out infinite', 'scan': 'scan 2.5s linear infinite', 'shine': 'shine 1.5s infinite', 'slide-up': 'slideUp 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards', }, keyframes: { blob: { '0%': { transform: 'translate(0px, 0px) scale(1)' }, '33%': { transform: 'translate(30px, -50px) scale(1.1)' }, '66%': { transform: 'translate(-20px, 20px) scale(0.9)' }, '100%': { transform: 'translate(0px, 0px) scale(1)' }, }, float: { '0%, 100%': { transform: 'translateY(0)' }, '50%': { transform: 'translateY(-10px)' }, }, scan: { '0%': { top: '0%', opacity: '0' }, '10%': { opacity: '1' }, '90%': { opacity: '1' }, '100%': { top: '100%', opacity: '0' }, }, shine: { '0%': { backgroundPosition: '200% center' }, '100%': { backgroundPosition: '-200% center' }, }, slideUp: { '0%': { opacity: '0', transform: 'translateY(30px)' }, '100%': { opacity: '1', transform: 'translateY(0)' }, }, pulseGlow: { '0%, 100%': { opacity: '1', boxShadow: '0 0 20px rgba(0, 242, 255, 0.3)' }, '50%': { opacity: '.8', boxShadow: '0 0 40px rgba(0, 242, 255, 0.6)' }, } } } } } </script> <style> /* --- GLOBAL RESETS & BASE --- */ body { background-color: #050505; color: #e2e8f0; min-height: 100vh; overflow-x: hidden; /* Noise Texture Overlay */ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)' opacity='0.05'/%3E%3C/svg%3E"); } /* --- ANIMATED BACKGROUND --- */ .ambient-light { position: fixed; width: 600px; height: 600px; background: radial-gradient(circle, rgba(112,0,255,0.15) 0%, rgba(0,0,0,0) 70%); border-radius: 50%; pointer-events: none; z-index: -1; mix-blend-mode: screen; } .ambient-1 { top: -200px; left: -200px; animation: blob 15s infinite alternate; } .ambient-2 { bottom: -200px; right: -200px; animation: blob 12s infinite alternate-reverse; background: radial-gradient(circle, rgba(0,242,255,0.1) 0%, rgba(0,0,0,0) 70%); } .ambient-3 { top: 40%; left: 40%; width: 400px; height: 400px; animation: blob 20s infinite linear; background: radial-gradient(circle, rgba(255,0,85,0.08) 0%, rgba(0,0,0,0) 70%); } /* --- GLASSMORPHISM COMPONENTS --- */ .glass-panel { background: rgba(20, 20, 23, 0.4); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border: 1px solid rgba(255, 255, 255, 0.06); box-shadow: 0 4px 30px rgba(0, 0, 0, 0.5); transition: all 0.3s ease; } .glass-panel:hover { border-color: rgba(255, 255, 255, 0.1); box-shadow: 0 10px 40px rgba(0, 0, 0, 0.6); } /* --- 3D TILT CARD EFFECT --- */ .tilt-card { transform-style: preserve-3d; transform: perspective(1000px); } .tilt-content { transform: translateZ(20px); } /* --- NEON TEXT --- */ .text-glow { text-shadow: 0 0 10px rgba(0, 242, 255, 0.5), 0 0 20px rgba(0, 242, 255, 0.3); } .text-gradient { background: linear-gradient(135deg, #fff 0%, #94a3b8 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } .brand-gradient { background: linear-gradient(to right, #00f2ff, #7000ff); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-size: 200% auto; animation: shine 5s linear infinite; } /* --- CUSTOM SCROLLBAR --- */ ::-webkit-scrollbar { width: 6px; } ::-webkit-scrollbar-track { background: #0a0a0a; } ::-webkit-scrollbar-thumb { background: #333; border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { background: #00f2ff; } /* --- BUTTONS --- */ .btn-cyber { position: relative; background: linear-gradient(90deg, rgba(0,242,255,0.1), rgba(112,0,255,0.1)); border: 1px solid rgba(255,255,255,0.1); overflow: hidden; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .btn-cyber::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); transition: 0.5s; } .btn-cyber:hover { border-color: #00f2ff; box-shadow: 0 0 15px rgba(0, 242, 255, 0.2); transform: translateY(-2px); } .btn-cyber:hover::before { left: 100%; } .btn-primary-grad { background: linear-gradient(135deg, #00f2ff 0%, #0088ff 100%); color: #000; font-weight: 800; box-shadow: 0 4px 15px rgba(0, 242, 255, 0.3); border: none; } .btn-primary-grad:hover { box-shadow: 0 6px 25px rgba(0, 242, 255, 0.5); transform: translateY(-2px) scale(1.02); } .btn-primary-grad:active { transform: translateY(1px); } /* --- FILTERS --- */ .filter-btn { background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.05); transition: all 0.3s ease; } .filter-btn.active, .filter-btn:hover { background: rgba(0, 242, 255, 0.1); border-color: #00f2ff; color: #00f2ff; box-shadow: 0 0 15px rgba(0, 242, 255, 0.1); } /* --- TOAST NOTIFICATIONS --- */ .toast-container { position: fixed; top: 20px; right: 20px; z-index: 9999; display: flex; flex-direction: column; gap: 10px; pointer-events: none; } .toast { pointer-events: auto; background: rgba(10, 10, 10, 0.95); backdrop-filter: blur(10px); border: 1px solid rgba(255,255,255,0.1); border-left: 3px solid; padding: 16px 24px; border-radius: 12px; display: flex; align-items: center; gap: 12px; box-shadow: 0 10px 30px -10px rgba(0,0,0,0.8); animation: slideInRight 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards; min-width: 300px; color: white; } .toast.success { border-color: #00ff9d; box-shadow: 0 0 20px rgba(0, 255, 157, 0.1); } .toast.error { border-color: #ff0055; box-shadow: 0 0 20px rgba(255, 0, 85, 0.1); } @keyframes slideInRight { from { opacity: 0; transform: translateX(50px); } to { opacity: 1; transform: translateX(0); } } /* --- MODALS & BACKDROP --- */ .modal-backdrop { background: rgba(0,0,0,0.8); backdrop-filter: blur(8px); } .modal-enter { animation: modalPop 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; } @keyframes modalPop { 0% { opacity: 0; transform: scale(0.9) translateY(20px); } 100% { opacity: 1; transform: scale(1) translateY(0); } } /* --- QR SCANNER FX --- */ .scanner-wrapper { position: relative; background: #000; padding: 10px; border-radius: 20px; box-shadow: 0 0 30px rgba(0, 242, 255, 0.1); } .scanner-line { position: absolute; top: 0; left: 0; right: 0; height: 2px; background: #00f2ff; box-shadow: 0 0 15px #00f2ff, 0 0 30px #00f2ff; z-index: 10; animation: scan 2s ease-in-out infinite; } .corner { position: absolute; width: 20px; height: 20px; border: 3px solid transparent; transition: all 0.3s; } .corner-tl { top: 0; left: 0; border-top-color: #00f2ff; border-left-color: #00f2ff; border-top-left-radius: 12px; } .corner-tr { top: 0; right: 0; border-top-color: #00f2ff; border-right-color: #00f2ff; border-top-right-radius: 12px; } .corner-bl { bottom: 0; left: 0; border-bottom-color: #00f2ff; border-left-color: #00f2ff; border-bottom-left-radius: 12px; } .corner-br { bottom: 0; right: 0; border-bottom-color: #00f2ff; border-right-color: #00f2ff; border-bottom-right-radius: 12px; } /* --- INPUTS --- */ .input-cyber { background: rgba(0,0,0,0.5); border: 1px solid rgba(255,255,255,0.1); color: white; transition: all 0.3s; } .input-cyber:focus { border-color: #00f2ff; box-shadow: 0 0 0 2px rgba(0, 242, 255, 0.1); outline: none; } #successSection { pointer-events: auto; } #successSection button { pointer-events: auto; position: relative; z-index: 999; } #scanSection.hidden { display: none !important; } </style> </head> <body class="antialiased selection:bg-primary selection:text-black pb-24"> <button onclick="toggleUserMenu()" class=" fixed top-5 left-5 z-50 glass-panel border border-white/10 rounded-2xl px-4 py-3 flex items-center gap-3 hover:border-cyan-500/30 transition-all " > <div class=" w-10 h-10 rounded-xl bg-cyan-500/10 flex items-center justify-center text-cyan-300 font-bold "> <span id="userFirstLetter">U</span> </div> <div class="text-left"> <div id="welcomeUser" class="text-white text-sm font-bold" > Welcome </div> <div id="liveClock" class="text-slate-400 text-xs" > 00:00 </div> </div> <i class="fa-solid fa-chevron-down text-slate-500 text-xs"></i> </button> <div id="userMenu" class=" hidden fixed top-24 left-5 z-50 w-64 glass-panel rounded-3xl border border-white/10 p-4 space-y-3 " > <button onclick="checkHistory()" class="w-full bg-white/5 hover:bg-white/10 rounded-2xl p-4 text-left transition-all" > <div class="text-white font-semibold"> Riwayat Transaksi </div> <div class="text-slate-400 text-sm"> Semua pesanan akun </div> </button> <button onclick="openMyKeys()" class="w-full bg-white/5 hover:bg-white/10 rounded-2xl p-4 text-left transition-all" > <div class="text-white font-semibold"> Cek Key Saya </div> <div class="text-slate-400 text-sm"> Key otomatis akun </div> </button> <button onclick="logoutUser()" class="w-full bg-red-500/10 hover:bg-red-500/20 rounded-2xl p-4 text-left transition-all" > <div class="text-red-400 font-semibold"> Logout </div> </button> </div> </div> <!-- Ambient Background Effects --> <div class="fixed inset-0 pointer-events-none overflow-hidden"> <div class="ambient-light ambient-1"></div> <div class="ambient-light ambient-2"></div> <div class="ambient-light ambient-3"></div> </div> <!-- Toast Container --> <div id="toast-container" class="toast-container"></div> <div class="max-w-7xl mx-auto relative z-10 px-4 sm:px-6 lg:px-8"> <!-- HEADER --> <header class="flex flex-col items-center justify-center pt-12 pb-16 text-center relative"> <!-- Logo --> <div class="relative group cursor-pointer mb-6"> <div class="absolute -inset-1 bg-gradient-to-r from-cyan-400 to-purple-600 rounded-2xl blur opacity-25 group-hover:opacity-50 transition duration-500 animate-pulse"></div> <div class="relative w-24 h-24 glass-panel rounded-2xl flex items-center justify-center border border-white/10 shadow-2xl overflow-hidden"> <img id="storeLogoImg" src="https://via.placeholder.com/100" class="w-full h-full object-cover rounded-2xl" /> </div> </div> <h1 id="storeTitle" class="font-display text-5xl md:text-7xl font-black tracking-tighter mb-3 text-white relative z-10" > CARLO <span class="brand-gradient">STORE</span> </h1> <p class="text-slate-400 text-lg md:text-xl font-light tracking-wide max-w-lg mx-auto mb-8"> Premium Digital Keys & Instant Delivery </p> <!-- History Trigger --> </button> </header> <!-- FILTERS --> <div class="flex flex-col gap-4 mb-12 animate-slide-up"> <!-- Device Filter --> <div class="flex flex-wrap justify-center gap-3" id="deviceFilters"> <button class="device-btn filter-btn active px-5 py-2 rounded-full text-xs font-bold uppercase tracking-widest" data-device="all"> <i class="fa-solid fa-layer-group mr-2"></i>All Devices </button> <button class="device-btn filter-btn px-5 py-2 rounded-full text-xs font-bold uppercase tracking-widest" data-device="android"> <i class="fa-brands fa-android mr-2"></i>Android </button> <button class="device-btn filter-btn px-5 py-2 rounded-full text-xs font-bold uppercase tracking-widest" data-device="ios"> <i class="fa-brands fa-apple mr-2"></i>iOS </button> <button class="device-btn filter-btn px-5 py-2 rounded-full text-xs font-bold uppercase tracking-widest" data-device="pc"> <i class="fa-solid fa-desktop mr-2"></i>PC </button> </div> <!-- Game Filter --> <div class="flex flex-wrap justify-center gap-3"> <button class="game-btn filter-btn active px-5 py-2 rounded-full text-xs font-bold uppercase tracking-widest" data-game="all"> <i class="fa-solid fa-gamepad mr-2"></i>Semua Game </button> <button class="game-btn filter-btn px-5 py-2 rounded-full text-xs font-bold uppercase tracking-widest" data-game="freefire"> <i class="fa-solid fa-fire mr-2"></i>Free Fire </button> <button class="game-btn filter-btn px-5 py-2 rounded-full text-xs font-bold uppercase tracking-widest" data-game="mobilelegends"> <i class="fa-solid fa-crown mr-2"></i>Mobile Legends </button> <button class="game-btn filter-btn px-5 py-2 rounded-full text-xs font-bold uppercase tracking-widest" data-game="pubg"> <i class="fa-solid fa-crosshairs mr-2"></i>PUBG </button> </div> </div> <!-- PRODUCT GRID --> <div id="productGrid" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 perspective-container"> <!-- Loading State --> <div class="col-span-full flex flex-col items-center justify-center py-32 text-slate-600"> <div class="relative w-20 h-20 mb-6"> <div class="absolute inset-0 border-4 border-slate-800 rounded-full"></div> <div class="absolute inset-0 border-4 border-t-cyan-500 border-r-transparent border-b-transparent border-l-transparent rounded-full animate-spin"></div> <i class="fa-solid fa-microchip absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-cyan-500/50 text-xl"></i> </div> <p class="text-sm font-medium tracking-widest animate-pulse uppercase">Connecting to Database...</p> </div> </div> </div> <!-- FOOTER --> <footer class="mt-24 text-center py-12 opacity-40 text-xs text-slate-500 hover:opacity-100 transition-opacity duration-500 font-mono border-t border-white/5"> <p class="mb-2">&copy; 2026 Carlo Store SYSTEM SECURE.</p> <p>Developed with <i class="fa-solid fa-heart text-red-500 animate-pulse"></i> & High Precision</p> </footer> <!-- MODAL VARIAN --> <div id="variantModal" class="hidden fixed inset-0 z-50 flex items-end sm:items-center justify-center"> <div class="absolute inset-0 modal-backdrop transition-opacity duration-300" onclick="closeVariantModal()"></div> <div id="variantModalContent" class="modal-content relative w-full max-w-lg bg-surface border border-white/10 sm:rounded-3xl rounded-t-3xl shadow-2xl transform translate-y-full sm:translate-y-10 opacity-0 transition-all duration-300 overflow-hidden"> <!-- Header Gradient --> <div class="h-2 w-full bg-gradient-to-r from-cyan-400 via-purple-500 to-pink-500"></div> <div class="p-6 sm:p-8 relative z-10"> <button onclick="closeVariantModal()" class="absolute top-4 right-4 w-8 h-8 rounded-full bg-white/5 hover:bg-white/10 flex items-center justify-center text-slate-400 hover:text-white transition-all border border-white/5"> <i class="fa-solid fa-xmark"></i> </button> <div class="flex items-center gap-4 mb-6"> <div class="w-12 h-12 rounded-xl bg-gradient-to-br from-cyan-500/20 to-blue-600/20 flex items-center justify-center border border-cyan-500/30 shadow-[0_0_15px_rgba(6,182,212,0.2)]"> <i class="fa-solid fa-layer-group text-cyan-400"></i> </div> <div> <h2 class="text-2xl font-bold text-white" id="modalProdName">Product Name</h2> <p class="text-xs text-slate-500 font-mono uppercase tracking-wider">Pilih Paket Lisensi</p> </div> </div> <div id="variantList" class="space-y-3 max-h-[50vh] overflow-y-auto pr-2 custom-scrollbar mb-6"> <!-- Variants injected via JS --> </div> <!-- Voucher --> <div id="voucherBox" class="hidden mb-6 p-4 rounded-2xl bg-gradient-to-br from-white/5 to-transparent border border-white/5"> <label class="text-[10px] text-slate-400 font-bold uppercase tracking-widest block mb-2">Voucher Code</label> <div class="flex gap-2"> <input type="text" id="voucherInput" placeholder="Punya kode promo?" class="flex-1 bg-black/40 border border-white/10 rounded-lg px-4 py-3 text-sm text-white outline-none focus:border-cyan-500 transition-all font-mono"> <button onclick="applyVoucher()" type="button" class="px-4 rounded-lg bg-surface border border-white/10 hover:border-cyan-500 hover:text-cyan-400 text-white font-bold text-xs transition-all uppercase"> Apply </button> </div> <p id="voucherInfo" class="text-xs mt-3 hidden flex items-center gap-2 font-medium"></p> </div> <button id="btnToPayment" onclick="goToPayment()" disabled class="w-full btn-primary-grad text-white font-bold py-4 rounded-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed disabled:grayscale flex justify-center items-center gap-2 text-sm uppercase tracking-widest relative overflow-hidden group"> <div class="absolute inset-0 bg-white/20 translate-y-full group-hover:translate-y-0 transition-transform duration-300"></div> <span id="btnPayText">PILIH PAKET</span> <i class="fa-solid fa-arrow-right"></i> </button> </div> </div> </div> <!-- MODAL PEMBAYARAN --> <div id="paymentModal" class="hidden fixed inset-0 z-50 flex items-center justify-center p-4"> <div class="absolute inset-0 bg-black/95 backdrop-blur-xl modal-backdrop transition-opacity" onclick="closePayment()"></div> <div id="paymentModalContent" class="modal-content relative w-full max-w-md bg-surface border border-white/10 rounded-3xl overflow-hidden transform scale-95 opacity-0 transition-all duration-300 shadow-[0_0_50px_rgba(0,0,0,0.8)]"> <button onclick="closePayment()" class="absolute top-4 right-4 z-30 w-8 h-8 rounded-full bg-black/50 hover:bg-white/10 flex items-center justify-center text-white/70 hover:text-white transition-all backdrop-blur-md border border-white/10"> <i class="fa-solid fa-xmark text-sm"></i> </button> <!-- SCAN SECTION --> <div id="scanSection" class="p-6 md:p-8 relative z-10"> <div class="text-center mb-8"> <h2 class="font-display text-2xl font-bold text-white mb-1">Scan QRIS</h2> <p class="text-xs text-slate-400 font-mono">Payment Gateway • Secure SSL</p> </div> <div class="relative mx-auto w-fit mb-8"> <div class="absolute -inset-1 bg-gradient-to-br from-cyan-500 to-purple-600 rounded-2xl blur opacity-20 animate-pulse-glow"></div> <div class="relative bg-white p-3 rounded-2xl shadow-2xl qr-container border-4 border-white scanner-wrapper"> <div class="scanner-line"></div> <div class="corner corner-tl"></div> <div class="corner corner-tr"></div> <div class="corner corner-bl"></div> <div class="corner corner-br"></div> <canvas id="qrcode" class="w-56 h-56 rounded-xl block"></canvas> </div> </div> <button onclick="downloadQRIS()" class="w-full mb-8 py-3 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 hover:border-cyan-500/40 text-white text-xs font-bold tracking-widest transition-all flex items-center justify-center gap-2 group"> <i class="fa-solid fa-download group-hover:translate-y-[2px] transition-transform"></i> DOWNLOAD QRIS </button> <div class="glass-panel p-6 rounded-2xl mb-8 text-center border border-white/5 bg-gradient-to-br from-white/5 to-transparent"> <p class="text-slate-500 text-[10px] font-bold uppercase tracking-widest mb-2">Total Pembayaran</p> <p id="totalText" class="text-4xl font-black text-white tracking-tighter mb-2 text-glow">Rp 0</p> <div class="inline-block px-3 py-1 bg-cyan-500/10 border border-cyan-500/20 rounded-full mb-4"> <p id="variantInfoText" class="text-xs text-cyan-400 font-bold tracking-wide">-</p> </div> <p id="expiredText" class="text-xs text-red-400 font-mono"><i class="fa-regular fa-clock mr-1"></i>Expired: -</p> </div> <button onclick="checkPayment()" id="btnCheck" class="w-full bg-emerald-600 hover:bg-emerald-500 text-white font-bold py-4 rounded-xl shadow-lg shadow-emerald-900/20 transition-all active:scale-[0.98] flex justify-center items-center gap-2 text-xs tracking-widest uppercase border border-emerald-500/30"> <span id="btnCheckText"><i class="fa-solid fa-circle-check mr-2"></i> Saya Sudah Bayar</span> </button> <!-- Status Progress --> <div id="paymentStatusMsg" class="mt-8 hidden animate-slide-up"> <div class="flex justify-between text-[10px] text-slate-400 font-bold mb-2 font-mono uppercase tracking-wider"> <span id="statusText">Checking Transaction...</span> <span id="progressText">0%</span> </div> <div class="w-full bg-black/50 h-1.5 rounded-full overflow-hidden border border-white/5"> <div id="progressBar" class="bg-gradient-to-r from-cyan-400 via-blue-500 to-emerald-400 h-full w-0 transition-all duration-300 ease-out relative"> <div class="absolute inset-0 bg-white/30 animate-[shimmer_1s_infinite]"></div> </div> </div> </div> </div> <!-- SUKSES SECTION --> <div id="successSection" class="hidden p-8 text-center h-full flex flex-col justify-center bg-surface relative overflow-hidden z-50 pointer-events-auto"> <div class="absolute inset-0 bg-[url('https://www.transparenttextures.com/patterns/cubes.png')] opacity-5"></div> <div class="relative z-10 mx-auto mb-8 animate-float"> <div class="w-24 h-24 bg-emerald-500/10 rounded-full flex items-center justify-center ring-1 ring-emerald-500/30 shadow-[0_0_40px_rgba(16,185,129,0.2)]"> <i class="fa-solid fa-check text-5xl text-emerald-400 drop-shadow-[0_0_10px_rgba(16,185,129,0.8)]"></i> </div> </div> <h2 class="font-display text-3xl font-black text-white mb-2 tracking-tight">PAYMENT SUCCESS!</h2> <p class="text-slate-400 text-sm mb-8 leading-relaxed">Lisensi aktif. Key Anda siap digunakan.</p> <div class="glass-panel p-1 rounded-2xl mb-8 text-left relative overflow-hidden group border-emerald-500/20"> <div class="absolute inset-0 bg-emerald-500/5 translate-y-full group-hover:translate-y-0 transition-transform duration-500"></div> <div class="relative bg-black/80 rounded-xl p-5 border border-white/5 flex items-center justify-between gap-3"> <input type="text" id="finalKeyInput" readonly class="bg-transparent text-emerald-400 font-mono text-sm w-full outline-none select-all tracking-widest font-bold uppercase"> <button onclick="copyKey()" class="w-10 h-10 flex items-center justify-center rounded-lg bg-slate-800 text-slate-400 hover:bg-emerald-500 hover:text-white transition-all border border-white/5 hover:border-emerald-400 active:scale-95"> <i class="fa-regular fa-copy text-lg"></i> </button> </div> </div> <div class="flex gap-3"> <button onclick="location.reload()" class="flex-1 py-3.5 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-slate-300 hover:text-white font-bold text-xs tracking-widest transition-all"> <i class="fa-solid fa-rotate-right mr-2"></i>BELANJA LAGI </button> <button onclick="checkHistory()" class="flex-1 py-3.5 rounded-xl btn-primary-grad text-white font-bold text-xs tracking-widest shadow-lg shadow-cyan-500/20"> RIWAYAT </button> </div> </div> </div> </div> <!-- MODAL RIWAYAT --> <div id="historyModal" class="hidden fixed inset-0 z-50 flex items-end sm:items-center justify-center"> <div class="absolute inset-0 modal-backdrop transition-opacity" onclick="closeHistoryModal()"></div> <div id="historyModalContent" class="modal-content relative w-full max-w-md h-[90vh] sm:h-auto sm:max-h-[85vh] bg-surface border-t sm:border border-white/10 sm:rounded-3xl rounded-t-3xl flex flex-col transform translate-y-full sm:translate-y-0 opacity-0 transition-all duration-300"> <div class="p-5 border-b border-white/5 flex justify-between items-center bg-black/40 sm:rounded-t-3xl backdrop-blur-md sticky top-0 z-20"> <h2 class="font-display text-lg font-bold text-white flex items-center gap-2 tracking-wide"> <i class="fa-solid fa-clock-rotate-left text-cyan-400"></i> Riwayat </h2> <button onclick="closeHistoryModal()" class="text-slate-500 hover:text-white transition-colors w-8 h-8 flex items-center justify-center rounded-full hover:bg-white/5"><i class="fa-solid fa-xmark"></i></button> </div> <!-- List items --> <div id="historyList" class=" flex-1 overflow-y-auto p-5 space-y-3 " > </div> </div> </div> </div> <!-- JAVASCRIPT LOGIC --> <script type="module"> import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.12.2/firebase-app.js'; import { getDatabase, ref, set, push, get, remove, onValue, query, orderByChild, equalTo } from 'https://www.gstatic.com/firebasejs/10.12.2/firebase-database.js'; // --- FIREBASE CONFIG (PRESERVED) --- const firebaseConfig = { apiKey: "AIzaSyC_3IPlnMlGPY3xufKSnnk_sN4ZVt_aGLQ", authDomain: "fixa-91280.firebaseapp.com", databaseURL: "https://fixa-91280-default-rtdb.firebaseio.com", projectId: "fixa-91280", storageBucket: "fixa-91280.firebasestorage.app", messagingSenderId: "53845427227", appId: "1:53845427227:web:1f0bf6516e1c6c9ed564be", measurementId: "G-KZ39Y0DW4F" }; const app = initializeApp(firebaseConfig); const db = getDatabase(app); window.openTransactionHistory = async () => { const modal = document.getElementById( 'transactionModal' ); const list = document.getElementById( 'transactionList' ); list.innerHTML = ''; const snap = await get( query( ref(db, 'orders'), orderByChild('uid'), equalTo(uid) ) ); if(!snap.exists()){ list.innerHTML = ` <div class="text-slate-400"> Belum ada transaksi </div> `; } else { snap.forEach((child) => { const o = child.val(); list.innerHTML += ` <div class=" bg-white/5 border border-white/10 rounded-2xl p-4 "> <div class="text-white font-bold"> ${o.product} </div> <div class="text-cyan-300 text-sm mt-1"> Rp ${o.amount || 0} </div> <div class="text-slate-500 text-xs mt-2"> ${new Date( o.created_at ).toLocaleString('id-ID')} </div> </div> `; }); } modal.classList.remove('hidden'); modal.classList.add('flex'); }; window.closeTransactionModal = () => { const modal = document.getElementById( 'transactionModal' ); modal.classList.add('hidden'); modal.classList.remove('flex'); }; setInterval(() => { const now = new Date(); document.getElementById( 'liveClock' ).innerText = now.toLocaleTimeString( 'id-ID' ); }, 1000); function getDeviceId(){ let id = localStorage.getItem('fixa_device_id'); if(!id){ id = crypto.randomUUID(); localStorage.setItem( 'fixa_device_id', id ); } return id; } const uid = localStorage.getItem('fixa_uid'); onValue( ref(db, 'users/' + uid), (snap) => { if(!snap.exists()) return; const user = snap.val(); document.getElementById( 'welcomeUser' ).innerText = 'Welcome, ' + user.username; document.getElementById( 'userFirstLetter' ).innerText = user.username .charAt(0) .toUpperCase(); } ); if(!uid){ location.href = 'login.html'; } onValue( ref(db, 'users/' + uid), (snap) => { if(!snap.exists()){ localStorage.clear(); location.href = 'login.html'; return; } const user = snap.val(); if(user.banned){ localStorage.clear(); alert('Akun dibanned'); location.href = 'login.html'; return; } if( user.deviceId !== getDeviceId() ){ localStorage.clear(); alert( 'Akun sudah dipakai di device lain' ); location.href = 'login.html'; return; } } ); onValue(ref(db, 'store_settings'), (snap) => { if(!snap.exists()) return; const data = snap.val(); if(data.name){ const parts = data.name.split(' '); if(parts.length >= 2){ document.getElementById('storeTitle').innerHTML = `${parts[0]} <span class="brand-gradient">${parts.slice(1).join(' ')}</span>`; } else { document.getElementById('storeTitle').innerHTML = data.name; } } if(data.logo){ document.getElementById('storeLogoImg') .src = data.logo; } }); let allProducts = {}; let currentDevice = "all"; let currentGame = "all"; let allKeys = {}; let currentSelection = {}; let currentOrderId = null; let paymentCountdown = null; let appliedVoucher = null; let originalPrice = 0; // --- VOUCHER LOGIC --- window.applyVoucher = async () => { const code = document.getElementById('voucherInput').value.trim().toUpperCase(); const info = document.getElementById('voucherInfo'); if(appliedVoucher) { info.classList.remove('hidden'); info.innerHTML = '<i class="fa-solid fa-triangle-exclamation text-warning"></i> <span class="text-slate-300">Voucher sudah dipakai</span>'; return; } if(!code) { info.classList.remove('hidden'); info.innerHTML = '<i class="fa-solid fa-circle-exclamation text-red-500"></i> <span class="text-red-400">Masukkan kode voucher</span>'; return; } try { const snap = await get(ref(db, 'vouchers/' + code)); if(!snap.exists()) { info.classList.remove('hidden'); info.innerHTML = '<i class="fa-solid fa-circle-xmark text-red-500"></i> <span class="text-red-400">Voucher tidak valid</span>'; appliedVoucher = null; return; } const voucher = snap.val(); if(voucher.expired_at && Date.now() > voucher.expired_at) { info.classList.remove('hidden'); info.innerHTML = '<i class="fa-solid fa-clock text-red-500"></i> <span class="text-red-400">Voucher expired</span>'; return; } if(voucher.used >= voucher.max_use) { info.classList.remove('hidden'); info.innerHTML = '<i class="fa-solid fa-ban text-red-500"></i> <span class="text-red-400">Kuota habis</span>'; return; } appliedVoucher = voucher; await set(ref(db, 'vouchers/' + code + '/used'), (voucher.used || 0) + 1); let finalPrice = originalPrice; if(voucher.type === 'percent') { finalPrice = originalPrice - Math.floor(originalPrice * voucher.value / 100); } else { finalPrice = originalPrice - voucher.value; } if(finalPrice < 500) finalPrice = 500; currentSelection.price = finalPrice; document.getElementById('btnPayText').innerText = 'BAYAR - Rp ' + finalPrice.toLocaleString('id-ID'); info.classList.remove('hidden'); info.innerHTML = '<i class="fa-solid fa-circle-check text-success"></i> <span class="text-emerald-300">Voucher Aktif: Hemat ' + (originalPrice - finalPrice).toLocaleString('id-ID') + '</span>'; showToast("Voucher berhasil digunakan!", "success"); } catch(err) { console.log(err); showToast("Gagal cek voucher", "error"); } }; // --- UI UTILITIES --- window.showToast = (msg, type='error') => { const container = document.getElementById('toast-container'); const toast = document.createElement('div'); toast.className = `toast ${type}`; let icon = '<i class="fa-solid fa-circle-exclamation text-red-500 text-lg"></i>'; if(type === 'success') icon = '<i class="fa-solid fa-circle-check text-success text-lg"></i>'; if(type === 'info') icon = '<i class="fa-solid fa-circle-info text-blue-500 text-lg"></i>'; toast.innerHTML = `${icon} <div class="flex-1 text-sm font-medium text-slate-200">${msg}</div>`; container.appendChild(toast); setTimeout(() => { toast.style.animation = 'slideInRight 0.3s cubic-bezier(0.16, 1, 0.3, 1) reverse forwards'; setTimeout(() => toast.remove(), 300); }, 3500); } function getStockCount(pid, vid) { let count = 0; if(allKeys) { Object.keys(allKeys).forEach(k => { if(allKeys[k].productId === pid && allKeys[k].variantId === vid) count++; }); } return count; } function getTotalStock(pid) { let count = 0; if(allKeys) { Object.keys(allKeys).forEach(k => { if(allKeys[k].productId === pid) count++; }); } return count; } // --- DATA LISTENERS --- onValue(ref(db, 'unused_keys'), (snap) => { allKeys = snap.val() || {}; renderProducts(); }); onValue(ref(db, 'products'), (snap) => { allProducts = snap.val() || {}; renderProducts(); }); document.querySelectorAll('.device-btn').forEach(btn=>{ btn.onclick = () => { document.querySelectorAll('.device-btn').forEach(b=>b.classList.remove('active')); btn.classList.add('active'); currentDevice = btn.dataset.device; renderProducts(); } }); document.querySelectorAll('.game-btn').forEach(btn=>{ btn.onclick = () => { document.querySelectorAll('.game-btn').forEach(b=>b.classList.remove('active')); btn.classList.add('active'); currentGame = btn.dataset.game; renderProducts(); } }); function renderProducts() { const grid = document.getElementById('productGrid'); grid.innerHTML = ''; if(Object.keys(allProducts).length === 0) { grid.innerHTML = '<div class="col-span-full text-center py-20 text-slate-600 text-sm">Belum ada produk.</div>'; return; } let index = 0; Object.keys(allProducts).forEach(key => { const p = allProducts[key]; // FILTER DEVICE if(currentDevice !== "all"){ if(Array.isArray(p.device)){ if(!p.device.includes(currentDevice)) return; } else { if(p.device !== currentDevice) return; } } // FILTER GAME if(currentGame !== "all"){ if(p.game !== currentGame) return; } const totalStock = getTotalStock(key); const isOutOfStock = totalStock === 0; const card = document.createElement('div'); // Add 3D Tilt JS Logic card.className = `tilt-card relative h-full group ${isOutOfStock ? 'opacity-50 grayscale' : ''}`; card.style.animation = `slideUp 0.5s ease-out ${index * 0.1}s forwards`; card.style.opacity = '0'; // Initial state for animation // Colors let iconClass = "fa-layer-group", bgGradient = "from-slate-800 to-slate-900", iconColor = "text-slate-400"; if(p.name.toLowerCase().includes("vip")) { iconClass = "fa-crown"; bgGradient = "from-yellow-900/50 to-orange-900/50"; iconColor = "text-yellow-400"; } if(p.name.toLowerCase().includes("mobile")) { iconClass = "fa-mobile-screen"; bgGradient = "from-purple-900/50 to-pink-900/50"; iconColor = "text-purple-400"; } // Device Badges let deviceBadges = ''; if(Array.isArray(p.device)) { deviceBadges = p.device.map(dev => { let color = dev === 'android' ? 'bg-lime-500 text-black' : dev === 'ios' ? 'bg-slate-200 text-black' : 'bg-cyan-500 text-white'; return `<span class="${color} px-2 py-0.5 rounded text-[9px] font-black uppercase tracking-wider shadow-sm border border-white/10">${dev}</span>`; }).join(''); } else { let dev = p.device; let color = dev === 'android' ? 'bg-lime-500 text-black' : dev === 'ios' ? 'bg-slate-200 text-black' : 'bg-cyan-500 text-white'; deviceBadges = `<span class="${color} px-2 py-0.5 rounded text-[9px] font-black uppercase tracking-wider shadow-sm border border-white/10">${dev}</span>`; } card.innerHTML = ` <!-- Card Container --> <div class="tilt-content h-full glass-panel rounded-2xl p-5 flex flex-col border border-white/5 relative overflow-hidden transition-all duration-300 group-hover:border-cyan-500/30"> <div class="absolute inset-0 overflow-hidden rounded-2xl"> <img src="${p.image || 'https://via.placeholder.com/500'}" class="w-full h-full object-cover opacity-20 group-hover:scale-110 transition-all duration-500" > <div class="absolute inset-0 bg-gradient-to-t from-black/90 via-black/40 to-black/10"></div> </div> <!-- Hover Glow Effect --> <div class="absolute inset-0 bg-gradient-to-br from-cyan-500/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div> <div class="relative z-10 flex flex-col h-full"> <!-- Header --> <div class="flex justify-between items-start mb-4"> <div class="w-12 h-12 rounded-xl bg-gradient-to-br ${bgGradient} flex items-center justify-center border border-white/5 shadow-inner group-hover:scale-110 transition-transform duration-300"> <i class="fa-solid ${iconClass} ${iconColor} text-lg"></i> </div> <div class="flex gap-1.5 flex-wrap justify-end"> ${deviceBadges} </div> </div> <!-- Content --> <h3 class="text-lg font-bold text-white mb-2 leading-tight group-hover:text-cyan-100 transition-colors"> ${p.name} </h3> <p class="text-xs text-slate-400 leading-relaxed mb-4 line-clamp-2 flex-grow"> ${p.description || 'Lisensi digital resmi.'} </p> <!-- Stock Badge --> <div class="mb-4 inline-flex items-center gap-2 px-2.5 py-1 rounded-lg bg-white/5 border border-white/5 w-fit"> <div class="w-1.5 h-1.5 rounded-full ${isOutOfStock ? 'bg-red-500' : 'bg-emerald-500 shadow-[0_0_5px_rgba(16,185,129,0.5)]'}"></div> <span class="text-[10px] font-bold uppercase tracking-wider ${isOutOfStock ? 'text-red-400' : 'text-emerald-400'}"> ${isOutOfStock ? 'HABIS' : 'TERSEDIA'} </span> </div> <!-- Footer Actions --> <div class="grid grid-cols-2 gap-3 mt-auto"> <button onclick="${isOutOfStock ? '' : `openVariantModal('${key}')`}" class="py-2.5 rounded-lg btn-primary-grad text-[10px] font-bold tracking-widest flex justify-center items-center gap-1 shadow-lg shadow-cyan-500/10 ${isOutOfStock ? 'opacity-50 cursor-not-allowed grayscale' : 'hover:shadow-cyan-500/30'}"> <i class="fa-solid fa-cart-shopping"></i> BELI </button> ${p.downloadLink ? ` <a href="${p.downloadLink}" target="_blank" class="py-2.5 rounded-lg bg-white/5 hover:bg-white/10 border border-white/10 hover:border-white/30 text-slate-300 text-[10px] font-bold tracking-widest transition-all flex justify-center items-center gap-1"> <i class="fa-solid fa-download"></i> DOWNLOAD </a>` : '<div></div>'} </div> </div> </div> `; // Add Mouse Move Event for 3D Tilt (Only on non-touch) if(!isOutOfStock && window.matchMedia("(min-width: 768px)").matches) { card.addEventListener('mousemove', (e) => { const rect = card.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const centerX = rect.width / 2; const centerY = rect.height / 2; const rotateX = ((y - centerY) / centerY) * -5; // Max 5deg rotation const rotateY = ((x - centerX) / centerX) * 5; card.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`; }); card.addEventListener('mouseleave', () => { card.style.transform = `perspective(1000px) rotateX(0deg) rotateY(0deg)`; }); } grid.appendChild(card); index++; }); } // --- MODAL LOGIC --- window.openVariantModal = (prodId) => { const p = allProducts[prodId]; const modal = document.getElementById('variantModal'); const content = document.getElementById('variantModalContent'); const list = document.getElementById('variantList'); const btn = document.getElementById('btnToPayment'); document.getElementById('modalProdName').innerText = p.name; list.innerHTML = ''; currentSelection = { prodId, vid: null, label: null, price: 0 }; btn.disabled = true; btn.classList.add('opacity-50', 'cursor-not-allowed'); document.getElementById('btnPayText').innerText = "PILIH PAKET"; document.getElementById('voucherBox').classList.add('hidden'); document.getElementById('voucherInput').value = ''; document.getElementById('voucherInfo').classList.add('hidden'); if(p.variants) { Object.keys(p.variants).forEach((vId, idx) => { const v = p.variants[vId]; const stock = getStockCount(prodId, vId); const isOutOfStock = stock === 0; const div = document.createElement('div'); div.className = `p-4 rounded-xl border border-white/5 bg-white/5 flex justify-between items-center cursor-pointer transition-all duration-200 group ${isOutOfStock ? 'opacity-40 cursor-not-allowed grayscale' : 'hover:border-cyan-500/50 hover:bg-white/10'}`; div.style.animationDelay = `${idx * 50}ms`; div.classList.add('animate-slide-up'); div.innerHTML = ` <div class="flex items-center gap-3"> <div class="w-5 h-5 rounded-full border border-slate-500 flex items-center justify-center group-radio"> <div class="w-2.5 h-2.5 rounded-full bg-cyan-400 opacity-0 transition-opacity"></div> </div> <div> <span class="font-bold text-white text-sm block">${v.label}</span> <span class="text-[10px] text-slate-500 uppercase tracking-wider">${isOutOfStock ? 'Stok Habis' : 'Ready Stock'}</span> </div> </div> <div class="text-right"> <span class="font-mono font-bold text-cyan-400 text-sm block">Rp ${Number(v.price).toLocaleString('id-ID')}</span> </div> `; if(!isOutOfStock) { div.onclick = () => { // UI Selection document.querySelectorAll('#variantList > div').forEach(el => { el.classList.remove('border-cyan-500', 'bg-cyan-500/10'); el.querySelector('.group-radio div').classList.add('opacity-0'); }); div.classList.add('border-cyan-500', 'bg-cyan-500/10'); div.querySelector('.group-radio div').classList.remove('opacity-0'); currentSelection = { prodId, vid: vId, label: v.label, price: Number(v.price) }; originalPrice = Number(v.price); appliedVoucher = null; btn.disabled = false; btn.classList.remove('opacity-50', 'cursor-not-allowed'); document.getElementById('btnPayText').innerText = "LANJUT BAYAR - Rp " + Number(v.price).toLocaleString('id-ID'); document.getElementById('voucherBox').classList.remove('hidden'); document.getElementById('voucherBox').classList.add('animate-slide-up'); }; } list.appendChild(div); }); } modal.classList.remove('hidden'); setTimeout(() => { content.classList.remove('translate-y-full', 'opacity-0'); content.classList.add('modal-enter'); }, 10); }; window.closeVariantModal = () => { const modal = document.getElementById('variantModal'); const content = document.getElementById('variantModalContent'); content.classList.add('translate-y-full', 'opacity-0'); content.classList.remove('modal-enter'); setTimeout(() => { modal.classList.add('hidden'); }, 300); }; window.goToPayment = async () => { closeVariantModal(); const payModal = document.getElementById('paymentModal'); const content = document.getElementById('paymentModalContent'); payModal.classList.remove('hidden'); document.getElementById('scanSection').classList.remove('hidden'); document.getElementById('successSection').classList.add('hidden'); document.getElementById('paymentStatusMsg').classList.add('hidden'); document.getElementById('progressBar').style.width = '0%'; setTimeout(() => { content.classList.remove('scale-95', 'opacity-0'); content.classList.add('modal-enter'); }, 10); try { currentOrderId = 'ORD-' + Math.random().toString(36).substring(2, 10).toUpperCase() + Date.now().toString().slice(-4); await set(ref(db, 'orders/' + currentOrderId), { uid: uid, productId: currentSelection.prodId, variantId: currentSelection.vid, variantLabel: currentSelection.label, amount: currentSelection.price, status: 'pending', created_at: Date.now() }); // SIMULASI PAYMENT GATEWAY (Karena endpoint asli mungkin tidak berjalan di local HTML) // Catatan: Jika menggunakan Netlify Functions asli, uncomment fetch dibawah. // Disini saya buat simulasi agar UI bisa ditampilkan tanpa error console jika dijalankan lokal. let data; try { const response = await fetch('/.netlify/functions/create-payment', { method:'POST', headers:{ 'Content-Type':'application/json' }, body: JSON.stringify({ product_name: allProducts[currentSelection.prodId].name + ' ' + currentSelection.label, amount: currentSelection.price, order_id: currentOrderId }) }); data = await response.json(); } catch (e) { // Fallback Simulation for Demo Purpose if API fails console.warn("Payment API unreachable, using simulation mode."); data = { payment: { order_id: currentOrderId, payment_number: "https://carlostore.com/simulated-qr", // Placeholder string total_payment: currentSelection.price, expired_at: Date.now() + 600000 // 10 mins } }; } currentOrderId = data.payment.order_id; const canvas = document.getElementById('qrcode'); canvas.innerHTML = ""; // Generate QR from string (url or text) QRCode.toCanvas( canvas, data.payment.payment_number, { width: 220, margin: 1, color: { dark: "#000000", light: "#ffffff" } } ); document.getElementById('totalText').innerText = 'Rp ' + Number(data.payment.total_payment).toLocaleString('id-ID'); document.getElementById('variantInfoText').innerText = currentSelection.label; if(paymentCountdown) clearInterval(paymentCountdown); const expiredTime = new Date(data.payment.expired_at).getTime(); paymentCountdown = setInterval(() => { const now = Date.now(); const distance = expiredTime - now; if(distance <= 0) { clearInterval(paymentCountdown); document.getElementById('expiredText').innerHTML = '<i class="fa-solid fa-ban text-red-500"></i> EXPIRED'; const btnCheck = document.getElementById('btnCheck'); btnCheck.disabled = true; btnCheck.classList.add('opacity-50'); btnCheck.innerHTML = '<i class="fa-solid fa-xmark"></i> EXPIRED'; return; } const m = Math.floor(distance / 1000 / 60); const s = Math.floor((distance / 1000) % 60); document.getElementById('expiredText').innerHTML = `<i class="fa-regular fa-clock text-cyan-400"></i> Exp: ${m}:${String(s).padStart(2,'0')}`; }, 1000); const btnCheck = document.getElementById('btnCheck'); btnCheck.innerHTML = '<i class="fa-solid fa-circle-check"></i> KONFIRMASI PEMBAYARAN'; btnCheck.disabled = false; btnCheck.classList.remove('opacity-50'); } catch(err) { console.error(err); showToast("Gagal memuat pembayaran", "error"); closePayment(); } }; window.checkPayment = async () => { const btn = document.getElementById('btnCheck'); const statusMsg = document.getElementById('paymentStatusMsg'); const progressBar = document.getElementById('progressBar'); const statusText = document.getElementById('statusText'); const progressText = document.getElementById('progressText'); btn.innerHTML = '<div class="loader w-4 h-4 mr-2 border-2 border-t-white/50 border-white rounded-full animate-spin"></div> CEK STATUS...'; btn.disabled = true; statusMsg.classList.remove('hidden'); statusText.innerText = "Verifying Transaction..."; let retries = 0; const maxRetries = 40; const delay = 1500; const pollPayment = async () => { if (retries >= maxRetries) { btn.innerHTML = '<i class="fa-solid fa-rotate-right"></i> COBA LAGI'; btn.disabled = false; progressBar.classList.add('bg-red-500'); statusText.innerText = "Payment Not Detected"; statusText.classList.add('text-red-400'); return; } try { const response = await fetch('/.netlify/functions/check-payment', { method:'POST', headers:{ 'Content-Type':'application/json' }, body:JSON.stringify({ order_id: currentOrderId, amount: currentSelection.price }) }); let data; try { data = await response.json(); } catch(e) { data = { transaction: { status: 'pending' } }; } // Prevent JSON parse error if API down const isSuccess = data.transaction && ( data.transaction.status === 'completed' || data.transaction.status === 'paid' || data.transaction.status === 'success' || data.transaction.status === 'settlement' ); if (isSuccess) { progressBar.style.width = '100%'; statusText.innerText = "Payment Verified!"; statusText.classList.add('text-emerald-400'); const keysRef = ref(db, 'unused_keys'); const snap = await get(keysRef); if(!snap.exists()) { throw "Out of Stock!"; } const keys = snap.val(); let foundKey = null; let foundKeyId = null; Object.keys(keys).forEach(kid => { if(!foundKey && keys[kid].productId === currentSelection.prodId && keys[kid].variantId === currentSelection.vid) { foundKey = keys[kid].value; foundKeyId = kid; } }); if(!foundKey) { throw "Stok habis."; } await set(ref(db, 'orders/' + currentOrderId + '/status'), 'completed'); await set(ref(db, 'orders/' + currentOrderId + '/key'), foundKey); await remove(ref(db, 'unused_keys/' + foundKeyId)); await set( ref(db, `customers/${uid}/${currentOrderId}`), { key: foundKey, product: allProducts[currentSelection.prodId].name, variant: currentSelection.label, date: Date.now() } ); console.log(foundKey); document.getElementById('scanSection').classList.add('hidden'); document.getElementById('successSection').classList.remove('hidden'); document.getElementById('finalKeyInput').value = foundKey; showToast("Transaksi Sukses!", "success"); } else { retries++; const percentage = Math.round((retries / maxRetries) * 100); progressBar.style.width = percentage + '%'; progressText.innerText = percentage + '%'; statusText.innerText = "Waiting for confirmation..."; setTimeout(pollPayment, delay); } } catch(err) { console.error(err); retries++; setTimeout(pollPayment, delay); } }; pollPayment(); }; window.closePayment = () => { if(paymentCountdown) clearInterval(paymentCountdown); const payModal = document.getElementById('paymentModal'); const content = document.getElementById('paymentModalContent'); content.classList.add('scale-95', 'opacity-0'); content.classList.remove('modal-enter'); setTimeout(() => { payModal.classList.add('hidden'); }, 300); }; // --- HISTORY LOGIC --- window.openHistoryModal = () => { const modal = document.getElementById('historyModal'); const content = document.getElementById('historyModalContent'); modal.classList.remove('hidden'); setTimeout(() => { content.classList.remove('translate-y-full', 'opacity-0'); content.classList.add('modal-enter'); }, 10); } window.closeHistoryModal = () => { const modal = document.getElementById('historyModal'); const content = document.getElementById('historyModalContent'); content.classList.add('translate-y-full', 'opacity-0'); content.classList.remove('modal-enter'); setTimeout(() => { modal.classList.add('hidden'); }, 300); } window.openMyKeys = async () => { openHistoryModal(); const list = document.getElementById( 'historyList' ); list.innerHTML = '<div class="text-slate-400">Loading key...</div>'; try { const snap = await get( query( ref(db, 'orders'), orderByChild('uid'), equalTo(uid) ) ); if(!snap.exists()){ list.innerHTML = ` <div class="text-slate-500 text-center py-10"> Belum ada key </div> `; return; } list.innerHTML = ''; snap.forEach((child) => { const item = child.val(); if(item.key){ list.innerHTML += ` <div class=" glass-panel p-4 rounded-2xl border border-cyan-500/20 mb-3 "> <div class="text-white font-bold"> ${item.variantLabel || 'Produk'} </div> <div class=" text-cyan-400 font-mono mt-3 break-all text-sm "> ${item.key} </div> <button onclick="navigator.clipboard.writeText('${item.key}')" class=" mt-4 w-full bg-cyan-500/10 hover:bg-cyan-500/20 border border-cyan-500/20 rounded-xl py-2 text-cyan-300 text-xs font-bold transition-all " > SALIN KEY </button> </div> `; } }); if(list.innerHTML === ''){ list.innerHTML = ` <div class="text-slate-500 text-center py-10"> Belum ada key aktif </div> `; } } catch(err){ console.log(err); listContainer.innerHTML = ` <div class="text-red-400 text-center py-10"> Gagal load transaksi </div> `; } }; window.checkHistory = async () => { openHistoryModal(); const listContainer = document.getElementById( 'historyList' ); listContainer.innerHTML = '<div class="text-slate-400">Loading...</div>'; try { const snap = await get( query( ref(db, 'orders'), orderByChild('uid'), equalTo(uid) ) ); if(!snap.exists()){ listContainer.innerHTML = ` <div class="text-slate-500 text-center py-10"> Belum ada transaksi </div> `; return; } listContainer.innerHTML = ''; snap.forEach((child) => { const item = child.val(); listContainer.innerHTML += ` <div class=" glass-panel p-4 rounded-2xl border border-white/10 mb-3 "> <div class="text-white font-bold"> ${item.variantLabel || '-'} </div> <div class="text-cyan-400 mt-2 text-sm"> Rp ${Number(item.amount || 0).toLocaleString('id-ID')} </div> <div class="text-slate-500 text-xs mt-2"> ${new Date(item.created_at).toLocaleString('id-ID')} </div> </div> `; }); } catch(err){ console.log(err); listContainer.innerHTML = ` <div class="text-red-400 text-center py-10"> Gagal load transaksi </div> `; } }; window.copyKey = () => { const val = document.getElementById( 'finalKeyInput' ).value; navigator.clipboard.writeText(val); showToast( 'Key berhasil disalin', 'success' ); }; window.downloadQRIS = () => { const canvas = document.getElementById( 'qrcode' ); const link = document.createElement('a'); link.download = 'qris-carlo-store.png'; link.href = canvas.toDataURL(); link.click(); }; window.logoutUser = () => { localStorage.clear(); location.href = 'login.html'; }; window.toggleUserMenu = () => { const menu = document.getElementById( 'userMenu' ); menu.classList.toggle( 'hidden' ); }; </script> <script> document.addEventListener( 'contextmenu', e => e.preventDefault() ); document.addEventListener( 'keydown', function(e){ // F12 if(e.key === 'F12'){ e.preventDefault(); } // CTRL+S if(e.ctrlKey && e.key === 's'){ e.preventDefault(); document.body.innerHTML = ` <h1 style=" color:red; font-family:sans-serif; text-align:center; margin-top:100px; "> ACCESS DENIED </h1> `; } // CTRL+U if(e.ctrlKey && e.key === 'u'){ e.preventDefault(); } // CTRL+SHIFT+I if( e.ctrlKey && e.shiftKey && e.key === 'I' ){ e.preventDefault(); } } ); </script> <script> window.addEventListener( 'offline', () => { document.body.innerHTML = ` <div style=" background:black; color:red; height:100vh; display:flex; justify-content:center; align-items:center; font-size:40px; font-family:sans-serif; "> NO INTERNET CONNECTION </div> `; } ); </script> </body> <script> const allowedHosts = [ 'carlovip.com', 'www.carlovip.com' ]; if( !allowedHosts.includes( location.hostname ) ){ document.body.innerHTML = 'NGAPAIN KONTOL?'; } </script> </html>