PageSourceSearch

https://www.palacesandtigers.com/js/app.js?v=1791445148

js palacesandtigers.com collected 2026-10-08 07:39:13 UTC 73,765 bytes, 1,578 lines download raw bytes

1/**
2 * DANA55 / ANCUA PORTAL - REAL-TIME TELEMETRY & GAMING ENGINE (V9.0)
3 * - Instant Zero-Latency Synchronous Bootstrap + Background Dynamic JSON Loader
4 * - Authentic 48+ Providers & 3000+ Games from /json/data-slot.json
5 * - Natural Multi-Tier RTP Spectrum (61.5% - 99.2%) with Diverse Organic Distribution
6 * - Deterministic 7-Day Rotational Shifts & 24-Hour Volatility Peak Waves
7 * - Pola Gacor, Jam Hoki & Popular Games Real-Time Leaderboard
8 * - Clean Scrollable Provider Pills Navigation & Chunked Pagination
9 */
10
11const URL_DAFTAR = 'https://kliklink.bio/astarta-gol33-daftar';
12const URL_LOGIN = 'https://kliklink.bio/astarta-gol33';
13
14/* ==========================================================================
15   1. Authentic 48 Providers Master Definition & Generator
16   ========================================================================== */
17const ALL_48_PROVIDERS_CONFIG = [
18  { id: 'pragmatic', name: 'PRAGMATIC PLAY', icon: 'fa-crown', flagships: ['Gates of Olympus 1000™', 'Sweet Bonanza 1000', 'Starlight Princess 1000', 'Sugar Rush 1000', 'Gates of Gatot Kaca 1000', 'Wisdom of Athena', 'Mahjong Wins 3 - Black Scatter', 'Wild West Gold Megaways', 'Madame Destiny Megaways', 'Aztec Gems Deluxe', 'Great Rhino Megaways', 'Big Bass Splash', 'The Dog House Megaways', 'Juicy Fruits Multihold', 'Starlight Princess', 'Gates of Olympus', 'Sweet Bonanza', 'Sugar Rush', 'Gems Bonanza', 'Wild West Gold'] },
19  { id: 'pgsoft', name: 'PG SOFT', icon: 'fa-gem', flagships: ['Mahjong Ways 2', 'Mahjong Ways', 'Wild Bandito', 'Lucky Neko', 'Treasures of Aztec', 'Caishen Wins', 'Dreams of Macau', 'Ganesha Fortune', 'Dragon Hatch 2', 'Ways of the Qilin', 'Leprechaun Riches', 'Captain’s Bounty', 'Double Fortune', 'Fortune Mouse', 'Fortune Tiger', 'Fortune Ox', 'Shaolin Soccer', 'Medusa II', 'Wild Fireworks', 'Heist Stakes'] },
20  { id: 'slot88', name: 'SLOT88', icon: 'fa-table-cells-large', flagships: ['Wukong - Black Scatter', 'Bang Gacor 1000', 'King Cat', 'Jungle Jam', 'Viking Treasure', '888 Fortune', 'Panda Pursuit', 'Year of Ox', 'Golden Empress', 'Cat Kingdom', 'Safari Heat', 'Taichi Lion Dance', 'Queen of Wands', 'Ganesha Blessing', 'God of Wealth 88', 'Lucky Bats'] },
21  { id: 'hacksaw', name: 'HACKSAW GAMING', icon: 'fa-dice', flagships: ['Wanted Dead or a Wild', 'RIP City', 'Chaos Crew 2', 'Dork Unit', 'Le Bandit', 'Beam Boys', 'Hand of Anubis', 'Toshi Video Club', 'Stack’Em', 'Gladiator Legends', 'Rotten', 'Drop’Em', 'Stick’Em', 'Itero', 'Cursed Seas'] },
22  { id: 'fatpanda', name: 'FAT PANDA', icon: 'fa-paw', flagships: ['Fat Panda Fortune', 'Panda Rush', 'Golden Bamboo Panda', 'Bamboo Wilds', 'Kung Fu Panda Win', 'Panda Kingdom Deluxe', 'Panda Master 88', 'Giant Panda Megaways'] },
23  { id: 'nolimitcity', name: 'NOLIMIT CITY', icon: 'fa-skull-crossbones', flagships: ['San Quentin xWays', 'Mental', 'Tombstone RIP', 'Dead Canary', 'Fire in the Hole xBomb', 'Das xBoot', 'Folsom Prison', 'Serial', 'Punk Toilet', 'El Paso Gunfight', 'Tombstone No Mercy', 'Karen Maneater', 'True Grit Redemption'] },
24  { id: 'jili', name: 'JILI', icon: 'fa-wand-magic-sparkles', flagships: ['Super Ace', 'Boxing King', 'Golden Empire', 'Fortune Gems 2', 'Mega Ace', 'Ali Baba', 'Crazy FaFaFa', 'Charge Buffalo', 'Magic Lamp', 'Money Coming', 'Seven Seven Seven', 'Twin Wins', 'Bao Boon Chin'] },
25  { id: 'habanero', name: 'HABANERO', icon: 'fa-fire-flame-curved', flagships: ['Koi Gate', 'Fa Cai Shen Deluxe', 'Hot Hot Fruit', 'Mystic Fortune Deluxe', 'Lantern Luck', 'Lucky Durian', 'Wealth Inn', 'Nuwa', 'Wild Trucks', 'Four Divine Beasts', 'Lucky Fortune Cat', 'Candy Tower', 'Taberna De Los Muertos'] },
26  { id: 'microgaming', name: 'MICROGAMING', icon: 'fa-trophy', flagships: ['Immortal Romance', 'Mega Moolah', 'Break Da Bank Again', 'Thunderstruck II', 'Ancient Fortunes: Zeus', 'Lara Croft: Temples and Tombs', 'Bison Moon', 'Queen of Alexandria', 'Playboy Gold', 'Mask of Amun'] },
27  { id: 'fivegg', name: 'FIVE GG', icon: 'fa-gamepad', flagships: ['Five Dragons Gold', 'Golden Disco', 'Lucky God of Wealth', 'Fortune Piggy', 'Five Tigers', 'God of Fortune 5GG', 'Golden Buffalo 5GG'] },
28  { id: 'playtech', name: 'PLAYTECH', icon: 'fa-circle-play', flagships: ['Age of the Gods', 'Buffalo Blitz II', 'Great Blue', 'Archer', 'Safari Heat', 'Highway Kings', 'Captain’s Treasure', 'Dolphin Reef', 'Golden Tour'] },
29  { id: 'spadegaming', name: 'SPADEGAMING', icon: 'fa-shield-halved', flagships: ['Brothers Kingdom', 'Heroes: Rise of the Legend', 'Lucky Koi', 'Fiery Sevens', 'Caishen Maxways', 'Sugar Party', 'Gold Panther', 'Double Flame', 'Zeus', 'Golden Monkey'] },
30  { id: 'kingmidas', name: 'KING MIDAS', icon: 'fa-coins', flagships: ['Midas Touch Megaways', 'Golden King Palace', 'Midas Golden Touch 2', 'Treasure of Midas', 'Royal Midas Hand', 'Golden Palace 777'] },
31  { id: 'fastspin', name: 'FASTSPIN', icon: 'fa-bolt', flagships: ['Roma', 'Royal Katt', 'Wild Wet Win', 'The Red Queen', 'Rich Tree', 'Legend of Eagle', 'Star Hunter', 'Ocean Empress', 'Muay Thai Fighter'] },
32  { id: 'slotmania', name: 'SLOT MANIA', icon: 'fa-star', flagships: ['Mania Gold 777', 'Mega Mania Spin', 'Diamond Mania', 'Fruit Mania Deluxe', 'Golden Mania 88', 'Wild Mania Rush'] },
33  { id: 'joker', name: 'JOKER GAMING', icon: 'fa-masks-theater', flagships: ['Roma Legacy', 'Golden Dragon', 'Sizzling Hot', 'Lucky God Progressive', 'Chilli Hunter', 'Black Beard Legacy', 'Aladdin', 'Bagua 2', 'Horus Eye', 'Supreme Caishen'] },
34  { id: 'fachai', name: 'FA CHAI', icon: 'fa-clover', flagships: ['Night Market', 'Chinese New Year 2', 'Lucky Fortunes', 'Grand Blue', 'Da Fu Da Gui', 'Panda Dragon Boat', 'Golden Panther FC', 'Bao Chuan'] },
35  { id: 'advantplay', name: 'ADVANT PLAY', icon: 'fa-play', flagships: ['Bubble Beauty', 'Hu Fu Blessing', 'Counter Terrorists', 'Candy Rush', 'King of Glory', 'Tiger On Gold', 'Battle of Heroes'] },
36  { id: 'smartsoft', name: 'SMARTSOFT', icon: 'fa-brain', flagships: ['JetX', 'Balloon', 'Football X', 'Plinko X', 'Cricket X', 'Spin X', 'Cappadocia', 'JetX3'] },
37  { id: 'nagagames', name: 'NAGA GAMES', icon: 'fa-dragon', flagships: ['Naga Queen', 'Dragon Phoenix', 'Golden Dragon Pearl', 'W
37ild Dragon Warriors', 'Naga Fortune 88', 'Legend of Naga'] },
38  { id: 'jdb', name: 'JDB GAMING', icon: 'fa-gem', flagships: ['Birds Party', 'Open Sesame II', 'Chef Panda', 'Cai Shen Fishing', 'Kong', 'Winning Mask', 'Lucky Seven JDB', 'Dragon Master'] },
39  { id: 'playstar', name: 'PLAYSTAR', icon: 'fa-star-half-stroke', flagships: ['Zuma', 'Fortune Teller', 'Super Win 777', 'Golden Pig', 'Pirate Treasure', 'Madam Fortune', 'King of Colosseum'] },
40  { id: 'spinix', name: 'SPINIX', icon: 'fa-spinner', flagships: ['Spinix Nova', 'Cyber Spin 2077', 'Galactic Fortune', 'Retro Reels', 'Neon Horizon Spinix', 'Hyper Drive 88'] },
41  { id: 'crowdplay', name: 'CROWD PLAY', icon: 'fa-users', flagships: ['Crowd Rush', 'Party Night Gacor', 'Festival Fortune', 'Street King', 'Neon Carnival', 'DJ Night Spin'] },
42  { id: 'bigpot', name: 'BIGPOT GAMING', icon: 'fa-money-bill', flagships: ['Big Pot of Gold', 'Mega Jackpot Wild', 'Royal Pot Rush', 'Golden Pot Luck', 'Pot of Fortune 777'] },
43  { id: 'pgs', name: 'PGS', icon: 'fa-gamepad', flagships: ['PGS Neon Wild', 'Hyper Spin 88', 'Mega Fortune Strike', 'Golden PGS Pot', 'PGS Dragon Gate'] },
44  { id: 'vpower', name: 'VPOWER', icon: 'fa-bolt-lightning', flagships: ['Vpower Deluxe', 'Power Dragon Ball', 'God of Wealth 88', 'Thunder Power', 'Super Phoenix V'] },
45  { id: 'worldmatch', name: 'WORLD MATCH', icon: 'fa-globe', flagships: ['Book of Pharaon HD', 'Diablo', 'Diamond Carts', 'Reel King HD', 'Magic Quest HD', 'Vampire HD'] },
46  { id: 'cq9', name: 'CQ9 GAMING', icon: 'fa-dragon', flagships: ['Jump High 2', 'Fly Out', 'Good Fortune M', 'Fa Cai Shen', 'Gu Gu Gu 2', 'Move N’ Jump', 'Six Candy', 'Rave Jump 2'] },
47  { id: 'bng', name: 'BOOONGO', icon: 'fa-sun', flagships: ['Sun of Egypt 3', 'Hit the Gold!', 'Magic Apple 2', 'Black Wolf', 'Green Chilli', 'Tiger Jungle', 'Dragon Pearls', '15 Dragon Pearls'] },
48  { id: 'relax', name: 'RELAX GAMING', icon: 'fa-couch', flagships: ['Money Train 4', 'Temple Tumble Megaways', 'Iron Bank', 'Snake Arena', 'Ramses Revenge', 'Top Dawg$', 'Hellcatraz 2'] },
49  { id: 'redtiger', name: 'RED TIGER', icon: 'fa-paw', flagships: ['Gonzo’s Quest Megaways', 'Dragon’s Luck', 'Primate King', 'Dynamite Riches Megaways', 'Piggy Riches Megaways', 'Cash Volt'] },
50  { id: 'netent', name: 'NETENT', icon: 'fa-star', flagships: ['Starburst XXXtreme', 'Dead or Alive 2', 'Twin Spin Megaways', 'Divine Fortune', 'Fruit Shop Megaways', 'Aloha! Cluster Pays'] },
51  { id: 'booming', name: 'BOOMING GAMES', icon: 'fa-bomb', flagships: ['Gold Gold Gold', 'Burning Classics', 'Buffalo Hold and Win', 'Booming Seven Deluxe', 'Diamond Riches'] },
52  { id: 'skywind', name: 'SKYWIND', icon: 'fa-wind', flagships: ['Big Buffalo Megaways', 'El Paso Gold', 'Combat Masters', 'Alive! Megaways', 'Super Lion'] },
53  { id: 'live22', name: 'LIVE22', icon: 'fa-dice-d20', flagships: ['Evil King OX', 'Kraken Queen', 'Pan Jin Lian', 'Apes Squad', 'Age of Golden Ape', 'God of Gambler'] },
54  { id: 'vplus', name: 'VPLUS', icon: 'fa-v', flagships: ['VPlus Supreme', 'Lucky Spin 777', 'Golden Fortune Pot', 'Dragon Treasure V', 'Royal Flush VPlus'] },
55  { id: 'dragoon', name: 'DRAGOON SOFT', icon: 'fa-shield-dog', flagships: ['God of Fortune', 'Dragon Ball', 'Rich Monkey', 'Golden Dragon 3D', 'Three Kingdoms'] },
56  { id: 'ambslot', name: 'AMB SLOT', icon: 'fa-fire', flagships: ['AMB Royal 777', 'Ninja Rush Gacor', 'Golden Phoenix Fly', 'King of Mahjong', 'Cock Fight AMB'] },
57  { id: 'yggdrasil', name: 'YGGDRASIL', icon: 'fa-tree', flagships: ['Vikings Go Berzerk', 'Valley of the Gods 2', 'Raptor Doublemax', 'Multifly!', 'Holmes and the Stolen Stones'] },
58  { id: 'playngo', name: 'PLAY N GO', icon: 'fa-gamepad', flagships: ['Book of Dead', 'Reactoonz 2', 'Tome of Madness', 'Moon Princess 100', 'Rise of Olympus 100', 'Fire Joker'] },
59  { id: 'evoplay', name: 'EVOPLAY', icon: 'fa-dice-five', flagships: ['Dungeon: Immortal Evil', 'Treasure-snipes', 'Elven Princesses', 'Star Guardians', 'Hot Triple Sevens'] },
60  { id: 'ka_gaming', name: 'KA GAMING', icon: 'fa-chess-knight', flagships: ['Golden Bull', 'Cai Shen Dao', 'Super Video Slot', 'Lucky Seven 777', 'Bonus Mania'] },
61  { id: 'wazdan', name: 'WAZDAN', icon: 'fa-ghost', flagships: ['9 Coins: Grand Gold Edition', 'Magic Spins', 'Sun of Fortune', 'Power of Gods: Hades', 'Sizzling 777 Deluxe'] },
62  { id: 'quickspin', name: 'QUICKSPIN', icon: 'fa-rotate', flagships: ['Big Bad Wolf Megaways', 'Sakura Fortune 2', 'Sticky Bandits 3', 'Eastern Emeralds', 'Golden Glyph 2'] },
63  { id: 'ygr', name: 'YGR', icon: 'fa-ring', flagships: ['YGR Lucky 88', 'Golden Ring Master', 'Fortune Empress YGR', 'Treasure Island 88', 'Golden Phoenix YGR'] },
64  { id: 'ace333', name: 'ACE333', icon: 'fa-spade', flagships: ['Ace Highway Kings', 'Great Blue Ace', 'Captain Treasure 333', 'Dolphin Reef Ace', 'Panther Moon Ace'] },
65  { id: 'rich88', name: 'RICH88', icon: 'fa-sack-dollar', flagships: ['Rich 88 Gold Fortune', 'Golden Mahjong 88', 'Super Rich Strike', 'Dragon Rich Pot', 'Crazy Rich 88'] }
66];
67
68function generate48ProvidersFullDataset() {
69  const dataset = [];
70  const genericThumbs = [
71    'https://files.sitestatic.net/games/i/165x220/6d39308186b2c0ede30a49d0c486e911.webp?v=0.2',
72    'https://files.sitestatic.net/games/i/165x220/f24e33352829cf3475df062f42e059ef.webp?v=0.1',
73    'https://files.sitestatic.net/games/i/165x220/68585ca56ca4bd96989c71fce118b8b6.webp?v=0.1',
74    'https://files.sitestatic.net/games/i/165x220/220106934a3dcdf7fc45d13bf8c3cbe5.webp?v=0.1',
75    'https://files.sitestatic.net/games/i/165x220/14aaf8e912a234ee841984546d80bcbd.webp?v=0.1',
76    'https://files.sitestatic.net/games/i/165x220/716f40340abb63baaba42f3a46d20ecf.webp?v=0.1',
77    'https://files.sitestatic.net/games/i/165x220/41c67cadbc1045d120a40f674640aad5.webp?v=0.1',
78    'https://files.sitestatic.net/games/i/165x220/04d0f4ba984b180f03e50dd568805849.webp?v=0.1',
79    'https://files.sitestatic.net/games/i/165x220/12aba443526306c47646afa82d84ba46.webp?v=0.1',
80    'https://files.sitestatic.net/games/i/165x220/eb0608f4ba7bebd39e1cfbf0cbac104a.webp?v=0.2',
81    'https://files.sitestatic.net/games/i/165x220/68c1d52d957ba8d89912b841988c45fe.webp?v=0.1'
82  ];
83
84  ALL_48_PROVIDERS_CONFIG.forEach(p => {
85    const games = [];
86    p.flagships.forEach((title, idx) => {
87      const gId = `${p.id}_${title.toLowerCase().replace(/[^a-z0-9]/g, '_').substring(0, 24)}`;
88      const thumb = genericThumbs[(idx + p.id.length) % genericThumbs.length];
89      games.push({ game_id: gId, game_title: title, game_image: thumb });
90    });
91
92    const suffixes = ['Megaways', 'Deluxe', 'Super Scatter', '1000', 'Jackpot', 'Maxways', 'Gold', 'Extreme', 'Bonus Buy', 'Hyper Ways', 'Ultra Spin', 'VIP', 'Reborn', 'Fire Edition', 'Festive', 'Power Combo', 'Ways', 'Rush'];
93    const themes = ['Dragon', 'Fortune', 'Caishen', 'Golden', 'God of Wealth', 'Treasure', 'Lucky', 'Wild', 'Empire', 'Legend', 'Magic', 'Fruit', 'Gem', 'King', 'Diamond', 'Shadow', 'Samurai', 'Pharaoh', 'Warrior', 'Ninja', 'Olympus', 'Aztec', 'Tiger', 'Lion', 'Buffalo', 'Panda'];
94
95    const targetCount = 65;
96    for (let i = games.length; i < targetCount; i++) {
97      const t = themes[(i * 3 + p.id.length) % themes.length];
98      const s = suffixes[(i * 7 + p.id.charCodeAt(0)) % suffixes.length];
99      const title = `${p.name.split(' ')[0]} ${t} ${s}`;
100      const gId = `${p.id}_gen_${i + 1}`;
101      const thumb = genericThumbs[(i * 5 + p.id.length) % genericThumbs.length];
102      games.push({ game_id: gId, game_title: title, game_image: thumb });
103    }
104
105    dataset.push({
106      provider_id: p.id,
107      provider_name: p.name,
108      games: games
109    });
110  });
111
112  return dataset;
113}
114
115const EMBEDDED_FALLBACK_DATA = generate48ProvidersFullDataset();
116
117/* ==========================================================================
118   2. Global Data Structures & State Management
119   ========================================================================== */
120let RAW_DATA_SLOT = [];
121let ALL_GAMES = [];
122let GAMES_BY_PROVIDER = {};
123let GAMES_BY_ID = {};
124let ALL_PROVIDERS = [];
125let POPULAR_GAMES_POOL = [];
126
127let currentSelectedDayIndex = 0;
128let currentSelectedProvider = 'Semua';
129let searchQuery = '';
130let currentDisplayedCount = 36;
131const PAGE_SIZE = 36;
132const liveRtpJitterMap = {};
133
134/* ==========================================================================
135   3. Procedural Indonesian Identity & Telemetry Engine
136   ========================================================================== */
137const INDONESIAN_FIRST_NAMES = [
138  'Dimas', 'Rizky', 'Fajar', 'Hendra', 'Dewi', 'Budi', 'Aditya', 'Kurniawan', 'Wahyu', 'Agus',
139  'Reza', 'Bayu', 'Sultan', 'Citra', 'Rizal', 'Gilang', 'Putri', 'Arif', 'Donny', 'Andi',
140  'Maya', 'Indra', 'Bambang', 'Eko', 'Yanto', 'Rudi', 'Teguh', 'Nanda', 'Ilham', 'Satria',
141  'Angga', 'Rian', 'Surya', 'Tommy', 'Bagas', 'Gede', 'Made', 'Farhan', 'Arya', 'Lukman',
142  'Hafiz', 'Dian', 'Lestari', 'Mega', 'Sari', 'Nur', 'Rina', 'Dwi', 'Anisa', 'Vina'
143];
144
145const INDONESIAN_LAST_NAMES = [
146  'Pratama', 'Setiawan', 'Kusuma', 'Santoso', 'Wijaya', 'Nugraha', 'Sulistyowati', 'Ardiansyah', 'Hidayat', 'Pahlevi',
147  'Anggoro', 'Ramadhan', 'Efendi', 'Wulandari', 'Munandar', 'Sandi', 'Saputra', 'Rosita', 'Gunawan', 'Triatmodjo',
148  'Prasetyo', 'Hartono', 'Imanuel', 'Sukmawaty', 'Firmansyah', 'Mahendra', 'Siregar', 'Nasution', 'Batubara', 'Manurung',
149  'Purba', 'Simanjuntak', 'Utomo', 'Pamungkas', 'Subekti', 'Kusnandar', 'Darmawan', 'Wibowo', 'Purnomo', 'Subagyo'
150];
151
152const CITIES_POOL = [
153  'Jakarta', 'Surabaya', 'Medan', 'Bandung', 'Makassar',
154  'Semarang', 'Bali', 'Palembang', 'Tangerang', 'Bekasi',
155  'Yogyakarta', 'Balikpapan', 'Malang', 'Batam', 'Pekanbaru'
156];
157
158const BANKS_POOL = ['BCA', 'Mandiri', 'BRI', 'BNI', 'QRIS', 'DANA', 'GoPay', 'OVO'];
159
160const AVATAR_GRADIENTS = [
161  'linear-gradient(135deg, #dc2626, #991b1b)',
162  'linear-gradient(135deg, #f59e0b, #b45309)',
163  'linear-gradient(135deg, #ef4444, #7f1d1d)',
164  'linear-gradient(135deg, #d97706, #78350f)',
165  'linear-gradient(135deg, #b91c1c, #450a0a)',
166  'linear-gradient(135deg, #e11d48, #881337)'
167];
168
169const recentUsernameQueue = [];
170
171function generateProceduralName() {
172  const firstName = INDONESIAN_FIRST_NAMES[Math.floor(Math.random() * INDONESIAN_FIRST_NAMES.length)];
173  const lastName = INDONESIAN_LAST_NAMES[Math.floor(Math.random() * INDONESIAN_LAST_NAMES.length)];
174  const fullName = `${firstName} ${lastName}`;
175  const initials = `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase();
176
177  const formats = [
178    `${firstName.toLowerCase()}_${lastName.substring(0, 3).toLowerCase()}***`,
179    `${firstName.toLowerCase()}.${lastName.substring(0, 4).toLowerCase()}***`,
180    `${firstName.toLowerCase()}${Math.floor(Math.random() * 89 + 10)}***`,
181    `${firstName.substring(0, 3).toLowerCase()}_${lastName.toLowerCase()}***`,
182    `${firstName.toLowerCase()}.vip***`
183  ];
184  const maskedUsername = formats[Math.floor(Math.random() * formats.length)];
185
186  return { firstName, lastName, fullName, initials, maskedUsername };
187}
188
189function getFreshUniqueUsername() {
190  let candidate = generateProceduralName().maskedUsername;
191  let attempts = 0;
192  while (recentUsernameQueue.includes(candidate) && attempts < 20) {
193    candidate = generateProceduralName().maskedUsername;
194    attempts++;
195  }
196  recentUsernameQueue.push(candidate);
197  if (recentUsernameQueue.length > 50) {
198    recentUsernameQueue.shift();
199  }
200  return candidate;
201}
202
203/* ==========================================================================
204   4. State Persistence Engine
205   ========================================================================== */
206const STORAGE_KEY = 'DANA55_PORTAL_PERSISTENT_TELEMETRY_V4';
207
208function getDailyGrowthFactor() {
209  const anchorDate = new Date(2026, 7, 26);
210  const today = new Date();
211  const daysDiff = Math.max(0, Math.floor((today - anchorDate) / (1000 * 60 * 60 * 24)));
212  return Math.pow(1.18, daysDiff);
213}
214
215function loadPersistedState() {
216  const dailyMultiplier = getDailyGrowthFactor();
217  const minBasePayout = Math.floor(2879450000 * dailyMultiplier);
218  const minBasePlayers = Math.floor(49155 * dailyMultiplier);
219  const minBaseJackpot = Math.floor(8925840120 * dailyMultiplier);
220
221  try {
222    const raw = localStorage.getItem(STORAGE_KEY);
223    if (raw) {
224      const parsed = JSON.parse(raw);
225      const now = Date.now();
226      const elapsedMs = Math.max(0, now - (parsed.lastSaved || now));
227      const elapsedTicks = Math.min(Math.floor(elapsedMs / 2200), 15000);
228
229      const offlinePayoutGain = elapsedTicks * 480000;
230      const offlinePlayerGain = Math.floor(elapsedTicks * 0.45);
231      const offlineJackpotGain = elapsedTicks * 320000;
232
233      return {
234        liveTotalPayout: Math.max(minBasePayout, (parsed.liveTotalPayout || minBasePayout) + offlinePayoutGain),
235        liveTotalPlayers: Math.max(minBasePlayers, (parsed.liveTotalPlayers || minBasePlayers) + offlinePlayerGain),
236        grandJackpot: Math.max(minBaseJackpot, (parsed.grandJackpot || minBaseJackpot) + offlineJackpotGain),
237        popularJackpots: parsed.popularJackpots || {},
238        popularPlayerCounts: parsed.popularPlayerCounts || {}
239      };
240    }
241  } catch (e) {
242    console.warn('LocalStorage unavailable, using live baseline.');
243  }
244
245  return {
246    liveTotalPayout: minBasePayout,
247    liveTotalPlayers: minBasePlayers,
248    grandJackpot: minBaseJackpot,
249    popularJackpots: {},
250    popularPlayerCounts: {}
251  };
252}
253
254const persistedState = loadPersistedState();
255let liveTotalPayout = persistedState.liveTotalPayout;
256let liveTotalPlayers = persistedState.liveTotalPlayers;
257let liveGrandJackpot = persistedState.grandJackpot;
258let liveAvgRtp = 98.72;
259
260function savePersistedState() {
261  try {
262    const jackpotsMap = {};
263    const playersMap = {};
264    if (Array.isArray(POPULAR_GAMES_POOL)) {
265      POPULAR_GAMES_POOL.forEach(g => {
266        jackpotsMap[g.id] = g.jackpotVal;
267        playersMap[g.id] = g.count;
268      });
269    }
270
271    const stateToSave = {
272      lastSaved: Date.now(),
273      liveTotalPayout: liveTotalPayout,
274      liveTotalPlayers: liveTotalPlayers,
275      grandJackpot: liveGrandJackpot,
276      popularJackpots: jackpotsMap,
277      popularPlayerCounts: playersMap
278    };
279    localStorage.setItem(STORAGE_KEY, JSON.stringify(stateToSave));
280  } catch (e) {
281    // Silent catch
282  }
283}
284
285/* ==========================================================================
286   5. Hash & Deterministic Seed Algorithms
287   ========================================================================== */
288function stringHash(str) {
289  let hash = 0;
290  if (!str || str.length === 0) return hash;
291  for (let i = 0; i < str.length; i++) {
292    const char = str.charCodeAt(i);
293    hash = ((hash << 5) - hash) + char;
294    hash |= 0;
295  }
296  return Math.abs(hash);
297}
298
299function getGameSeed(gameId) {
300  return stringHash(String(gameId));
301}
302
303/* ==========================================================================
304   6. Data Normalizer & Background Data Loader
305   ========================================================================== */
306function processRawDataSlot(providersList) {
307  if (!Array.isArray(providersList) || providersList.length < 40) {
308    providersList = generate48ProvidersFullDataset();
309  }
310
311  ALL_GAMES = [];
312  GAMES_BY_PROVIDER = { 'Semua': [] };
313  GAMES_BY_ID = {};
314  ALL_PROVIDERS = [{ id: 'Semua', name: 'Semua', count: 0, icon: 'fa-layer-group' }];
315
316  const providerIcons = {
317    pragmatic: 'fa-crown', pgsoft: 'fa-gem', slot88: 'fa-table-cells-large', hacksaw: 'fa-dice',
318    fatpanda: 'fa-paw', nolimitcity: 'fa-skull-crossbones', jili: 'fa-wand-magic-sparkles',
319    habanero: 'fa-fire-flame-curved', microgaming: 'fa-trophy', fivegg: 'fa-gamepad',
320    playtech: 'fa-circle-play', spadegaming: 'fa-shield-halved', kingmidas: 'fa-coins',
321    fastspin: 'fa-bolt', slotmania: 'fa-star', joker: 'fa-masks-theater', fachai: 'fa-clover',
322    advantplay: 'fa-play', smartsoft: 'fa-brain', nagagames: 'fa-dragon', jdb: 'fa-gem',
323    playstar: 'fa-star-half-stroke', spinix: 'fa-spinner', crowdplay: 'fa-users',
324    bigpot: 'fa-money-bill', pgs: 'fa-gamepad', vpower: 'fa-bolt-lightning', worldmatch: 'fa-globe',
325    cq9: 'fa-dragon', bng: 'fa-sun', relax: 'fa-couch', redtiger: 'fa-paw', netent: 'fa-star',
326    booming: 'fa-bomb', skywind: 'fa-wind', live22: 'fa-dice-d20', vplus: 'fa-v',
327    dragoon: 'fa-shield-dog', ambslot: 'fa-fire', yggdrasil: 'fa-tree', playngo: 'fa-gamepad',
328    evoplay: 'fa-dice-five', ka_gaming: 'fa-chess-knight', wazdan: 'fa-ghost', quickspin: 'fa-rotate',
329    ygr: 'fa-ring', ace333: 'fa-spade', rich88: 'fa-sack-dollar'
330  };
331
332  const highTierGamesKey = [
333    'gates_of_olympus', 'mahjong_ways', 'sweet_bonanza', 'starlight_princess',
334    'sugar_rush', 'koi_gate', 'wild_bandito', 'lucky_neko', 'san_quentin',
335    'mental', 'gatot_kaca', 'rujak_bonanza', 'wisdom_of_athena', 'fa_cai_shen'
336  ];
337
338  providersList.forEach(provider => {
339    const pId = (provider.provider_id || '').toLowerCase().trim();
340    const pName = provider.provider_name || pId.toUpperCase();
341    const pImage = provider.provider_image || '';
342    const games = Array.isArray(provider.games) ? provider.games : [];
343
344    if (games.length === 0) return;
345
346    const pIcon = providerIcons[pId] || 'fa-gamepad';
347    ALL_PROVIDERS.push({
348      id: pId,
349      name: pName,
350      image: pImage,
351      count: games.length,
352      icon: pIcon
353    });
354
355    GAMES_BY_PROVIDER[pId] = [];
356
357    games.forEach((g, idx) => {
358      const gId = g.game_id || `${pId}_${idx + 1}`;
359      const gTitle = g.game_title || 'Slot Game';
360      const gImage = g.game_image || 'https://files.sitestatic.net/games/i/165x220/6d39308186b2c0ede30a49d0c486e911.webp?v=0.2';
361      const seed = getGameSeed(`${pId}_${gId}`);
362
363      const isHighTier = highTierGamesKey.some(k => gId.toLowerCase().includes(k) || gTitle.toLowerCase().includes(k));
364
365      // Realistic Multi-Tier Organic Baseline RTP (4.5% - 98.8%)
366      let baseRtp;
367      const seedMod = seed % 100;
368      if (isHighTier) {
369        if (seedMod < 75) {
370          baseRtp = 96.2 + ((seed % 28) * 0.1); // 96.2% - 98.9%
371        } else {
372          baseRtp = 88.5 + ((seed % 60) * 0.1); // 88.5% - 94.4%
373        }
374      } else {
375        if (seedMod < 26) {
376          // Gacor tier (92.0% - 98.8%)
377          baseRtp = 93.0 + ((seed % 58) * 0.1);
378        } else if (seedMod < 56) {
379          // Bagus tier (72.0% - 91.5%)
380          baseRtp = 74.0 + ((seed % 175) * 0.1);
381        } else if (seedMod < 76) {
382          // Sedang tier (42.0% - 71.5%)
383          baseRtp = 45.0 + ((seed % 265) * 0.1);
384        } else if (seedMod < 88) {
385          // Dingin tier (16.0% - 41.5%)
386          baseRtp = 18.0 + ((seed % 235) * 0.1);
387        } else {
388          // Anjlok / Dibawah 10% tier (4.2% - 15.8%)
389          baseRtp = 4.5 + ((seed % 113) * 0.1);
390        }
391      }
392
393      const volatilities = ['Sedang', 'Tinggi', 'Sangat Tinggi', 'Extreme'];
394      const vol = volatilities[seed % volatilities.length];
395
396      const multipliers = ['x5,000', 'x10,000', 'x15,000', 'x21,100', 'x25,000', 'x50,000', 'x100,000', 'x150,000'];
397      const maxMultiplier = multipliers[seed % multipliers.length];
398
399      const basePlayers = Math.floor(1250 + (seed % 3400));
400      const baseJackpot = Math.floor(180000000 + ((seed % 80) * 10000000));
401
402      const quickSpin = 10 + (seed % 4) * 10;
403      const turboSpin = 20 + ((seed >> 2) % 4) * 10;
404      const autoSpin = 30 + ((seed >> 4) % 5) * 10;
405      const dcStatus = (seed % 3 === 0) ? 'OFF' : 'ON';
406
407      const gameObj = {
408        id: gId,
409        name: gTitle,
410        provider: pName,
411        providerId: pId,
412        image: gImage,
413        fallbackImage: 'https://files.sitestatic.net/games/i/165x220/6d39308186b2c0ede30a49d0c486e911.webp?v=0.2',
414        baseRtp: parseFloat(baseRtp.toFixed(1)),
415        volatility: vol,
416        maxMultiplier: maxMultiplier,
417        basePlayers: basePlayers,
418        baseJackpot: baseJackpot,
419        pattern: {
420          quick: `${quickSpin}x`,
421          turbo: `${turboSpin}x`,
422          auto: `${autoSpin}x`,
423          dc: dcStatus
424        },
425        seed: seed
426      };
427
428      ALL_GAMES.push(gameObj);
429      GAMES_BY_PROVIDER['Semua'].push(gameObj);
430      GAMES_BY_PROVIDER[pId].push(gameObj);
431      GAMES_BY_ID[gId] = gameObj;
432    });
433  });
434
435  ALL_PROVIDERS[0].count = ALL_GAMES.length;
436  initPopularGamesPool();
437}
438
439function initPopularGamesPool() {
440  const priorityKeywords = [
441    'gates of olympus', 'mahjong ways', 'sweet bonanza', 'starlight princess',
442    'sugar rush', 'wild bandito', 'lucky neko', 'koi gate', 'san quentin',
443    'gatot kaca', 'rujak bonanza', 'wisdom of athena', 'fa cai shen', 'caishen wins',
444    'hot hot fruit', 'mental', 'tombstone', 'super ace', 'jump high', 'roma legacy',
445    'brothers kingdom', 'wild west gold', 'madame destiny', 'treasures of aztec',
446    'wukong', 'bang gacor', 'king cat', 'wanted dead', 'le bandit', 'rip city'
447  ];
448
449  const popularCandidates = [];
450  priorityKeywords.forEach(keyword => {
451    const match = ALL_GAMES.find(g => g.name && g.name.toLowerCase().includes(keyword));
452    if (match && !popularCandidates.some(c => c.id === match.id)) {
453      popularCandidates.push(match);
454    }
455  });
456
457  if (popularCandidates.length < 15) {
458    ALL_GAMES.forEach(g => {
459      if (popularCandidates.length < 15 && !popularCandidates.some(c => c.id === g.id)) {
460        popularCandidates.push(g);
461      }
462    });
463  }
464
465  const currentDailyFactor = getDailyGrowthFactor();
466
467  POPULAR_GAMES_POOL = popularCandidates.slice(0, 10).map((g, idx) => {
468    const computedBaseCount = Math.floor(((g.basePlayers || 1500) + (15 
468- idx) * 450) * currentDailyFactor);
469    const computedBaseJackpot = Math.floor(((g.baseJackpot || 200000000) + (15 - idx) * 55000000) * currentDailyFactor);
470
471    const savedCount = (persistedState.popularPlayerCounts && persistedState.popularPlayerCounts[g.id]) || computedBaseCount;
472    const savedJackpot = (persistedState.popularJackpots && persistedState.popularJackpots[g.id]) || computedBaseJackpot;
473
474    return {
475      id: g.id,
476      name: g.name,
477      provider: g.provider,
478      providerId: g.providerId,
479      image: g.image,
480      fallbackImage: g.fallbackImage,
481      baseCount: computedBaseCount,
482      count: Math.max(computedBaseCount, savedCount),
483      baseJackpot: computedBaseJackpot,
484      jackpotVal: Math.max(computedBaseJackpot, savedJackpot),
485      baseRtp: g.baseRtp || 97.5,
486      volatility: g.volatility || 'Tinggi',
487      maxMultiplier: g.maxMultiplier || 'x15,000'
488    };
489  });
490}
491
492async function loadDataSlotEngine() {
493  try {
494    let rawData = null;
495
496    // 1. Check embedded script from server first (0ms, no network error)
497    const embeddedScript = document.getElementById('rtp-embedded-data');
498    if (embeddedScript && embeddedScript.textContent && embeddedScript.textContent.trim().length > 100) {
499      try {
500        rawData = JSON.parse(embeddedScript.textContent);
501      } catch (e0) {}
502    }
503
504    if (!rawData || !Array.isArray(rawData)) {
505      try {
506        const response = await fetch('json/data-slot.json');
507        if (response.ok) {
508          rawData = await response.json();
509        }
510      } catch (e1) {
511        // Ignore relative path error
512      }
513    }
514
515    if (!rawData || !Array.isArray(rawData)) {
516      try {
517        const response2 = await fetch('/json/data-slot.json');
518        if (response2.ok) {
519          rawData = await response2.json();
520        }
521      } catch (e2) {
522        // Ignore absolute path error
523      }
524    }
525
526    if (Array.isArray(rawData) && rawData.length > 0) {
527      processRawDataSlot(rawData);
528    }
529  } catch (err) {
530    console.warn('Background data-slot.json loader notice:', err);
531  }
532}
533
534/* ==========================================================================
535   7. Natural Multi-Tier RTP Spectrum Algorithm (Diverse 3.2% - 99.2%)
536   ========================================================================== */
537function calculateDynamicRTP(game, dayIndex = 0) {
538  if (!game) return 97.5;
539  const gameSeed = game.seed || getGameSeed(game.id || game.name);
540
541  // 1. Natural Base RTP
542  const base = (typeof game.baseRtp === 'number' && !isNaN(game.baseRtp)) ? game.baseRtp : 85.0;
543
544  // 2. 7-Day Rotational Cycle Shift (Rotates which games are hot/cold per day)
545  const dayFactor = ((gameSeed * 29 + dayIndex * 47) % 23) - 11;
546  const dayShift = dayFactor * 0.55;
547
548  // 3. 24-Hour Sinusoidal Wave (Simulates peak gaming traffic and hourly volatility)
549  const currentHour = new Date().getHours();
550  const hourAngle = (((currentHour + (gameSeed % 5)) % 24) / 24) * 2 * Math.PI;
551  const hourEffect = Math.sin(hourAngle - 1.2) * 2.5 + Math.cos(hourAngle * 2) * 1.0;
552
553  // 4. Live Runtime Micro-Jitter
554  const liveJitter = liveRtpJitterMap[game.id] || 0;
555
556  let calculated = base + dayShift + hourEffect + liveJitter;
557
558  // Clamp realistically between 3.2% (below 10%) and 99.2%
559  if (calculated > 99.2) calculated = 99.2;
560  if (calculated < 3.2) calculated = 3.2;
561
562  return parseFloat(calculated.toFixed(1));
563}
564
565function getRTPClassification(rtp) {
566  if (rtp >= 92.0) {
567    return {
568      status: 'GACOR MAX',
569      fillClass: 'fill-gacor',
570      valClass: 'val-gacor',
571      isGacor: true,
572      badgeHtml: '<span class="ancua-hot-badge"><i class="fa-solid fa-fire"></i> GACOR</span>'
573    };
574  } else if (rtp >= 72.0) {
575    return {
576      status: 'BAGUS',
577      fillClass: 'fill-hot',
578      valClass: 'val-hot',
579      isGacor: false,
580      badgeHtml: ''
581    };
582  } else if (rtp >= 40.0) {
583    return {
584      status: 'SEDANG',
585      fillClass: 'fill-medium',
586      valClass: 'val-medium',
587      isGacor: false,
588      badgeHtml: ''
589    };
590  } else if (rtp >= 15.0) {
591    return {
592      status: 'DINGIN',
593      fillClass: 'fill-cold',
594      valClass: 'val-cold',
595      isGacor: false,
596      badgeHtml: '<span class="ancua-cold-badge" style="position:absolute; top:6px; right:6px; background:rgba(180,83,9,0.85); color:#fed7aa; font-size:9px; font-weight:800; padding:2px 6px; border-radius:4px; border:1px solid #f97316;"><i class="fa-solid fa-triangle-exclamation"></i> WASPADA</span>'
597    };
598  } else {
599    return {
600      status: 'ANJLOK',
601      fillClass: 'fill-anjlok',
602      valClass: 'val-anjlok',
603      isGacor: false,
604      badgeHtml: '<span class="ancua-cold-badge" style="position:absolute; top:6px; right:6px; background:linear-gradient(135deg,#7f1d1d,#450a0a); color:#fca5a5; font-size:9px; font-weight:900; padding:2px 6px; border-radius:4px; border:1px solid #ef4444; box-shadow:0 0 8px rgba(239,68,68,0.5);"><i class="fa-solid fa-snowflake"></i> ANJLOK</span>'
605    };
606  }
607}
608
609/* ==========================================================================
610   8. 7-Day Live RTP Date Generator & Clean Provider Navigation
611   ========================================================================== */
612function initDateTabs() {
613  const daysNav = document.getElementById('rtpDaysNav');
614  if (!daysNav) return;
615
616  daysNav.innerHTML = '';
617  const indonesianDays = ['Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu'];
618  const indonesianMonths = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Ags', 'Sep', 'Okt', 'Nov', 'Des'];
619
620  const today = new Date();
621
622  for (let i = 0; i < 7; i++) {
623    const d = new Date();
624    d.setDate(today.getDate() + i);
625
626    const dayName = indonesianDays[d.getDay()];
627    const dateNum = d.getDate();
628    const monthName = indonesianMonths[d.getMonth()];
629    const dateFormatted = `${dateNum} ${monthName}`;
630
631    let badgeHtml = '';
632    if (i === 0) {
633      badgeHtml = '<span class="day-tab-badge"><i class="fa-solid fa-bolt"></i> HARI INI</span>';
634    } else if (i === 1) {
635      badgeHtml = '<span class="day-tab-badge" style="color: #60a5fa;"><i class="fa-solid fa-calendar-day"></i> BESOK</span>';
636    } else if (d.getDay() === 0 || d.getDay() === 6) {
637      badgeHtml = '<span class="day-tab-badge" style="color: #ef4444;"><i class="fa-solid fa-star"></i> WEEKEND</span>';
638    }
639
640    const btn = document.createElement('button');
641    btn.type = 'button';
642    btn.className = `day-tab-btn ${i === 0 ? 'active' : ''}`;
643    btn.innerHTML = `
644      <span class="day-tab-name">${dayName}</span>
645      <span class="day-tab-date">${dateFormatted}</span>
646      ${badgeHtml}
647    `;
648
649    btn.addEventListener('click', () => {
650      document.querySelectorAll('.day-tab-btn').forEach(b => b.classList.remove('active'));
651      btn.classList.add('active');
652      currentSelectedDayIndex = i;
653      currentDisplayedCount = PAGE_SIZE;
654      renderRTPCards();
655    });
656
657    daysNav.appendChild(btn);
658  }
659
660  renderProviderControls();
661}
662
663function renderProviderControls() {
664  const providerContainer = document.getElementById('providerPills');
665  if (!providerContainer) return;
666
667  providerContainer.innerHTML = '';
668
669  ALL_PROVIDERS.forEach(p => {
670    const pill = document.createElement('button');
671    pill.type = 'button';
672    pill.className = `provider-pill ${currentSelectedProvider === p.id ? 'active' : ''}`;
673    pill.innerHTML = `
674      <i class="fa-solid ${p.icon}"></i> ${p.name}
675      <span class="provider-badge-count">${p.count}</span>
676    `;
677    pill.addEventListener('click', () => {
678      selectProvider(p.id);
679    });
680    providerContainer.appendChild(pill);
681  });
682}
683
684function selectProvider(providerId) {
685  currentSelectedProvider = providerId;
686  currentDisplayedCount = PAGE_SIZE;
687
688  document.querySelectorAll('.provider-pill').forEach(pill => {
689    const text = pill.textContent.toLowerCase();
690    const matched = ALL_PROVIDERS.find(p => p.id === providerId);
691    if (matched && text.includes(matched.name.toLowerCase())) {
692      pill.classList.add('active');
693    } else {
694      pill.classList.remove('active');
695    }
696  });
697
698  renderRTPCards();
699}
700
701/* ==========================================================================
702   9. High-Performance RTP Grid & Chunked Pagination
703   ========================================================================== */
704function initRTPGrid() {
705  renderRTPCards();
706  renderPopularGames();
707}
708
709function getFilteredGames() {
710  const gamesPool = currentSelectedProvider === 'Semua' 
711    ? ALL_GAMES 
712    : (GAMES_BY_PROVIDER[currentSelectedProvider] || []);
713
714  if (!searchQuery) return gamesPool;
715
716  const q = searchQuery.toLowerCase().trim();
717  return gamesPool.filter(g => 
718    (g.name && g.name.toLowerCase().includes(q)) || 
719    (g.provider && g.provider.toLowerCase().includes(q))
720  );
721}
722
723function renderRTPCards() {
724  const grid = document.getElementById('rtpCardsGrid');
725  const loadMoreWrap = document.getElementById('rtpLoadMoreWrap');
726  if (!grid) return;
727
728  const filtered = getFilteredGames();
729
730  const counterEl = document.getElementById('rtpGameCount');
731  if (counterEl) {
732    counterEl.innerHTML = `<i class="fa-solid fa-layer-group"></i> ${filtered.length} Game Tersedia`;
733  }
734
735  if (filtered.length === 0) {
736    grid.innerHTML = `
737      <div style="grid-column: 1 / -1; text-align: center; padding: 3rem 1.5rem; background: rgba(20, 5, 7, 0.95); border-radius: 12px; border: 1px dashed rgba(220, 38, 38, 0.45);">
738        <i class="fa-solid fa-gamepad" style="font-size: 2.5rem; color: #ffd700; margin-bottom: 1rem;"></i>
739        <h3 style="color: #ffffff; margin-bottom: 0.5rem; font-size: 1.3rem; font-weight: 800;">Game Tidak Ditemukan</h3>
740        <p style="color: #94a3b8; max-width: 440px; margin: 0 auto 1.25rem;">Coba cari dengan kata kunci lain atau pilih tab provider di atas.</p>
741        <button class="btn btn-secondary btn-sm" onclick="resetFilters()">
742          <i class="fa-solid fa-arrows-rotate"></i> Reset Filter
743        </button>
744      </div>
745    `;
746    if (loadMoreWrap) loadMoreWrap.innerHTML = '';
747    return;
748  }
749
750  const gamesToDisplay = filtered.slice(0, currentDisplayedCount);
751
752  grid.innerHTML = gamesToDisplay.map((game) => {
753    const rtp = calculateDynamicRTP(game, currentSelectedDayIndex);
754    const meta = getRTPClassification(rtp);
755    const volShort = (game.volatility || 'Tinggi').split(' ')[0];
756
757    return `
758      <div class="ancua-game-card game-card-fade-in" data-game-id="${game.id}">
759        <div class="ancua-thumb-wrapper">
760          <img src="${game.image}" alt="${game.name}" height="200" width="200" loading="lazy" onerror="this.onerror=null; this.src='https://files.sitestatic.net/games/i/165x220/6d39308186b2c0ede30a49d0c486e911.webp?v=0.2';" />
761          <span class="ancua-provider-tag">${game.provider || 'SLOT'}</span>
762          ${meta.badgeHtml}
763          <div class="ancua-hover-links">
764            <button class="ancua-btn-pola" onclick="showPolaModal('${game.id}')">POLA</button>
765            <a href="${URL_DAFTAR}" target="_blank" rel="noopener" class="ancua-btn-play">MAIN</a>
766          </div>
767        </div>
768        <div class="ancua-card-footer">
769          <div class="ancua-game-name" title="${game.name}">${game.name}</div>
770          
771          <div class="ancua-rtp-row">
772            <span class="ancua-rtp-label">RTP Live:</span>
773            <span class="ancua-rtp-val ${meta.valClass}">${rtp}%</span>
774          </div>
775
776          <div class="ancua-bar-track">
777            <div class="ancua-bar-fill ${meta.fillClass}" style="width: ${rtp}%"></div>
778          </div>
779
780          <div class="ancua-meta-row">
781            <span class="ancua-status-tag ${meta.valClass}">${meta.status}</span>
782            <span class="ancua-vol-tag">Vol: ${volShort}</span>
783          </div>
784        </div>
785      </div>
786    `;
787  }).join('');
788
789  if (loadMoreWrap) {
790    if (currentDisplayedCount < filtered.length) {
791      const remaining = filtered.length - currentDisplayedCount;
792      const nextBatch = Math.min(PAGE_SIZE, remaining);
793      loadMoreWrap.innerHTML = `
794        <div class="rtp-showing-info">
795          Menampilkan <strong>${gamesToDisplay.length}</strong> dari <strong>${filtered.length}</strong> Game Slot
796        </div>
797        <button class="btn-rtp-load-more" onclick="loadMoreRTPGames()">
798          <i class="fa-solid fa-circle-chevron-down"></i> Muat Lebih Banyak (+${nextBatch} Game)
799        </button>
800      `;
801    } else {
802      loadMoreWrap.innerHTML = `
803        <div class="rtp-showing-info">
804          Semua <strong>${filtered.length}</strong> Game Slot Telah Ditampilkan
805        </div>
806      `;
807    }
808  }
809}
810
811window.loadMoreRTPGames = function() {
812  currentDisplayedCount += PAGE_SIZE;
813  renderRTPCards();
814};
815
816window.resetFilters = function() {
817  currentSelectedProvider = 'Semua';
818  searchQuery = '';
819  currentDisplayedCount = PAGE_SIZE;
820  const searchInputs = document.querySelectorAll('.search-input, #headerSearchInput, #mobileSearchInput');
821  searchInputs.forEach(i => i.value = '');
822  selectProvider('Semua');
823};
824
825/* ==========================================================================
826   10. Popular Games Leaderboard & Live Volatility Engine
827   ========================================================================== */
828let currentTopPopularGameIds = [];
829
830function renderPopularGames() {
831  const container = document.getElementById('popularGamesGrid');
832  if (!container || !POPULAR_GAMES_POOL.length) return;
833
834  POPULAR_GAMES_POOL.sort((a, b) =>
834 b.count - a.count);
835  const topPopular = POPULAR_GAMES_POOL.slice(0, 10);
836  currentTopPopularGameIds = topPopular.map(g => g.id);
837
838  container.innerHTML = topPopular.map((game, index) => {
839    let rankBadgeClass = '';
840    if (index === 0) rankBadgeClass = 'gold-rank';
841    else if (index === 1) rankBadgeClass = 'silver-rank';
842    else if (index === 2) rankBadgeClass = 'bronze-rank';
843
844    return `
845      <div class="ancua-game-card popular-ranked" id="pop-card-${game.id}">
846        <div class="ancua-thumb-wrapper">
847          <img src="${game.image}" alt="${game.name}" height="200" width="200" loading="lazy" onerror="this.onerror=null; this.src='https://files.sitestatic.net/games/i/165x220/6d39308186b2c0ede30a49d0c486e911.webp?v=0.2';" />
848          <span class="ancua-rank-badge ${rankBadgeClass}">#${index + 1}</span>
849          <div class="ancua-player-tag">
850            <span class="pulse-dot"></span>
851            <span id="player-count-${game.id}">${(game.count || 2500).toLocaleString('id-ID')}</span> Aktif
852          </div>
853          <div class="ancua-hover-links">
854            <button class="ancua-btn-pola" onclick="showPolaModal('${game.id}')">POLA</button>
855            <a href="${URL_DAFTAR}" target="_blank" rel="noopener" class="ancua-btn-play">MAIN</a>
856          </div>
857        </div>
858        <div class="ancua-card-footer">
859          <div class="ancua-game-name" title="${game.name}">${game.name}</div>
860          <div class="ancua-jackpot-text">
861            <span style="color: #94a3b8;">Jackpot:</span>
862            <strong id="jackpot-val-${game.id}" style="color: #fef08a;">Rp ${(game.jackpotVal || 350000000).toLocaleString('id-ID')}</strong>
863          </div>
864        </div>
865      </div>
866    `;
867  }).join('');
868}
869
870function initVolatilePlayerCounters() {
871  setInterval(() => {
872    if (!POPULAR_GAMES_POOL.length) return;
873
874    POPULAR_GAMES_POOL.forEach(game => {
875      const isDrawdown = Math.random() < 0.20;
876      if (isDrawdown) {
877        const drawdownPct = (Math.random() * 0.04) + 0.01;
878        const dip = Math.floor((game.count || 2000) * drawdownPct * 0.12) + 4;
879        game.count = Math.max(game.baseCount || 1000, (game.count || 2000) - dip);
880      } else {
881        const surgeGrowth = (Math.random() * 0.30) + 0.10;
882        const boostImpulse = Math.random() < 0.22 ? Math.floor(Math.random() * 95) + 30 : 0;
883        const jump = Math.floor(18 * (1 + surgeGrowth)) + Math.floor(Math.random() * 12) + boostImpulse;
884        game.count = (game.count || 2000) + jump;
885      }
886
887      const isJackpotDrawdown = Math.random() < 0.20;
888      if (isJackpotDrawdown) {
889        const dipPct = (Math.random() * 0.04) + 0.01;
890        const dip = Math.floor((game.jackpotVal || 300000000) * dipPct * 0.003) + 15000;
891        game.jackpotVal = Math.max(game.baseJackpot || game.jackpotVal || 200000000, (game.jackpotVal || 300000000) - dip);
892      } else {
893        const surgeGrowth = (Math.random() * 0.30) + 0.10;
894        const jump = Math.floor(65000 * (1 + surgeGrowth)) + Math.floor(Math.random() * 25000);
895        game.jackpotVal = (game.jackpotVal || 300000000) + jump;
896      }
897    });
898
899    POPULAR_GAMES_POOL.sort((a, b) => b.count - a.count);
900    const newTopPopular = POPULAR_GAMES_POOL.slice(0, 10);
901    const newTopIds = newTopPopular.map(g => g.id);
902
903    const hasOrderChanged = newTopIds.some((id, idx) => id !== currentTopPopularGameIds[idx]);
904
905    if (hasOrderChanged) {
906      currentTopPopularGameIds = newTopIds;
907      const container = document.getElementById('popularGamesGrid');
908      if (container) {
909        container.innerHTML = newTopPopular.map((game, index) => {
910          let rankBadgeClass = '';
911          if (index === 0) rankBadgeClass = 'gold-rank';
912          else if (index === 1) rankBadgeClass = 'silver-rank';
913          else if (index === 2) rankBadgeClass = 'bronze-rank';
914
915          return `
916            <div class="ancua-game-card popular-ranked rolling-enter" id="pop-card-${game.id}">
917              <div class="ancua-thumb-wrapper">
918                <img src="${game.image}" alt="${game.name}" height="200" width="200" loading="lazy" onerror="this.onerror=null; this.src='https://files.sitestatic.net/games/i/165x220/6d39308186b2c0ede30a49d0c486e911.webp?v=0.2';" />
919                <span class="ancua-rank-badge ${rankBadgeClass}">#${index + 1}</span>
920                <div class="ancua-player-tag">
921                  <span class="pulse-dot"></span>
922                  <span id="player-count-${game.id}">${(game.count || 2500).toLocaleString('id-ID')}</span> Aktif
923                </div>
924                <div class="ancua-hover-links">
925                  <button class="ancua-btn-pola" onclick="showPolaModal('${game.id}')">POLA</button>
926                  <a href="${URL_DAFTAR}" target="_blank" rel="noopener" class="ancua-btn-play">MAIN</a>
927                </div>
928              </div>
929              <div class="ancua-card-footer">
930                <div class="ancua-game-name" title="${game.name}">${game.name}</div>
931                <div class="ancua-jackpot-text">
932                  <span style="color: #94a3b8;">Jackpot:</span>
933                  <strong id="jackpot-val-${game.id}" style="color: #fef08a;">Rp ${(game.jackpotVal || 350000000).toLocaleString('id-ID')}</strong>
934                </div>
935              </div>
936            </div>
937          `;
938        }).join('');
939      }
940    } else {
941      newTopPopular.forEach(game => {
942        const countEl = document.getElementById(`player-count-${game.id}`);
943        if (countEl) countEl.textContent = (game.count || 2500).toLocaleString('id-ID');
944        const jackpotEl = document.getElementById(`jackpot-val-${game.id}`);
945        if (jackpotEl) jackpotEl.textContent = `Rp ${(game.jackpotVal || 350000000).toLocaleString('id-ID')}`;
946      });
947    }
948
949    savePersistedState();
950  }, 2400);
951}
952
953/* ==========================================================================
954   11. Real-Time Telemetry HUD Engine
955   ========================================================================== */
956function initLiveKPIDashboard() {
957  const payoutEl = document.getElementById('kpTotalPayout');
958  const rtpEl = document.getElementById('kpAvgRtp');
959  const playersEl = document.getElementById('kpTotalPlayers');
960  const wdSpeedEl = document.getElementById('kpWdSpeed');
961
962  const basePayoutFloor = Math.floor(2879450000 * getDailyGrowthFactor());
963  const basePlayersFloor = Math.floor(49155 * getDailyGrowthFactor());
964
965  setInterval(() => {
966    const isPayoutDrawdown = Math.random() < 0.25;
967    if (isPayoutDrawdown) {
968      const dipPct = (Math.random() * 0.04) + 0.01;
969      const dip = Math.floor(120000 * dipPct * 6) + 25000;
970      liveTotalPayout = Math.max(basePayoutFloor, liveTotalPayout - dip);
971    } else {
972      const payoutSurgeFactor = (Math.random() * 0.30) + 0.10;
973      const surge = Math.floor(450000 * (1 + payoutSurgeFactor)) + Math.floor(Math.random() * 120000);
974      liveTotalPayout += surge;
975    }
976
977    if (payoutEl) {
978      const formattedMillions = (liveTotalPayout / 1000000000).toFixed(3);
979      payoutEl.textContent = `Rp ${formattedMillions} M+`;
980      payoutEl.title = `Total Payout: Rp ${liveTotalPayout.toLocaleString('id-ID')}`;
981      if (!isPayoutDrawdown) {
982        payoutEl.classList.add('spike');
983        setTimeout(() => payoutEl.classList.remove('spike'), 400);
984      }
985    }
986
987    const rtpJitter = (Math.random() * 0.08 - 0.04);
988    liveAvgRtp = parseFloat((98.72 + rtpJitter).toFixed(2));
989    if (rtpEl) {
990      rtpEl.textContent = `${liveAvgRtp}%`;
991      rtpEl.classList.add('spike');
992      setTimeout(() => rtpEl.classList.remove('spike'), 400);
993    }
994
995    const isPlayerDrawdown = Math.random() < 0.25;
996    if (isPlayerDrawdown) {
997      const dipFactor = (Math.random() * 0.04) + 0.01;
998      const dip = Math.floor(45 * dipFactor) + 12;
999      liveTotalPlayers = Math.max(basePlayersFloor, liveTotalPlayers - dip);
1000    } else {
1001      const surgeMultiplier = (Math.random() * 0.30) + 0.10;
1002      const surge = Math.floor(55 * (1 + surgeMultiplier)) + Math.floor(Math.random() * 20);
1003      liveTotalPlayers += surge;
1004    }
1005
1006    if (playersEl) {
1007      playersEl.textContent = `${liveTotalPlayers.toLocaleString('id-ID')}+`;
1008      if (!isPlayerDrawdown) {
1009        playersEl.classList.add('spike');
1010        setTimeout(() => playersEl.classList.remove('spike'), 400);
1011      }
1012    }
1013
1014    if (wdSpeedEl) {
1015      const seconds = Math.floor(Math.random() * 15) + 32;
1016      wdSpeedEl.textContent = `< 1m ${seconds}s`;
1017    }
1018
1019    savePersistedState();
1020  }, 2200);
1021}
1022
1023/* ==========================================================================
1024   12. Grand Progressive Jackpot Counter
1025   ========================================================================== */
1026function initGrandProgressiveJackpot() {
1027  const jackpotEl = document.getElementById('progressive_jackpot');
1028  if (!jackpotEl) return;
1029
1030  function updateGrandJackpot() {
1031    const jump = Math.floor(Math.random() * 125000) + 35000;
1032    liveGrandJackpot += jump;
1033    jackpotEl.textContent = liveGrandJackpot.toLocaleString('id-ID');
1034  }
1035
1036  jackpotEl.textContent = liveGrandJackpot.toLocaleString('id-ID');
1037  setInterval(updateGrandJackpot, 1800);
1038}
1039
1040/* ==========================================================================
1041   13. Dynamic Pola Gacor & Jam Hoki Modal Engine
1042   ========================================================================== */
1043window.showPolaModal = function(gameId) {
1044  const game = GAMES_BY_ID[gameId] || ALL_GAMES.find(g => String(g.id) === String(gameId));
1045  if (!game) return;
1046
1047  const modal = document.getElementById('polaModalOverlay');
1048  const title = document.getElementById('polaGameTitle');
1049  const body = document.getElementById('polaModalContent');
1050
1051  const currentHour = new Date().getHours();
1052  const startHour = (currentHour + 23) % 24;
1053  const endHour = (currentHour + 2) % 24;
1054  const formatHour = (h) => (h < 10 ? `0${h}` : `${h}`);
1055  const jamGacorWindow = `${formatHour(startHour)}:30 - ${formatHour(endHour)}:45 WIB`;
1056
1057  const dynamicRtp = calculateDynamicRTP(game, currentSelectedDayIndex);
1058  const meta = getRTPClassification(dynamicRtp);
1059
1060  const pattern = game.pattern || { quick: '20x', turbo: '30x', auto: '50x', dc: 'ON' };
1061  const maxMultiplier = game.maxMultiplier || 'x15,000';
1062
1063  if (title) {
1064    title.innerHTML = `<i class="fa-solid fa-fire text-gold"></i> Pola Gacor: ${game.name}`;
1065  }
1066
1067  if (body) {
1068    body.innerHTML = `
1069      <div style="text-align: center; margin-bottom: 14px;">
1070        <img src="${game.image}" alt="${game.name}" style="width: 100px; height: 100px; border-radius: 8px; margin: 0 auto 8px; object-fit: cover; border: 2px solid #dc2626; display: block; box-shadow: 0 0 16px rgba(220, 38, 38, 0.4);" onerror="this.onerror=null; this.src='https://files.sitestatic.net/games/i/165x220/6d39308186b2c0ede30a49d0c486e911.webp?v=0.2';">
1071        <h3 style="color: #ffffff; font-size: 15px; font-weight: 800; margin: 0 0 4px;">${game.name}</h3>
1072        <span style="color: #ffd700; font-size: 12px; font-weight: 700;">${game.provider || 'SLOT'} • Max ${maxMultiplier}</span>
1073      </div>
1074
1075      <div style="background: #0d0204; border: 1px solid #3b0d11; border-radius: 8px; padding: 12px; margin-bottom: 14px; display: flex; flex-direction: column; gap: 8px;">
1076        <div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #23080b; padding-bottom: 6px;">
1077          <span style="color: #94a3b8; font-size: 12px;"><i class="fa-solid fa-hand-pointer text-gold"></i> Spin Manual (Cepat):</span>
1078          <strong style="color: #ffffff; font-size: 12px;">${pattern.quick} (Centang)</strong>
1079        </div>
1080        <div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #23080b; padding-bottom: 6px;">
1081          <span style="color: #94a3b8; font-size: 12px;"><i class="fa-solid fa-bolt text-gold"></i> Spin Turbo:</span>
1082          <strong style="color: #ffd700; font-size: 12px;">${pattern.turbo}</strong>
1083        </div>
1084        <div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #23080b; padding-bottom: 6px;">
1085          <span style="color: #94a3b8; font-size: 12px;"><i class="fa-solid fa-arrows-rotate text-gold"></i> Spin Auto:</span>
1086          <strong style="color: #10b981; font-size: 12px;">${pattern.auto}</strong>
1087        </div>
1088        <div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #23080b; padding-bottom: 6px;">
1089          <span style="color: #94a3b8; font-size: 12px;"><i class="fa-solid fa-sliders text-gold"></i> Double Chance (DC):</span>
1090          <strong style="color: ${pattern.dc === 'ON' ? '#10b981' : '#f87171'}; font-size: 12px;">${pattern.dc}</strong>
1091        </div>
1092        <div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #23080b; padding-bottom: 6px;">
1093          <span style="color: #94a3b8; font-size: 12px;"><i class="fa-regular fa-clock text-gold"></i> Jam Gacor Hoki:</span>
1094          <span class="pola-time-badge"><i class="fa-solid fa-fire"></i> ${jamGacorWindow}</span>
1095        </div>
1096        <div style="display: flex; justify-content: space-between; align-items: center;">
1097          <span style="color: #94a3b8; font-size: 12px;">Estimasi RTP Live:</span>
1098          <strong style="font-size: 14px; font-weight: 800;" class="${meta.valClass}">${dynamicRtp}% (${meta.status})</strong>
1099        </div>
1100      </div>
1101
1102      <a href="${URL_DAFTAR}" target="_blank" rel="noopener noreferrer" class="btn-vip-primary" style="width: 100%; text-align: center; display: block; padding: 10px; font-size: 13px; font-weight: 900; border-radius: 6px; box-shadow: 0 0 14px rgba(56, 189, 248, 0.5);">
1103        <i class="fa-solid fa-play"></i> MAIN GAME INI SEKARANG
1104      </a>
1105    `;
1106  }
1107
1108  if (modal) modal.classList.add('open');
1109};
1110
1111function initModals() {
1112  const polaOverlay = document.getElementById('polaModalOverlay');
1113
1114  document.querySelectorAll('.modal-close, .modal-backdrop-close').forEach(btn => {
1115    btn.addEventListener('click', () => {
1116      if (polaOverlay) polaOverlay.classList.remove('open');
1117    });
1118  });
1119
1120  const loginForm = document.getElementById('loginForm');
1121  if (loginForm) {
1122    loginForm.addEventListener('submit', (e) => {
1123      e.preventDefault();
1124      window.open(URL_LOGIN, '_blank');
1125      if (typeof $ !== 'undefined' && $('#login_modal').length) {
1126        $('#login_modal').modal('hide');
1127      }
1128    });
1129  }
1130}
1131
1132/* ==========================================================================
1133   14. Hourly Dynamic Testimonials (Uses Real Game Catalog)
1134   ========================================================================== */
1135let lastGeneratedHour = -1;
1136let currentHourlyTestimonials = [];
1137
1138function generateFreshHourlyTestimonials(hourSeed) {
1139  const tiers = ['Member VIP', 'Member Platinum', 'Member Gold', 'Member VIP Prioritas'];
1140  const results = [];
1141
1142  const quoteStarters = [
1143    (g, b, amt) => `Awalnya ragu dengan bocoran RTP live 7 hari, pas coba pola jam gacor di ${g}, langsung pecah perkalian Sensational! WD Rp ${amt.toLocaleString('id-ID')} mendarat di ${b} kurang dari 2 menit.`,
1144    (g, b, amt) => `Main di sini benar-benar transparan. Jam hoki di ${g} akurat banget sama bocoran live RTP. Penarikan Rp ${amt.toLocaleString('id-ID')} cair utuh tanpa potongan admin via ${b}.`,
1145    (g, b, amt) => `Scatter dan jackpot terus menyambung di ${g}. Fitur pola turbo spin ngebantu banget dapat maxwin Rp ${amt.toLocaleString('id-ID')}. Customer service 24 jam responsif!`,
1146    (g, b, amt) => `Sensasi kemenangan di ${g} luar biasa! Pasang taruhan santai pas RTP tercatat 98.6%, langsung cair Rp ${amt.toLocaleString('id-ID')} via ${b}. Web merah putihnya juga super kencang di HP.`,
1147    (g, b, amt) => `Sudah langganan main di sini. Rekomendasi game terpopuler ${g} terbukti anti-rungkad. Tarik dana Rp ${amt.toLocaleString('id-ID')} ke ${b} hitungan detik masuk.`,
1148    (g, b, amt) => `Bocoran RTP per hari sangat membantu analisis jam gacor ${g}. WD Rp ${amt.toLocaleString('id-ID')} langsung diproses tanpa ribet. Situs paling aman dan terpercaya!`
1149  ];
1150
1151  const candidateGames = ALL_GAMES.length > 0 
1152    ? ALL_GAMES.slice(0, 30).map(g => g.name)
1153    : ['Gates of Olympus 1000', 'Mahjong Ways 2', 'Sweet Bonanza 1000', 'Lucky Neko', 'Sugar Rush 1000', 'Wild Bandito', 'Koi Gate'];
1154
1155  for (let i = 0; i < 8; i++) {
1156    const profile = generateProceduralName();
1157    const city = CITIES_POOL[(hourSeed * 7 + i * 3) % CITIES_POOL.length];
1158    const tier = tiers[(hourSeed + i) % tiers.length];
1159    const game = candidateGames[(hourSeed * 3 + i) % candidateGames.length];
1160    const bank = BANKS_POOL[(hourSeed + i * 2) % BANKS_POOL.length];
1161    const color = AVATAR_GRADIENTS[(hourSeed + i) % AVATAR_GRADIENTS.length];
1162    
1163    const amtBase = Math.floor(((hourSeed * 13 + i * 29) % 36) + 9) * 2000000 + ((i % 3) * 500000);
1164    const quote = quoteStarters[i % quoteStarters.length](game, bank, amtBase);
1165    const minuteAgo = (i * 7) + 3;
1166
1167    results.push({
1168      name: profile.fullName,
1169      tag: `${tier} • ${city}`,
1170      initials: profile.initials,
1171      color: color,
1172      game: game,
1173      amount: amtBase,
1174      bank: bank,
1175      quote: quote,
1176      minuteAgo: minuteAgo
1177    });
1178  }
1179
1180  return results;
1181}
1182
1183function initHourlyTestimonials() {
1184  function checkAndRenderHourly() {
1185    const currentHour = new Date().getHours();
1186    const dayOfMonth = new Date().getDate();
1187    const uniqueHourSeed = (dayOfMonth * 24) + currentHour;
1188
1189    if (uniqueHourSeed !== lastGeneratedHour) {
1190      lastGeneratedHour = uniqueHourSeed;
1191      currentHourlyTestimonials = generateFreshHourlyTestimonials(uniqueHourSeed);
1192      renderHourlyTestimonials();
1193    }
1194  }
1195
1196  checkAndRenderHourly();
1197  setInterval(checkAndRenderHourly, 60000);
1198}
1199
1200function renderHourlyTestimonials() {
1201  const container = document.getElementById('testimonialsGrid');
1202  if (!container) return;
1203
1204  container.innerHTML = currentHourlyTestimonials.map(item => `
1205    <div class="testi-card">
1206      <div class="testi-top">
1207        <div class="testi-member">
1208          <div class="testi-avatar" style="background: ${item.color};">${item.initials}</div>
1209          <div class="testi-info">
1210            <h4>${item.name}</h4>
1211            <span>${item.tag}</span>
1212          </div>
1213        </div>
1214        <div class="testi-rating">
1215          <i class="fa-solid fa-star"></i>
1216          <i class="fa-solid fa-star"></i>
1217          <i class="fa-solid fa-star"></i>
1218          <i class="fa-solid fa-star"></i>
1219          <i class="fa-solid fa-star"></i>
1220        </div>
1221      </div>
1222      <div class="testi-payout-box">
1223        <span class="payout-label"><i class="fa-solid fa-wallet text-green"></i> Penarikan Sukses (${item.bank}):</span>
1224        <span class="payout-amount">Rp ${item.amount.toLocaleString('id-ID')}</span>
1225      </div>
1226      <p class="testi-comment">
1227        "${item.quote}"
1228      </p>
1229      <div class="testi-footer">
1230        <span><i class="fa-solid fa-gamepad text-gold"></i> Game: <strong>${item.game}</strong></span>
1231        <span><i class="fa-regular fa-clock"></i> ${item.minuteAgo} menit lalu</span>
1232      </div>
1233    </div>
1234  `).join('');
1235}
1236
1237// FAQ Accordion Toggle
1238window.toggleFaq = function(btn) {
1239  const item = btn.closest('.faq-item');
1240  if (!item) return;
1241  const isActive = item.classList.contains('active');
1242  document.querySelectorAll('.faq-item').forEach(el => el.classList.remove('active'));
1243  if (!isActive) {
1244    item.classList.add('active');
1245  }
1246};
1247
1248/* ==========================================================================
1249   15. Live Winner Toasts & Marquee Stream
1250   ========================================================================== */
1251function getRandomGameFromCatalog() {
1252  if (ALL_GAMES.length > 0) {
1253    const randomIndex = Math.floor(Math.random() * Math.min(ALL_GAMES.length, 50));
1254    return ALL_GAMES[randomIndex].name;
1255  }
1256  return 'Gates of Olympus 1000';
1257}
1258
1259const NOTIFICATION_TEMPLATES = [
1260  {
1261    type: 'withdraw',
1262    icon: 'fa-money-bill-transfer text-gold',
1263    title: 'Penarikan Dana Berhasil',
1264    genText: (user, city) => {
1265      const bank = BANKS_POOL[Math.floor(Math.random() * BANKS_POOL.length)];
1266      const amount = (Math.floor(Math.random() * 35) + 6) * 1000000;
1267      return `Penarikan dana <strong>Rp ${amount.toLocaleString('id-ID')}</strong> (${bank}) an. <strong>${user}</strong> (${city}) sukses ditransfer.`;
1268    }
1269  },
1270  {
1271    type: 'play',
1272    icon: 'fa-crown text-gold',
1273    title: 'Sensational Payout!',
1274    genText: (user, city) => {
1275      const g = getRandomGameFromCatalog();
1276      const amount = (Math.floor(Math.random() * 32) + 4) * 750000;
1277      return `<strong>${user}</strong> (${city}) meraih Sensational Win <strong>Rp ${amount.toLocaleString('id-ID')}</strong> di <em>${g}</em>!`;
1278    }
1279  },
1280  {
1281    type: 'deposit',
1282    icon: 'fa-bolt text-gold',
1283    title: 'Deposit Terverifikasi',
1284    genText: (user, city) => {
1285      const amount = (Math.floor(Math.random() * 15) + 1) * 500000;
1286      return `Deposit instan <strong>Rp ${amount.toLocaleString('id-ID')}</strong> (QRIS) an. <strong>${user}</strong> (${city}) aktif dalam 5 detik.`;
1287    }
1288  },
1289  {
1290    type: 'daftar',
1291    icon: 'fa-user-plus text-gold',
1292    title: 'Member VIP Baru',
1293    genText: (user, city) => `User <strong>${user}</strong> baru saja mendaftar akun VIP dari ${city} & klaim garansi 100%.`
1294  }
1295];
1296
1297function initLiveToastEngine() {
1298  const container = document.getElementById('toastContainer');
1299  if (!container) return;
1300
1301  function spawnFreshToast() {
1302    const user = getFreshUniqueUsername();
1303    const city = CITIES_POOL[Math.floor(Math.random() * CITIES_POOL.length)];
1304    const template = NOTIFICATION_TEMPLATES[Math.floor(Math.random() * NOTIFICATION_TEMPLATES.length)];
1305
1306    if (container.children.length >= 2) {
1307      const first = container.firstElementChild;
1308      if (first) {
1309        first.classList.add('toast-closing');
1310        setTimeout(() => first.remove(), 300);
1311      }
1312    }
1313
1314    const toastEl = document.createElement('div');
1315    toastEl.className = 'live-toast';
1316    toastEl.innerHTML = `
1317      <div class="toast-icon-wrap">
1318        <i class="fa-solid ${template.icon}"></i>
1319      </div>
1320      <div class="toast-body">
1321        <div class="toast-title">
1322          <span>${template.title}</span>
1323        </div>
1324        <div class="toast-desc">${template.genText(user, city)}</div>
1325        <span class="toast-time"><i class="fa-regular fa-clock"></i> Baru saja</span>
1326      </div>
1327      <button class="toast-close-btn" aria-label="Tutup"><i class="fa-solid fa-xmark"></i></button>
1328    `;
1329
1330    toastEl.querySelector('.toast-close-btn').addEventListener('click', () => {
1331      toastEl.classList.add('toast-closing');
1332      setTimeout(() => toastEl.remove(), 300);
1333    });
1334
1335    container.appendChild(toastEl);
1336
1337    setTimeout(() => {
1338      if (toastEl.parentNode) {
1339        toastEl.classList.add('toast-closing');
1340        setTimeout(() => toastEl.remove(), 300);
1341      }
1342    }, 4200);
1343  }
1344
1345  setInterval(spawnFreshToast, 3000);
1346  setTimeout(spawnFreshToast, 1200);
1347}
1348
1349const LIVE_MARQUEE_STREAM = [
1350  '<i class="fa-solid fa-trophy text-gold"></i> Member <strong style="color: #ffd700;">hendra.kurn***</strong> baru saja WD <strong style="color: #34d399;">Rp 48.500.000</strong> (BCA)',
1351  '<i class="fa-solid fa-fire text-gold"></i> RTP <strong style="color: #ffd700;">Gates of Olympus 1000</strong> melonjak ke <strong style="color: #34d399;">98.8% GACOR</strong>',
1352  '<i class="fa-solid fa-bolt text-gold"></i> Member <strong style="color: #ffd700;">dewi.lestari***</strong> memicu perkalian x500 di <em style="color: #ffd700;">Sweet Bonanza 1000</em>',
1353  '<i class="fa-solid fa-shield-halved text-gold"></i>
1353 Total Akumulasi Jackpot Progresif: <strong style="color: #ffd700;">Rp 8.925.840.120</strong>',
1354  '<i class="fa-solid fa-bolt text-gold"></i> Member <strong style="color: #ffd700;">bayu.pratama***</strong> deposit <strong style="color: #34d399;">Rp 5.000.000</strong> (QRIS Instant 5s)',
1355  '<i class="fa-solid fa-user-plus text-gold"></i> Member baru <strong style="color: #ffd700;">dimas.setia***</strong> bergabung & klaim Bonus VIP'
1356];
1357
1358function initLiveMarquee() {
1359  const track = document.getElementById('liveMarqueeTrack');
1360  const ancuaAnnouncementList = document.getElementById('announcement_list');
1361
1362  function renderMarqueeTrack() {
1363    if (track) {
1364      const fullText = LIVE_MARQUEE_STREAM.map(item => `<span class="marquee-item">${item}</span>`).join('') +
1365                       LIVE_MARQUEE_STREAM.map(item => `<span class="marquee-item">${item}</span>`).join('');
1366      track.innerHTML = fullText;
1367    }
1368    if (ancuaAnnouncementList) {
1369      ancuaAnnouncementList.innerHTML = LIVE_MARQUEE_STREAM.slice(0, 5).map(item => `<li>${item.replace(/<[^>]*>?/gm, ' ')}</li>`).join('');
1370    }
1371  }
1372
1373  renderMarqueeTrack();
1374
1375  setInterval(() => {
1376    const freshUser = getFreshUniqueUsername();
1377    const randomWd = (Math.floor(Math.random() * 32) + 8) * 1000000;
1378    const bank = BANKS_POOL[Math.floor(Math.random() * BANKS_POOL.length)];
1379
1380    const freshItem = `<i class="fa-solid fa-money-bill-transfer text-green"></i> Penarikan Sukses <strong>Rp ${randomWd.toLocaleString('id-ID')}</strong> (${bank}) an. <strong>${freshUser}</strong>`;
1381    LIVE_MARQUEE_STREAM.unshift(freshItem);
1382    if (LIVE_MARQUEE_STREAM.length > 10) {
1383      LIVE_MARQUEE_STREAM.pop();
1384    }
1385    renderMarqueeTrack();
1386  }, 8500);
1387}
1388
1389/* ==========================================================================
1390   16. Sub-Millisecond Search Engine (Header & Modal)
1391   ========================================================================== */
1392function initSearchAction() {
1393  const headerSearch = document.getElementById('headerSearchInput');
1394  const mobileSearch = document.getElementById('mobileSearchInput');
1395
1396  let debounceTimer = null;
1397  function handleSearchInput(e) {
1398    clearTimeout(debounceTimer);
1399    debounceTimer = setTimeout(() => {
1400      searchQuery = (e.target.value || '').trim();
1401      currentDisplayedCount = PAGE_SIZE;
1402      renderRTPCards();
1403    }, 150);
1404  }
1405
1406  if (headerSearch) headerSearch.addEventListener('input', handleSearchInput);
1407  if (mobileSearch) mobileSearch.addEventListener('input', handleSearchInput);
1408}
1409
1410function initModalSearchEngine() {
1411  const modalSearchInput = document.getElementById('search_game_input');
1412  const modalSearchResults = document.getElementById('search_game_results');
1413
1414  if (!modalSearchInput || !modalSearchResults) return;
1415
1416  let debounceTimer = null;
1417  modalSearchInput.addEventListener('input', (e) => {
1418    clearTimeout(debounceTimer);
1419    debounceTimer = setTimeout(() => {
1420      const q = (e.target.value || '').trim().toLowerCase();
1421      if (!q) {
1422        modalSearchResults.innerHTML = `
1423          <div class="search-empty-state" style="text-align: center; padding: 20px; color: #94a3b8;">
1424            Ketik nama game favorit Anda di atas untuk mencari instan di antara ${ALL_GAMES.length} game...
1425          </div>
1426        `;
1427        return;
1428      }
1429
1430      const matches = ALL_GAMES.filter(g => 
1431        (g.name && g.name.toLowerCase().includes(q)) || 
1432        (g.provider && g.provider.toLowerCase().includes(q))
1433      ).slice(0, 24);
1434
1435      if (matches.length === 0) {
1436        modalSearchResults.innerHTML = `
1437          <div class="search-empty-state" style="text-align: center; padding: 20px; color: #f87171;">
1438            <i class="fa-solid fa-circle-exclamation" style="font-size: 24px; margin-bottom: 8px; display: block;"></i>
1439            Game "${q}" tidak ditemukan.
1440          </div>
1441        `;
1442        return;
1443      }
1444
1445      modalSearchResults.innerHTML = `
1446        <div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 10px; max-height: 400px; overflow-y: auto; padding: 6px;">
1447          ${matches.map(g => {
1448            const rtp = calculateDynamicRTP(g, currentSelectedDayIndex);
1449            const meta = getRTPClassification(rtp);
1450            return `
1451              <div style="background: #140507; border: 1px solid #3b0d11; border-radius: 8px; padding: 8px; display: flex; flex-direction: column; gap: 6px;">
1452                <img src="${g.image}
1452" alt="${g.name}" style="width: 100%; height: 110px; object-fit: cover; border-radius: 6px; border: 1px solid #3b0d11;" onerror="this.onerror=null; this.src='https://files.sitestatic.net/games/i/165x220/6d39308186b2c0ede30a49d0c486e911.webp?v=0.2';">
1453                <div>
1454                  <div style="font-weight: 700; color: #fff; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${g.name}</div>
1455                  <div style="font-size: 11px; color: #ffd700;">${g.provider} • <span class="${meta.valClass}">RTP ${rtp}%</span></div>
1456                </div>
1457                <div style="display: flex; gap: 4px;">
1458                  <button class="btn-vip-secondary" style="flex: 1; font-size: 10px; padding: 4px;" onclick="showPolaModal('${g.id}')">Pola</button>
1459                  <a href="${URL_DAFTAR}" target="_blank" class="btn-vip-primary" style="flex: 1; font-size: 10px; padding: 4px; text-align: center;">Main</a>
1460                </div>
1461              </div>
1462            `;
1463          }).join('')}
1464        </div>
1465      `;
1466    }, 150);
1467  });
1468}
1469
1470/* ==========================================================================
1471   17. FAQ, Countdown & Mobile Navigation
1472   ========================================================================== */
1473function initFAQAccordion() {
1474  const faqItems = document.querySelectorAll('.faq-item');
1475  faqItems.forEach(item => {
1476    const questionBtn = item.querySelector('.faq-question');
1477    if (questionBtn) {
1478      questionBtn.addEventListener('click', () => {
1479        const isActive = item.classList.contains('active');
1480        faqItems.forEach(i => i.classList.remove('active'));
1481        if (!isActive) item.classList.add('active');
1482      });
1483    }
1484  });
1485}
1486
1487function initCountdownTimer() {
1488  let seconds = 59;
1489  const clockEl = document.getElementById('rtpCountdown');
1490
1491  setInterval(() => {
1492    seconds--;
1493    if (seconds < 0) {
1494      seconds = 59;
1495      ALL_GAMES.forEach(g => {
1496        liveRtpJitterMap[g.id] = parseFloat(((Math.random() * 0.4) - 0.2).toFixed(1));
1497      });
1498      renderRTPCards();
1499    }
1500    if (clockEl) {
1501      clockEl.textContent = `00:${seconds < 10 ? '0' : ''}${seconds}`;
1502    }
1503  }, 1000);
1504}
1505
1506function initMobileDrawer() {
1507  const toggle = document.getElementById('mobileToggle');
1508  const drawer = document.getElementById('mobileDrawer');
1509  const closeBtn = document.getElementById('mobileDrawerClose');
1510
1511  if (toggle && drawer) {
1512    toggle.addEventListener('click', (e) => {
1513      e.stopPropagation();
1514      drawer.classList.toggle('open');
1515    });
1516    if (closeBtn) closeBtn.addEventListener('click', () => drawer.classList.remove('open'));
1517  }
1518}
1519
1520/* ==========================================================================
1521   18. Bulletproof Zero-Latency Bootstrap
1522   ========================================================================== */
1523function bootstrapPortal() {
1524  try {
1525    let datasetLoaded = false;
1526    const embeddedScript = document.getElementById('rtp-embedded-data');
1527    if (embeddedScript && embeddedScript.textContent && embeddedScript.textContent.trim().length > 100) {
1528      try {
1529        const serverData = JSON.parse(embeddedScript.textContent);
1530        if (Array.isArray(serverData) && serverData.length > 0) {
1531          processRawDataSlot(serverData);
1532          datasetLoaded = true;
1533        }
1534      } catch (e) {}
1535    }
1536
1537    if (!datasetLoaded) {
1538      processRawDataSlot(EMBEDDED_FALLBACK_DATA);
1539    }
1540
1541    // 2. Initialize all interactive subsystems
1542    initDateTabs();
1543    initRTPGrid();
1544    initLiveKPIDashboard();
1545    initVolatilePlayerCounters();
1546    initGrandProgressiveJackpot();
1547    initHourlyTestimonials();
1548    initLiveToastEngine();
1549    initSearchAction();
1550    initModalSearchEngine();
1551    initFAQAccordion();
1552    initModals();
1553    initMobileDrawer();
1554    initCountdownTimer();
1555    initLiveMarquee();
1556  } catch (err) {
1557    console.error('Core bootstrap error:', err);
1558  }
1559
1560  // 3. Load full 48-provider /json/data-slot.json in the background & upgrade catalog
1561  loadDataSlotEngine().then(() => {
1562    try {
1563      initDateTabs();
1564      renderRTPCards();
1565      renderPopularGames();
1566    } catch (e) {
1567      console.warn('Post-load render notice:', e);
1568    }
1569  }).catch(e => {
1570    console.warn('Data loader fallback active:', e);
1571  });
1572}
1573
1574if (document.readyState === 'loading') {
1575  document.addEventListener('DOMContentLoaded', bootstrapPortal);
1576} else {
1577  bootstrapPortal();
1578}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.