1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <title>Apps Gone Free - Daily Deals | IPA Universe</title> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <meta name="description" content="Discover the best paid iOS apps that are temporarily free. Check back daily for new deals on apps and games for your iPhone and iPad."> 8 9 <!-- Modern Font (Inter) --> 10 <link rel="preconnect" href="https://fonts.googleapis.com"> 11 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 12 <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> 13 14 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css"> 15 16 <style> 17 :root { 18 --primary-color: #388E3C; 19 --primary-light: #54d858; 20 --text-primary: #1a1a1a; 21 --text-secondary: #555; 22 --bg-main: #f7f9fa; 23 --bg-card: #ffffff; 24 --border-color: #e0e0e0; 25 --shadow-color: rgba(0,0,0,0.07); 26 } 27 28 body.dark-mode { 29 --primary-color: #54d858; 30 --primary-light: #388E3C; 31 --text-primary: #f0f0f0; 32 --text-secondary: #aaa; 33 --bg-main: #121212; 34 --bg-card: #1e1e1e; 35 --border-color: #333; 36 --shadow-color: rgba(0,0,0,0.2); 37 } 38 39 body { 40 font-family: 'Inter', sans-serif; 41 background: var(--bg-main); 42 color: var(--text-primary); 43 margin: 0; 44 padding: 16px; 45 transition: background-color 0.3s, color 0.3s; 46 } 47 .top-bar { 48 display: flex; 49 justify-content: space-between; 50 align-items: center; 51 margin-bottom: 24px; 52 flex-wrap: wrap; 53 gap: 15px; 54 max-width: 1200px; 55 margin-left: auto; 56 margin-right: auto; 57 } 58 .top-bar-left { display: flex; align-items: center; gap: 10px; text-decoration: none; color: inherit; } 59 .top-bar-right { display: flex; align-items: center; gap: 15px; } 60 61 .search-container { 62 position: relative; 63 display: flex; 64 align-items: center; 65 } 66 #search-input { 67 padding: 8px 16px 8px 36px; 68 border-radius: 20px; 69 border: 1px solid var(--border-color); 70 background-color: var(--bg-card); 71 color: var(--text-primary); 72 width: 200px; 73 transition: all 0.3s; 74 } 75 #search-input:focus { 76 outline: none; 77 border-color: var(--primary-color); 78 box-shadow: 0 0 5px var(--primary-color); 79 } 80 .search-container .fa-search { 81 position: absolute; 82 left: 14px; 83 color: var(--text-secondary); 84 } 85 86 .nav-links { 87 display: flex; 88 align-items: center; 89 gap: 10px; 90 } 91 92 .home-btn { 93 padding: 8px 17px; 94 background-color: var(--primary-light); 95 color: #fff; 96 text-decoration: none; 97 border-radius: 6px; 98 font-size: 1rem; 99 font-weight: 600; 100 transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease; 101 border: 2px solid transparent; 102 } 103 .home-btn:hover { background-color: var(--primary-color); } 104 .home-btn.secondary { 105 background-color: transparent; 106 color: var(--primary-color); 107 border-color: var(--primary-light); 108 } 109 .home-btn.active { 110 background-color: var(--primary-color); 111 color: white; 112 } 113 .home-btn.donate-btn { 114 background-color: #ff9800; 115 border-color: #ff9800; 116 color: white; 117 } 118 .home-btn.donate-btn:hover { 119 background-color: #f57c00; 120 } 121 122 123 .theme-toggle { 124 background: none; 125 border: none; 126 color: var(--text-secondary); 127 font-size: 1.4rem; 128 cursor: pointer; 129 padding: 5px; 130 } 131 132 .hamburger { 133 display: none; 134 flex-direction: column; 135 cursor: pointer; 136 gap: 4px; 137 z-index: 101; /* Ensure hamburger is above the nav menu */ 138 } 139 .hamburger .line { 140 width: 25px; 141 height: 3px; 142 background-color: var(--text-primary); 143 border-radius: 5px; 144 transition: all 0.3s ease-in-out; 145 } 146 147 .container { 148 max-width: 1200px; 149 margin: 0 auto; 150 } 151 .page-title { 152 font-size: 2rem; 153 font-weight: 700; 154 margin-bottom: 16px; 155 text-align: center; 156 } 157 .category-filters { 158 display: flex; 159 justify-content: center; 160 flex-wrap: wrap; 161 gap: 10px; 162 margin-bottom: 24px; 163 } 164 .filter-btn { 165 padding: 8px 16px; 166 font-size: 0.9rem; 167 font-weight: 600; 168 color: var(--primary-color); 169 background-color: var(--bg-card); 170 border: 1px solid var(--border-color); 171 border-radius: 20px; 172 cursor: pointer; 173 transition: all 0.2s ease; 174 } 175 .filter-btn:hover { 176 background-color: var(--primary-light); 177 color: white; 178 border-color: var(--primary-light); 179 } 180 .filter-btn.active { 181 background-color: var(--primary-color); 182 color: white; 183 border-color: var(--primary-color); 184 } 185 .app-grid { 186 display: grid; 187 grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); 188 gap: 20px; 189 margin-bottom: 24px; 190 } 191 .app-card { 192 background: var(--bg-card); 193 border-radius: 12px; 194 padding: 16px; 195 box-shadow: 0 4px 16px var(--shadow-color); 196 display: flex; 197 flex-direction: column; 198 align-items: center; 199 text-align: center; 200 transition: background-color 0.3s, box-shadow 0.3s;
201 } 202 .app-card img { 203 width: 80px; 204 height: 80px; 205 border-radius: 18px; 206 margin-bottom: 12px; 207 background-color: #f0f0f0; 208 } 209 .app-category { font-size: 0.85rem; color: var(--text-secondary); font-weight: 500; margin-bottom: 4px; } 210 .app-name { font-weight: 600; font-size: 1.1rem; margin-bottom: 8px; word-break: break-word; } 211 .price-drop { font-size: 1rem; font-weight: 500; margin-bottom: 8px; } 212 .price-drop .old-price { text-decoration: line-through; color: #999; margin-right: 8px; } 213 .price-drop .new-price { color: var(--primary-color); font-weight: 700; } 214 .app-date { font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 8px; } 215 .app-rating { font-size: 0.9rem; color: var(--text-secondary); margin-bottom: 16px; display: flex; align-items: center; gap: 4px; } 216 .app-rating .star { color: #ffc107; } 217 .view-btn { margin-top: auto; width: 100%; padding: 10px; background: var(--primary-color); color: white; text-decoration: none; border-radius: 8px; font-weight: 600; transition: background-color 0.2s; } 218 .view-btn:hover { background: var(--primary-light); } 219 .loader, .load-more-container { text-align: center; font-size: 1.2rem; padding: 20px; grid-column: 1 / -1; } 220 .load-more-btn { padding: 12px 24px; background-color: var(--primary-color); color: white; border: none; border-radius: 8px; font-weight: 600; cursor: pointer; transition: background-color 0.2s; } 221 .load-more-btn:hover { background-color: var(--primary-light); } 222 .load-more-btn:disabled { background-color: #ccc; cursor: not-allowed; } 223 .hidden { display: none; } 224 225 footer.disclaimer-footer { 226 margin-top: 40px; 227 text-align: center; 228 font-size: 0.9rem; 229 color: var(--text-secondary); 230 user-select: none; 231 opacity: 0; 232 transform: translateY(30px); 233 transition: opacity 0.8s ease, transform 0.8s ease; 234 padding: 0 20px; 235 } 236 footer.disclaimer-footer.visible { 237 opacity: 1; 238 transform: translateY(0); 239 } 240 footer.disclaimer-footer a { 241 color: inherit; 242 text-decoration: none; 243 font-weight: 600; 244 } 245 footer.disclaimer-footer .disclaimer-text { 246 font-size: 0.8rem; 247 max-width: 720px; 248 margin: 10px auto; 249 line-height: 1.4; 250 opacity: 0.9; 251 } 252 253 @media (max-width: 992px) { 254 .hamburger { display: flex; } 255 .nav-links { 256 display: none; 257 flex-direction: column; 258 align-items: stretch; /* Make items fill the width */ 259 width: 100%; 260 background-color: var(--bg-card); 261 position: absolute; 262 top: 85px; /* Adjusted to prevent overlap */ 263 left: 0; 264 padding: 20px; 265 box-shadow: 0 4px 10px var(--shadow-color); 266 z-index: 100; 267 } 268 .nav-links.active { display: flex; } 269 .nav-links .home-btn { 270 width: auto; /* Let flexbox handle width */ 271 text-align: center; 272 margin-bottom: 10px; /* Add space between stacked buttons */ 273 } 274 .nav-links .home-btn:last-child { 275 margin-bottom: 0; 276 } 277 278 .top-bar-right { 279 width: 100%; 280 justify-content: space-between; 281 } 282 } 283 </style> 284</head> 285<body> 286 287 <header class="top-bar" id="topBar"> 288 <a href="index.html" class="top-bar-left"> 289 <svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 64 64" fill="none"> 290 <ellipse cx="32" cy="32" rx="28" ry="10" stroke="#54d858" stroke-width="2" transform="rotate(-20 32 32)" /> 291 <circle cx="32" cy="32" r="20" fill="#388E3C" /> 292 <text x="32" y="38" text-anchor="middle" fill="white" font-family="'Inter', sans-serif" font-weight="700" font-size="18" letter-spacing="0.5">iPA</text> 293 </svg> 294 <h1 style="margin: 0; font-weight: 700; font-size: 1.5rem; user-select:none;">Universe</h1> 295 </a> 296 <div class="top-bar-right"> 297 <div class="search-container"> 298 <i class="fas fa-search"></i> 299 <input type="text" id="search-input" placeholder="Search apps..."> 300 </div> 301 <nav class="nav-links" id="nav-links"> 302 <a href="index.html" class="home-btn secondary">iPA Home</a> 303 <a href="app-free.html" class="home-btn active">Apps Gone Free</a> 304 <a href="games.html" class="home-btn secondary">Games</a> 305 <a href="https://watch.ipauniverse.com/" class="home-btn secondary">Watch</a> 306 <a href="https://wallpapers.ipauniverse.com/" class="home-btn secondary">Wallpapers</a> 307 <a href="donate.html" class="home-btn donate-btn">Donate</a> 308 <a href="https://t.me/lPAUniverse" class="home-btn secondary">Telegram</a> 309 </nav> 310 <button class="theme-toggle" id="theme-toggle"> 311 <i class="fas fa-moon"></i> 312 </button> 313 <div class="hamburger" id="hamburger"> 314 <div class="line"></div> 315 <div class="line"></div> 316 <div class="line"></div> 317 </div> 318 </div> 319 </header> 320 321 <div class="container"> 322 <h1 class="page-title">Apps Gone Free</h1> 323 <div class="category-filters" id="category-filters"></div> 324 325 <div class="app-grid" id="app-grid"></div> 326 <div class="load-more-container" id="load-more-container"> 327 <button class="load-more-btn hidden" id="load-more-btn">Load More</button> 328 </div> 329 </div> 330 331 <footer class="disclaimer-footer"> 332 <p><a href="index.html">ð <strong>IPAUniverse.com</strong> © <span id="year"></span></a> â All rights reserved.</p> 333 <p class="disclaimer-text">â ï¸ IPAUniverse.com is an independent site and is not affiliated with Apple Inc. Content is for educational and research use only. Users are responsible for their actions.</p> 334 <p>â¤ï¸ Designed with love by <a href="/cdn-cgi/l/email-protection#9cf5ecfde9f2f5eaf9eeeff9efdcfbf1fdf5f0b2fff3f1">IPAUniverse Team</a></p> 335 </footer>
vendor: 114 bytes, lines 335-337
335 336 337 <script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script>
337<script type="c61d0eb5f152e1fe45d4e912-text/javascript"> 338 document.addEventListener('DOMContentLoaded', () => { 339 const appGrid = document.getElementById('app-grid'); 340 const loadMoreBtn = document.getElementById('load-more-btn'); 341 const categoryFiltersContainer = document.getElementById('category-filters'); 342 const searchInput = document.getElementById('search-input'); 343 const themeToggle = document.getElementById('theme-toggle'); 344 const hamburger = document.getElementById('hamburger'); 345 const navLinks = document.getElementById('nav-links'); 346 347 let currentPage = 0; 348 let hasNextPage = true; 349 let isLoading = false; 350 const allCategories = new Set(); 351 352 // --- Dark Mode --- 353 const themeIcon = themeToggle.querySelector('i'); 354 const currentTheme = localStorage.getItem('theme'); 355 if (currentTheme === 'dark') { 356 document.body.classList.add('dark-mode'); 357 themeIcon.classList.replace('fa-moon', 'fa-sun'); 358 } 359 themeToggle.addEventListener('click', () => { 360 document.body.classList.toggle('dark-mode'); 361 let theme = 'light'; 362 if (document.body.classList.contains('dark-mode')) { 363 theme = 'dark'; 364 themeIcon.classList.replace('fa-moon', 'fa-sun'); 365 } else { 366 themeIcon.classList.replace('fa-sun', 'fa-moon'); 367 } 368 localStorage.setItem('theme', theme); 369 }); 370 371 // --- Hamburger Menu --- 372 hamburger.addEventListener('click', () => { 373 navLinks.classList.toggle('active'); 374 }); 375 376 // --- Footer --- 377 document.getElementById("year").textContent = new Date().getFullYear(); 378 const footer = document.querySelector(".disclaimer-footer"); 379 const observer = new IntersectionObserver(([entry]) => { 380 if (entry.isIntersecting) { 381 footer.classList.add("visible"); 382 observer.disconnect(); 383 } 384 }, { threshold: 0.1 }); 385 if (footer) { 386 observer.observe(footer); 387 } 388 389 const apiUrl = 'https://appraven.net/appraven/graphql'; 390 const proxyUrl = 'https://corsproxy.io/?' + encodeURIComponent(apiUrl); 391 const headers = { 'Content-Type': 'application/json', 'X-APOLLO-OPERATION-NAME': 'GetAppsOnSale', 'X-APOLLO-OPERATION-TYPE': 'query', 'apollographql-client-name': 'net.appraven.app-apollo-ios', 'apollographql-client-version': '2.2.9-2' }; 392 393 function getPriceFromTier(tier) { 394 const priceMap = { 1: '0.99', 2: '1.99', 3: '2.99', 4: '3.99', 5: '4.99', 6: '5.99', 7: '6.99', 8: '7.99', 9: '8.99', 10: '9.99', 11: '10.99', 12: '11.99', 13: '12.99', 14: '13.99', 15: '14.99', 20: '19.99', 25: '24.99', 30: '29.99' }; 395 return priceMap[tier] || null; 396 } 397 398 /** 399 * Fetches the official App ID from the iTunes Search API. 400 * @param {string} appName - The name of the app to search for. 401 * @returns {Promise<string|null>} - The trackId or null if not found. 402 */ 403 async function getAppIdFromItunes(appName) { 404 try { 405 const endpoint = `https://itunes.apple.com/search?term=${encodeURIComponent(appName)}&entity=software&country=us&limit=5`; 406 const response = await fetch(endpoint); 407 if (!response.ok) { 408 console.error(`iTunes API error for "${appName}": ${response.status}`); 409 return null; 410 } 411 const data = await response.json(); 412 if (data.resultCount > 0) { 413 // Find an exact match, case-insensitive 414 const exactMatch = data.results.find(app => app.trackName.toLowerCase() === appName.toLowerCase()); 415 return exactMatch ? exactMatch.trackId : null; 416 } 417 return null; 418 } catch (error) { 419 console.error(`Failed to fetch App ID for "${appName}":`, error); 420 return null; 421 } 422 } 423 424 function createStructuredData(app, activity, originalPriceString, officialAppId) { 425 const appUrl = `https://apps.apple.com/app/id${officialAppId}`; 426 const appIcon = app.artworkUrl.replace('{w}x{h}{c}.{f}', '160x160bb.jpg'); 427 const category = (app.genres && app.genres.length > 0) ? app.genres[0].title : (app.game ? 'Game' : 'App'); 428 const schema = { "@context": "https://schema.org", "@type": "SoftwareApplication", name: app.title, operatingSystem: "iOS", applicationCategory: category, image: appIcon, url: appUrl, aggregateRating: { "@type": "AggregateRating", "ratingValue": app.rating || "0", "ratingCount": app.ratingCount || "0" }, offers: { "@type": "Offer", "price": "0.00", "priceCurrency": "USD", "validFrom": new Date(activity.timestamp).toISOString() } }; 429 if(originalPriceString) { schema.offers.priceSpecification = { "@type": "PriceSpecification", "price": getPriceFromTier(activity.priceTierFrom), "priceCurrency": "USD", "valueAddedTaxIncluded": "true" } } 430 const script = document.createElement('script'); 431 script.type = 'application/ld+json'; 432 script.textContent = JSON.stringify(schema); 433 document.head.appendChild(script); 434 } 435 436 async function createAppCard(activity) { 437 const app = activity.app; 438 if (!app || activity.__typename !== 'AppActivityPriceChange' || app.priceTier !== 0 || activity.priceTierFrom <= 0) return null; 439 440 const appName = app.title; 441 const officialAppId = await getAppIdFromItunes(appName); 442 443 if (!officialAppId) { 444 console.warn(`Could not find official App ID for "${appName}". Skipping card creation.`); 445 return null; 446 } 447 448 const appIcon = app.artworkUrl.replace('{w}x{h}{c}.{f}', '160x160bb.jpg'); 449 450 const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; 451 const appUrl = isIOS 452 ? `itms-apps://itunes.apple.com/app/id${officialAppId}` 453 : `https://itunes.apple.com/app/id${officialAppId}`; 454 455 const rating = app.rating || 0; 456 const ratingCount = app.ratingCount || 0; 457 const priceDropDate = new Date(activity.timestamp).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
458 const originalPrice = getPriceFromTier(activity.priceTierFrom); 459 const originalPriceString = originalPrice ? `$${originalPrice}` : 'Paid'; 460 const category = (app.genres && app.genres.length > 0) ? app.genres[0].title : (app.game ? 'Game' : 'App'); 461 allCategories.add(category); 462 const appCard = document.createElement('article'); 463 appCard.className = 'app-card'; 464 appCard.dataset.category = category; 465 appCard.dataset.title = appName.toLowerCase(); 466 appCard.innerHTML = `<img src="${appIcon}" alt="${appName} icon" onerror="this.onerror=null;this.src='https://placehold.co/160x160/e0e0e0/777?text=Icon';"><div class="app-category">${category}</div><div class="app-name">${appName}</div><div class="price-drop"><span class="old-price">${originalPriceString}</span><span class="new-price">FREE</span></div><div class="app-date">Dropped: ${priceDropDate}</div><div class="app-rating"><i class="fas fa-star star"></i><span>${rating.toFixed(1)} (${ratingCount} ratings)</span></div><a href="${appUrl}" class="view-btn" target="_blank" rel="noopener noreferrer">View on App Store</a>`; 467 468 createStructuredData(app, activity, originalPrice, officialAppId); 469 return appCard; 470 } 471 472 function renderCategoryFilters() { 473 const currentActive = categoryFiltersContainer.querySelector('.active')?.dataset.filter || 'All'; 474 categoryFiltersContainer.innerHTML = ''; 475 const allButton = document.createElement('button'); 476 allButton.className = 'filter-btn'; 477 allButton.textContent = 'All'; 478 allButton.dataset.filter = 'All'; 479 if (currentActive === 'All') allButton.classList.add('active'); 480 categoryFiltersContainer.appendChild(allButton); 481 const sortedCategories = [...allCategories].sort(); 482 sortedCategories.forEach(category => { 483 const button = document.createElement('button'); 484 button.className = 'filter-btn'; 485 button.textContent = category; 486 button.dataset.filter = category; 487 if (currentActive === category) button.classList.add('active'); 488 categoryFiltersContainer.appendChild(button); 489 }); 490 } 491 492 function applyFilters() { 493 const searchTerm = searchInput.value.toLowerCase(); 494 const selectedCategory = categoryFiltersContainer.querySelector('.active')?.dataset.filter || 'All'; 495 496 document.querySelectorAll('.app-card').forEach(card => { 497 const titleMatch = card.dataset.title.includes(searchTerm); 498 const categoryMatch = (selectedCategory === 'All' || card.dataset.category === selectedCategory); 499 if (titleMatch && categoryMatch) { 500 card.style.display = 'flex'; 501 } else { 502 card.style.display = 'none'; 503 } 504 }); 505 } 506 507 async function fetchApps(page) { 508 if (isLoading || !hasNextPage) return; 509 isLoading = true; 510 const loaderId = `loader-${page}`; 511 const loader = document.createElement('div'); 512 loader.className = 'loader'; 513 loader.id = loaderId; 514 loader.textContent = 'Loading deals...'; 515 if (page === 0) appGrid.innerHTML = ''; 516 appGrid.appendChild(loader); 517 loadMoreBtn.disabled = true; 518 const graphqlPayload = { operationName: "GetAppsOnSale", query: `query GetAppsOnSale($rareOnly: Boolean!, $miniFilter: MiniFilterInput!, $page: Int!) { appsOnSale(rareOnly: $rareOnly, miniFilter: $miniFilter, page: $page) { __typename hasNext content { __typename ...AppActivityRelation } } } fragment AppActivityRelation on AppActivity { __typename id app { __typename ...AppThumbnail } inAppPurchase { __typename ...IAPThumbnail app { __typename ...AppThumbnail } } ...AppActivity } fragment AppThumbnail on App { __typename id title artworkUrl priceTier rating ratingCount preorder game arcade onStore hasInAppPurchases ratingInfo { __typename topPosition } developer { __typename name } lastActivity { __typename ...AppActivity } genres { __typename ...AppGenre } devices upvoteCount downvoteCount commentCount myWatch { __typename ...WatchInteractionInfo } youOwn } fragment AppActivity on AppActivity { __typename timestamp ... on AppActivityAvailability { __typename availabilityChangeType: type } ... on AppActivityPriceChange { __typename priceChangeType: type priceTierFrom } ... on AppActivityUpdate { __typename updateSize versionTo } } fragment AppGenre on AppGenre { __typename ITunesId title parentITunesId } fragment WatchInteractionInfo on WatchInteraction { __typename price inAppPurchases maxPriceTier rareOnly updates availability comments newAppAdded notificationLevel } fragment IAPThumbnail on InAppPurchase { __typename id title priceTier youOwn lastActivity { __typename ...AppActivity } }`, variables: { miniFilter: { price: "FREE", ratingCount: 10 }, page: page, rareOnly: false } }; 519 try { 520 const response = await fetch(proxyUrl, { method: 'POST', headers: headers, body: JSON.stringify(graphqlPayload) }); 521 if (!response.ok) throw new Error(`Network response was not ok, status: ${response.status}`); 522 const result = await response.json(); 523 const currentLoader = document.getElementById(loaderId); 524 if(currentLoader) currentLoader.remove(); 525 const saleInfo = result?.data?.appsOnSale; 526 const activities = saleInfo?.content; 527 hasNextPage = saleInfo?.hasNext || false; 528 if (activities && activities.length > 0) { 529 530 const cardPromises = activities.map(activity => createAppCard(activity)); 531 const appCards = await Promise.all(cardPromises); 532 533 let appsAdded = 0; 534 appCards.forEach(card => { 535 if (card) { 536 appGrid.appendChild(card); 537 appsAdded++; 538 } 539 }); 540 541 if (page === 0 && appsAdded === 0) { appGrid.innerHTML = '<div class="loader">No new price drops found today. Check back later!</div>'; } 542 } else if (page === 0) { 543 appGrid.innerHTML = '<div class="loader">No deals found today. Check back later!</div>'; 544 } 545 renderCategoryFilters(); 546 applyFilters();
547 if (hasNextPage) { loadMoreBtn.classList.remove('hidden'); } else { loadMoreBtn.classList.add('hidden'); } 548 } catch (error) { 549 console.error('Failed to fetch apps gone free:', error); 550 const currentLoader = document.getElementById(loaderId); 551 if(currentLoader) currentLoader.remove(); 552 if (page === 0) { appGrid.innerHTML = '<div class="loader">Could not load deals. Please try again later.</div>'; } 553 } finally { 554 isLoading = false; 555 loadMoreBtn.disabled = false; 556 } 557 } 558 559 categoryFiltersContainer.addEventListener('click', (e) => { 560 if (!e.target.matches('.filter-btn')) return; 561 categoryFiltersContainer.querySelector('.active').classList.remove('active'); 562 e.target.classList.add('active'); 563 applyFilters(); 564 }); 565 566 searchInput.addEventListener('input', applyFilters); 567 loadMoreBtn.addEventListener('click', () => { currentPage++; fetchApps(currentPage); }); 568 fetchApps(currentPage); 569 }); 570 </script>
570 571<!-- Statcounter code --> 572
572<script type="c61d0eb5f152e1fe45d4e912-text/javascript"> 573 var sc_project=13063880; 574 var sc_invisible=1; 575 var sc_security="43f6311f"; 576 </script>
576 577
577<script type="c61d0eb5f152e1fe45d4e912-text/javascript" src="https://www.statcounter.com/counter/counter_xhtml.js" async></script>
577 578 <noscript> 579 <div class="statcounter"> 580 <a title="Web Analytics" href="https://statcounter.com/" class="statcounter"> 581 <img class="statcounter" src="https://c.statcounter.com/13063880/0/43f6311f/1/" alt="Web Analytics" referrerPolicy="no-referrer-when-downgrade" /> 582 </a> 583 </div> 584 </noscript>
vendor: 144 bytes, lines 584-585
584 585<script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="c61d0eb5f152e1fe45d4e912-|49" defer></script>
585<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"fab193e2588349c1a0046ef22f4a2491","r":1,"spa":2}' crossorigin="anonymous"></script>
585 586</body> 587</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.