<!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">© 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>