PageSourceSearch

https://ipauniverse.com/app-free

html ipauniverse.com collected 2026-10-03 20:14:04 UTC 27,923 bytes, 587 lines download raw bytes

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> &copy; <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.