PageSourceSearch

https://www.productscraper.online/

html productscraper.online collected 2026-09-24 09:06:34 UTC 62,570 bytes, 898 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <title>Scrapify – WooCommerce & Shopify Product Scraper</title>
7    <meta name="description" content="Stock your store from any WooCommerce or Shopify supplier and keep prices and stock in sync. Export to CSV, Excel or Shopify. Free plugin, no card needed.">
8    <link rel="canonical" href="https://www.productscraper.online/">
9    <meta name="robots" content="index, follow">
10    <meta property="og:type" content="website">
11    <meta property="og:url" content="https://www.productscraper.online/">
12    <meta property="og:site_name" content="Scrapify">
13    <meta property="og:title" content="Scrapify – WooCommerce & Shopify Product Scraper">
14    <meta property="og:description" content="Stock your store from any WooCommerce or Shopify supplier and keep prices and stock in sync. Export to CSV, Excel or Shopify. Free plugin, no card needed.">
15    <meta property="og:image" content="https://www.productscraper.online/img/og-image.png">
16    <meta name="twitter:card" content="summary_large_image">
17    <meta name="twitter:title" content="Scrapify – WooCommerce & Shopify Product Scraper">
18    <meta name="twitter:description" content="Stock your store from any WooCommerce or Shopify supplier and keep prices and stock in sync. Export to CSV, Excel or Shopify. Free plugin, no card needed.">
19    <meta name="twitter:image" content="https://www.productscraper.online/img/og-image.png">
20    <link rel="icon" href="/favicon.png" sizes="32x32">
21    
21<script type="application/ld+json">
22    {
23      "@context": "https://schema.org",
24      "@type": "SoftwareApplication",
25      "name": "Scrapify",
26      "url": "https://www.productscraper.online",
27      "description": "Extract all products from any WooCommerce store and export to CSV, Excel, Shopify or WooCommerce formats.",
28      "applicationCategory": "BusinessApplication",
29      "operatingSystem": "Web",
30      "offers": {
31        "@type": "Offer",
32        "price": "9.99",
33        "priceCurrency": "USD",
34        "description": "Premium plan, up to 100,000 products per scrape and 5 live feeds"
35      },
36      "aggregateRating": {
37        "@type": "AggregateRating",
38        "ratingValue": "4.8",
39        "reviewCount": "3",
40        "bestRating": "5",
41        "worstRating": "1"
42      },
43      "provider": {
44        "@type": "Organization",
45        "name": "Scrapify",
46        "url": "https://www.productscraper.online",
47        "email": "[email protected]"
48      }
49    }
50    </script>
50
51    
51<script type="application/ld+json">
52    {
53      "@context": "https://schema.org",
54      "@type": "FAQPage",
55      "mainEntity": [
56        {
57          "@type": "Question",
58          "name": "How to install WooCommerce Scraper?",
59          "acceptedAnswer": {
60            "@type": "Answer",
61            "text": "Install via the Chrome Web Store, find the icon in your toolbar, search any WooCommerce website, and click the scraper icon to follow the instructions in the popup."
62          }
63        },
64        {
65          "@type": "Question",
66          "name": "Does WooCommerce Scraper work on protected websites?",
67          "acceptedAnswer": {
68            "@type": "Answer",
69            "text": "Yes, it can export content regardless of security apps. Contact support if you encounter any scraping issues with specific websites."
70          }
71        },
72        {
73          "@type": "Question",
74          "name": "How do I cancel my subscription?",
75          "acceptedAnswer": {
76            "@type": "Answer",
77            "text": "You can cancel at any time from your profile page. Your access is retained until the end of the current billing period."
78          }
79        },
80        {
81          "@type": "Question",
82          "name": "Do you have customer support?",
83          "acceptedAnswer": {
84            "@type": "Answer",
85            "text": "Yes, we offer customer support via email at [email protected] or through the contact area on our website."
86          }
87        },
88        {
89          "@type": "Question",
90          "name": "How do I import into WooCommerce, and what is the Live Feed?",
91          "acceptedAnswer": {
92            "@type": "Answer",
93            "text": "The Live Feed keeps a store you have scraped continuously up to date, refreshing every hour. It is available on every account: a free account includes 1 live feed capped at 15 products, and Premium removes that cap and allows up to 5 feeds. The feed is built for our free WooCommerce import plugin: connect your account and import products, variations, images, categories and prices directly into WooCommerce, with automatic re-sync. It is an import feed for your own store, not a shopping or advertising feed."
94          }
95        },
96        {
97          "@type": "Question",
98          "name": "How do I migrate products from WooCommerce to Shopify?",
99          "acceptedAnswer": {
100            "@type": "Answer",
101            "text": "With Scrapify, migrating from WooCommerce to Shopify takes just a few steps. Enter the WooCommerce store URL, let Scrapify extract all products, then download the file in Shopify CSV format. Go to your Shopify admin, navigate to Products > Import, and upload the file. All product titles, prices, descriptions, images, and variants will be ready to go. No coding or manual data entry required."
102          }
103        },
104        {
105          "@type": "Question",
106          "name": "How do I migrate products from Shopify to WooCommerce?",
107          "acceptedAnswer": {
108            "@type": "Answer",
109            "text": "To migrate from Shopify to WooCommerce, paste the Shopify store URL into Scrapify and run the scraper. Once complete, download the export in WooCommerce CSV format. In your WordPress dashboard, go to WooCommerce > Products > Import and upload the file. Scrapify automatically formats the data to match WooCommerce's import requirements, so products appear correctly without any manual reformatting."
110          }
111        },
112        {
113          "@type": "Question",
114          "name": "Can I export all WooCommerce products to CSV or Excel?",
115          "acceptedAnswer": {
116            "@type": "Answer",
117            "text": "Yes. Scrapify can export the full product catalogue from any WooCommerce store to CSV or Excel with one click. The free plan supports up to 10 products per export, and every export format is included on every plan: WooCommerce CSV, Shopify CSV and Excel. A free account raises that to 15 products and adds the hourly auto-refreshing live feed plus the WordPress import plugin that syncs from it, both capped at the same 15 products. A Premium account raises that ceiling to 100,000 products so you can export and sync a full c
117atalogue, and allows up to 5 live feeds."
118          }
119        },
120        {
121          "@type": "Question",
122          "name": "Is there a free WooCommerce product scraper?",
123          "acceptedAnswer": {
124            "@type": "Answer",
125            "text": "Yes, Scrapify has a free plan that requires no account and no credit card. You can extract up to 10 products from any WooCommerce or Shopify store, from as many stores as you like, and download them instantly as WooCommerce CSV, Shopify CSV or Excel. Creating a free account raises this to 15 products and unlocks the hourly auto-refreshing live feed and the WordPress import plugin at that same 15 product cap. For bigger jobs of up to 100,000 products per scrape and 5 live feeds, the Premium plan is $9.99 per month."
126          }
127        },
128        {
129          "@type": "Question",
130          "name": "What product data does Scrapify export?",
131          "acceptedAnswer": {
132            "@type": "Answer",
133            "text": "Scrapify exports product titles, prices, descriptions, images, stock levels, categories, and variants. On the Premium plan you also get access to a full product details view, making it easy to check the original listing during or after migration."
134          }
135        },
136        {
137          "@type": "Question",
138          "name": "Can I copy products from one WooCommerce store to another?",
139          "acceptedAnswer": {
140            "@type": "Answer",
141            "text": "Yes. Scrapify is the fastest way to copy products between WooCommerce stores. Scrape the source store, download the export in WooCommerce CSV format, then import it directly into the destination store via WooCommerce's built-in product importer. The entire process takes minutes regardless of catalog size."
142          }
143        },
144        {
145          "@type": "Question",
146          "name": "How long does it take to scrape a large WooCommerce store?",
147          "acceptedAnswer": {
148            "@type": "Answer",
149            "text": "Scrapify uses a direct API approach rather than page-by-page crawling, so even large stores with thousands of products are typically extracted in seconds to a few minutes. You can watch products being pulled in real time and download the export as soon as it completes."
150          }
151        },
152        {
153          "@type": "Question",
154          "name": "What is the difference between the Free and Premium plan?",
155          "acceptedAnswer": {
156            "@type": "Answer",
157            "text": "The Free plan (no account needed) lets you extract up to 10 products from as many stores as you like, and download them as WooCommerce CSV, Shopify CSV or Excel. A free registered account raises this to 15 products, adds 3 private email aliases, and includes 1 hourly auto-refreshing live product feed plus the WordPress import plugin that syncs from it, both capped at those same 15 products. The Premium plan at $9.99 per month raises the product cap to 100,000 so you can export and sync an entire catalogue, raises the feed limit to 5, and includes 20 private email aliases."
158          }
159        },
160        {
161          "@type": "Question",
162          "name": "Why am I charged in ZAR at checkout instead of USD?",
163          "acceptedAnswer": {
164            "@type": "Answer",
165            "text": "Scrapify's payment processor, PayFast, is based in South Africa and processes all transactions in South African Rand (ZAR). The USD price shown on our pricing page is a display price for international reference, and your bank or card provider will convert the ZAR charge at the current exchange rate at the time of payment. The final amount in your local currency may vary slightly depending on your bank's conversion rate. If you have any questions about a charge, contact us at [email protected]."
166          }
167        }
168      ]
169    }
170    </script>
170
171    <link rel="preconnect" href="https://fonts.googleapis.com">
172    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
173    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
174    
174<script src="/socket.io/socket.io.js"></script>
174
175    <link rel="stylesheet" href="/css/design-system.css?v=30">
176    
176<script src="/js/components.js?v=14"></script>
176
177    <!-- Google tag (gtag.js) -->
vendor: 68 bytes, lines 177-178
177
178    <script async src="https://www.googletagmanager.com/gtag/js?id=
178G-C2D9RBPFSJ
vendor: 16 bytes, lines 178-179
178"></script>
179    
179<script>
180      
vendor: 145 bytes, lines 180-183
180window.dataLayer = window.dataLayer || [];
181      function gtag(){dataLayer.push(arguments);}
182      gtag('js', new Date());
183      gtag('config', '
183G-C2D9RBPFSJ
vendor: 8 bytes, lines 183-184
183');
184    
184</script>
184
185</head>
186<body>
187    <div class="geo-pattern"></div>
188    
189    <!-- Navigation (rendered by components.js) -->
190    <nav class="geo-nav"></nav>
191
192    <!-- Hero Section -->
193    <section class="geo-hero">
194        <div class="hero-blob hero-blob-1"></div>
195        <div class="hero-blob hero-blob-2"></div>
196        <div class="hero-blob hero-blob-3"></div>
197        <div class="geo-hero-inner">
198            <div class="geo-hero-content">
199                <div class="geo-animate">
200                    <div class="hero-badges" style="display:flex;gap:var(--geo-space-3);flex-wrap:wrap;margin-bottom:var(--geo-space-6);">
201                        <div class="geo-badge">${Icons.rss} Supplier sync for WooCommerce &bull; free plugin included</div>
202                    </div>
203                </div>
204                <div class="geo-animate geo-animate-delay-1">
205                    <h1 class="geo-h1">
206                        Stock Your Store From Any WooCommerce Or Shopify Supplier, And <span class="geo-gradient-text">Keep It In Sync</span>
207                    </h1>
208                </div>
209                <div class="geo-animate geo-animate-delay-2">
210                    <p class="geo-text-lg geo-text-muted hero-subtitle" style="max-width: 520px; margin: var(--geo-space-6) 0; line-height: 1.7;">
211                        Pull a supplier's full catalogue in seconds, then let it look after itself: prices and stock update every hour with your markup applied. Free to try, no card needed.
212                    </p>
213                </div>
214                <div class="geo-animate geo-animate-delay-3 hero-buttons">
215                    <button class="geo-btn geo-btn-primary geo-btn-lg" onclick="location.href='/scrape'">
216                        ${Icons.zap} Try It Free
217                    </button>
218                    <button class="geo-btn geo-btn-outline geo-btn-lg" onclick="showModal('register')">
219                        Get Started Free
220                    </button>
221                </div>
222                <div class="geo-animate geo-animate-delay-4 hero-secondary-links">
223                    <a href="https://chromewebstore.google.com/detail/scrapify-detector/jajikfnjodgmmcjkphkefffkolkoclgm" target="_blank" rel="noopener">${Icons.chrome} Chrome Extension</a>
224                    <a href="/scrapify-android.apk" download>${Icons.android} Download Android App</a>
225                </div>
226            </div>
227            <div class="geo-hero-mockup geo-animate geo-animate-delay-3">
228                <div class="mockup-screen">
229                    <div class="mockup-header">
230                        <div class="mockup-dot" style="background:#ef4444"></div>
231                        <div class="mockup-dot" style="background:#f59e0b"></div>
232                        <div class="mockup-dot" style="background:#10b981"></div>
233                        <span style="margin-left: auto; font-size: 0.75rem; color: var(--geo-text-dim); font-family: var(--geo-font-mono);">store.example.com</span>
234                    </div>
235                    <div class="mockup-body">
236                        <div class="mockup-url-bar">
237                            ${Icons.globe} https://store.example.com
238                        </div>
239                        <div class="mockup-progress-steps">
240                            <div class="mockup-step done">
241                                <span class="mockup-step-dot"></span>
242                                Search
243                            </div>
244                            <div class="mockup-step done">
245                                <span class="mockup-step-dot"></span>
246                                Categories: 3
247                            </div>
248                            <div class="mockup-step active">
249                                <span class="mockup-step-dot"></span>
250                                Products: 2,481
251                            </div>
252                        </div>
253                        <div style="display: flex; justify-content: space-between; margin-bottom: var(--geo-space-4); align-items: center;">
254                            <div style="display: flex; gap: var(--geo-space-8);">
255                                <div><div class="geo-text-xs geo-text-dim" style="text-transform:uppercase;letter-spacing:0.05em;">Total</div><div class="geo-h3 mockup-counter" data-count="2481" style="color:var(--geo-primary-light)">0</div></div>
256                                <div><div class="geo-text-xs geo-text-dim" style="text-transform:uppercase;letter-spacing:0.05em;">Exported</div><div class="geo-h3 mockup-counter" data-count="1847" style="color:var(--geo-success)">0</div></div>
257                            </div>
258                            <span class="status-badge status-running">Running</span>
259                        </div>
260                        <table class="geo-table">
261                            <thead>
262                                <tr>
263                                    <th>Product</th>
264                                    <th>Price</th>
265                                    <th>Status</th>
266                                </tr>
267                            </thead>
268                            <tbody>
269                                <tr>
270                                    <td style="font-weight:500;">Premium T-Shirt</td>
271                                    <td class="geo-text-muted">$29.99</td>
272                                    <td><span class="geo-badge geo-badge-success">Exported</span></td>
273                                </tr>
274                                <tr>
275                                    <td style="font-weight:500;">Leather Wallet</td>
276                                    <td class="geo-text-muted">$49.99</td>
277                                    <td><span class="geo-badge geo-badge-success">Exported</span></td>
278                                </tr>
279                                <!-- Two extra rows give the mockup enough height to hold its own
280                                     against the copy column, which was half again as tall. -->
281                                <tr>
282                                    <td style="font-weight:500;">Canvas Backpack</td>
283                                    <td class="geo-text-muted">$79.99</td>
284                                    <td><span class="geo-badge geo-badge-success">Exported</span></td>
285                                </tr>
286                                <tr>
287                                    <td style="font-weight:500;">Merino Scarf</td>
288                                    <td class="geo-text-muted">$34.99</td>
289                                    <td><span class="status-badge status-running">Syncing</span></td>
290                                </tr>
291                            </tbody>
292                        </table>
293                    </div>
294                </div>
295            </div>
296        </div>
297    </section>
298
299    <!-- Features / Capabilities Section -->
300    <section class="geo-section" id="features" style="position: relative;">
301        <div class="blur-circle blur-circle-purple" style="width: 300px; height: 300px; top: -100px; right: -40px;"></div>
302        <div class="blur-circle blur-circle-cyan" style="width: 200px; height: 200px; bottom: -50px; left: -40px;"></div>
303        <div class="geo-container">
304            <div class="geo-section-header">
305                <div class="geo-animate geo-badge">Powerful Capabilities</div>
306                <h2 class="geo-animate geo-animate-delay-1 geo-h2 geo-mt-4">Everything you need to extract products from any store</h2>
307                <p class="geo-animate geo-animate-delay-2">Scrape WooCommerce and Shopify stores with one click. Export to CSV, Excel, Shopify, and WooCommerce formats.</p>
308            </div>
309            
310            <div class="geo-grid geo-grid-3">
311                <div class="geo-animate geo-card geo-card-glow">
312                    <div class="feature-icon">${Icons.globe}</div>
313                    <h3 class="geo-h4 geo-mb-2">Universal Store Support</h3>
314                    <p class="geo-text-muted geo-text-sm">Works with any WooCommerce or Shopify store. Auto-detects the platform and pulls products and categories.</p>
315                </div>
316                <div class="geo-animate geo-animate-delay-1 geo-card geo-card-glow">
317                    <div class="feature-icon">${Icons.sparkle}</div>
318                    <h3 class="geo-h4 geo-mb-2">One-Click Export</h3>
319                    <p class="geo-text-muted geo-text-sm">Pull thousands of products and download in your preferred format immediately.</p>
320                </div>
321                <div class="geo-animate geo-animate-delay-2 geo-card geo-card-glow">
322                    <div class="feature-icon">${Icons.trendingUp}</div>
323                    <h3 class="geo-h4 geo-mb-2">Format Flexibility</h3>
324                    <p class="geo-text-muted geo-text-sm">Export to Shopify CSV, WooCommerce CSV, Excel, or standard CSV. Ready to import anywhere.</p>
325                </div>
326                <div class="geo-animate geo-animate-delay-4 geo-card geo-card-glow">
327                    <div class="feature-icon">${Icons.android}</div>
328                    <h3 class="geo-h4 geo-mb-2">Scrapify Android App</h3>
329                    <p class="geo-text-muted geo-text-sm">Scrape any WooCommerce or Shopify supplier from your phone and export to CSV or Excel. Sign in with the same account and 
329your plan limits apply.</p>
330                    <a href="/scrapify-android.apk" download style="display:inline-flex;align-items:center;gap:var(--geo-space-2);margin-top:var(--geo-space-3);padding:var(--geo-space-2) 0;font-weight:600;color:var(--geo-primary-light);font-size:0.875rem;">${Icons.android} Download for Android</a>
331                </div>
332                <div class="geo-animate geo-animate-delay-5 geo-card geo-card-glow" style="border-color: var(--geo-primary); background: linear-gradient(135deg, rgba(99,102,241,0.08) 0%, rgba(139,92,246,0.05) 100%);">
333                    <div class="feature-icon">${Icons.plugin}</div>
334                    <h3 class="geo-h4 geo-mb-2">WooCommerce Import Plugin <span class="geo-badge" style="font-size:0.72rem;vertical-align:middle;margin-left:4px;">Free with account</span></h3>
335                    <p class="geo-text-muted geo-text-sm">Install our free WordPress plugin, connect once, and import any scraped catalogue straight into WooCommerce. Products, variations, images, categories and tags, with currency conversion and automatic re-sync. No CSV, no manual steps. Free accounts can connect it too, capped at 15 products.</p>
336                </div>
337                <div class="geo-animate geo-animate-delay-5 geo-card geo-card-glow">
338                    <div class="feature-icon">${Icons.rss}</div>
339                    <h3 class="geo-h4 geo-mb-2">Live Auto-Refreshing Feed <span class="geo-badge" style="font-size:0.72rem;vertical-align:middle;margin-left:4px;">Free with account</span></h3>
340                    <p class="geo-text-muted geo-text-sm">Your scraped catalogue refreshes every hour and stays in sync. Our free WordPress plugin pulls it straight into WooCommerce, matching on SKU so nothing duplicates. Free accounts get 1 feed capped at 15 products, Premium allows 5 feeds of up to 100,000.</p>
341                </div>
342            </div>
343        </div>
344    </section>
345
346    <!-- Pricing Section -->
347    <section class="geo-section" id="pricing" style="position: relative;">
348        <div class="blur-circle blur-circle-purple" style="width: 250px; height: 250px; top: 50px; left: -40px;"></div>
349        <div class="geo-container">
350            <div class="geo-section-header">
351                <div class="geo-animate geo-badge">Simple Pricing</div>
352                <h2 class="geo-animate geo-animate-delay-1 geo-h2 geo-mt-4">Choose Your Plan</h2>
353                <p class="geo-animate geo-animate-delay-2">Start free, upgrade when you need more power.</p>
354            </div>
355            
356            <div class="pricing-wrapper geo-animate">
357
358                <!-- Free. Merged from the old anonymous and registered tiers. Two $0
359                     columns spent half the table on free and forced a cramped 4-up grid;
360                     the anonymous limit is now a note under the list instead. -->
361                <div class="price-card">
362                    <button class="plan-info-btn" type="button" aria-expanded="false" aria-controls="planInfoFree" aria-label="See everything included in Free"></button>
363                    <div class="plan-info-pop" id="planInfoFree" role="dialog" aria-label="Everything included in Free" hidden>
364                        <div class="plan-info-head">
365                            <span>Free includes</span>
366                            <button class="plan-info-close" type="button" aria-label="Close">${Icons.x}</button>
367                        </div>
368                        <ul>
369                            <li>${Icons.check} 15 products per export &amp; feed</li>
370                            <li>${Icons.check} 10 products without an account</li>
371                            <li>${Icons.check} 1 hourly auto-refresh feed</li>
372                            <li>${Icons.check} 3 private email aliases</li>
373                            <li>${Icons.check} Unlimited stores</li>
374                            <li>${Icons.check} WooCommerce &amp; Shopify sources</li>
375                            <li>${Icons.check} Woo CSV, Shopify CSV &amp; Excel export</li>
376                            <li>${Icons.check} All categories, tags &amp; images</li>
377                            <li>${Icons.check}
377 WordPress import plugin</li>
378                        </ul>
379                    </div>
380                    <div class="price-card-icon">${Icons.speed}</div>
381                    <div class="price-card-name">Free</div>
382                    <div class="price-card-desc">Test the real thing, end to end</div>
383                    <div class="price">$0<span>/mo</span></div>
384                    <div class="price-billing">Free account, always &bull; No credit card</div>
385                    <div class="price-divider"></div>
386                    <ul>
387                        <li>${Icons.check} 15 products per export &amp; feed</li>
388                        <li>${Icons.check} 1 hourly auto-refresh feed</li>
389                        <li>${Icons.check} 3 private email aliases</li>
390                        <li>${Icons.check} WordPress import plugin</li>
391                    </ul>
392                    <button class="geo-btn geo-btn-outline geo-btn-lg geo-w-full" onclick="showModal('register')">Create Free Account</button>
393                </div>
394
395                <!-- Premium -->
396                <div class="price-card featured">
397                    <div class="popular-tag">Most Popular</div>
398                    <button class="plan-info-btn" type="button" aria-expanded="false" aria-controls="planInfoPremium" aria-label="See everything included in Premium"></button>
399                    <div class="plan-info-pop" id="planInfoPremium" role="dialog" aria-label="Everything included in Premium" hidden>
400                        <div class="plan-info-head">
401                            <span>Premium includes</span>
402                            <button class="plan-info-close" type="button" aria-label="Close">${Icons.x}</button>
403                        </div>
404                        <ul>
405                            <li>${Icons.check} Up to 100,000 products</li>
406                            <li>${Icons.check} 5 hourly auto-refresh feeds</li>
407                            <li>${Icons.check} 20 private email aliases</li>
408                            <li>${Icons.check} Supplier sync with your markup applied</li>
409                            <li>${Icons.check} Unlimited stores</li>
410                            <li>${Icons.check} WooCommerce &amp; Shopify sources</li>
411                            <li>${Icons.check} Woo CSV, Shopify CSV &amp; Excel export</li>
412                            <li>${Icons.check} All categories, tags &amp; images</li>
413                            <li>${Icons.check} WordPress import plugin</li>
414                            <li>${Icons.check} Cancel anytime</li>
415                        </ul>
416                    </div>
417                    <div class="price-card-icon">${Icons.zap}</div>
418                    <div class="price-card-name">Premium</div>
419                    <div class="price-card-desc">Full catalogue power for professionals</div>
420                    <div class="price">$9.99<span>/mo</span></div>
421                    <div class="price-billing">Pay in your currency via PayFast &bull; Cancel anytime</div>
422                    <div class="price-divider"></div>
423                    <ul>
424                        <li>${Icons.check} Up to 100,000 products</li>
425                        <li>${Icons.check} 5 hourly auto-refresh feeds</li>
426                        <li>${Icons.check} 20 private email aliases</li>
427                        <li>${Icons.check} WordPress import plugin</li>
428                    </ul>
429                    <button class="geo-btn geo-btn-primary geo-btn-lg geo-w-full" onclick="startCheckout()">Upgrade Now</button>
430                </div>
431
432                <!-- Agency. Sold on running many stores at once, not on volume:
433                     Premium's product ceiling is already effectively unlimited. -->
434                <div class="price-card">
435                    <button class="plan-info-btn" type="button" aria-expanded="false" aria-controls="planInfoAgency" aria-label="See everything included in Agency"></button>
436                    <div class="plan-info-pop" id="planInfoAgency" role="dialog" aria-label="Everything included in Agency" hidden>
437                        <div class="plan-info-head">
438                            <span>Agency includes</span>
439                            <button class="plan-info-close" type="button" aria-label="Close">${Icons.x}</button>
440                        </div>
441                        <ul>
442                            <li>${Icons.check} Everything in Premium</li>
443                            <li>${Icons.check} <span id="agencyFeedsPop">25</span> hourly auto-refresh feeds</li>
444                            <li>${Icons.check} <span id="agencyAliasPop">60</span> private email aliases</li>
445                            <li>${Icons.check} Run many client stores at once</li>
446                            <li>${Icons.check} Priority support</li>
447                            <li>${Icons.check} Up to 100,000 products</li>
448                            <li>${Icons.check} Unlimited stores</li>
449                            <li>${Icons.check} WooCommerce &amp; Shopify sources</li>
450                            <li>${Icons.check} Woo CSV, Shopify CSV &amp; Excel export</li>
451                            <li>${Icons.check} All categories, tags &amp; images</li>
452                            <li>${Icons.check} WordPress import plugin</li>
453                        </ul>
454                    </div>
455                    <div class="price-card-icon">${Icons.building}</div>
456                    <div class="price-card-name">Agency</div>
457                    <div class="price-card-desc">For agencies and multi-store sellers</div>
458                    <div class="price" id="agencyPrice">$39<span>/mo</span></div>
459                    <div class="price-billing">Pay in your currency via PayFast &bull; Cancel anytime</div>
460                    <div class="price-divider"></div>
461                    <ul>
462                        <li>${Icons.check} Everything in Premium</li>
463                        <li>${Icons.check} <strong><span id="agencyFeedsLabel">25</span> hourly auto-refresh feeds</strong></li>
464                        <li>${Icons.check} <strong><span id="agencyAliasLabel">60</span> private email aliases</strong></li>
465                        <li>${Icons.check} Priority support</li>
466                    </ul>
467                    <button class="geo-btn geo-btn-outline geo-btn-lg geo-w-full" onclick="startCheckout('agency')">Go Agency</button>
468                </div>
469
470            </div>
471
472
473            <!-- One-off pass. Aimed at the large group who need a single catalogue out
474                 and will not take a subscription they have to remember to cancel. -->
475            <div class="geo-animate" style="max-width:1100px;margin:var(--geo-space-8) auto 0;">
476                <div style="display:flex;flex-wrap:wrap;align-items:center;gap:var(--geo-space-5);background:var(--geo-bg-card);border:1px solid var(--geo-border);border-radius:var(--geo-radius-xl);padding:var(--geo-space-6) var(--geo-space-8);">
477                    <div style="flex:1;min-width:260px;">
478                        <div style="font-size:.72rem;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--geo-text-dim);margin-bottom:6px;">Just need one catalogue?</div>
479                        <h3 style="font-size:1.15rem;font-weight:700;color:var(--geo-text);margin:0 0 6px;">One-off Export Pass</h3>
480                        <p class="geo-text-sm geo-text-muted" style="margin:0;line-height:1.65;">Full Premium limits for <strong id="passDaysLabel">3</strong> days. Export what you came for, then walk away. No subscription, nothing to cancel.</p>
481                    </div>
482                    <div style="text-align:center;">
483                        <div style="font-size:2rem;font-weight:800;color:var(--geo-text);line-height:1;"><span id="passPriceLabel">$19</span></div>
484                        <div class="geo-text-xs geo-text-dim" style="margin:6px 0 var(--geo-space-3);">one-time</div>
485                        <button class="geo-btn geo-btn-outline" onclick="startCheckout('pass')">Buy a pass</button>
486                    </div>
487                </div>
488            </div>
489        </div>
490    </section>
491
492    <!-- Why Use Section -->
493    <section class="geo-section" id="why" style="position: relative;">
494        <div class="blur-circle blur-circle-cyan" style="width: 300px; height: 300px; bottom: -100px; right: -40px;"></div>
495        <div class="geo-container">
496            <div class="geo-section-header">
497                <div class="geo-animate geo-badge">Why Scrapify</div>
498                <h2 class="geo-animate geo-animate-delay-1 geo-h2 geo-mt-4">Best scraping tool for E-commerce entrepreneurs</h2>
499                <p class="geo-animate geo-animate-delay-2">Build large product catalogs in seconds, not hours. The fastest way to populate your online store.</p>
500            </div>
501            
502            <div class="geo-animate geo-grid geo-grid-3" style="grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));">
503                <div class="geo-card geo-text-center" style="padding: var(--geo-space-8);">
504                    <div style="font-size: 2.5rem; margin-bottom: var(--geo-space-4);">${Icons.speed}</div>
505                    <div class="geo-h3 geo-gradient-text">10x Faster</div>
506                    <p class="geo-text-muted geo-text-sm geo-mt-4">Extract thousands of products in seconds, not hours. API-first approach means no crawling.</p>
507                </div>
508                <div class="geo-card geo-text-center" style="padding: var(--geo-space-8);">
509                    <div style="font-size: 2.5rem; margin-bottom: var(--geo-space-4);">${Icons.table}</div>
510                    <div class="geo-h3 geo-gradient-text">
510Multi-Format</div>
511                    <p class="geo-text-muted geo-text-sm geo-mt-4">Export to CSV, Excel, Shopify, and WooCommerce formats. Ready to import instantly.</p>
512                </div>
513                <div class="geo-card geo-text-center" style="padding: var(--geo-space-8);">
514                    <div style="font-size: 2.5rem; margin-bottom: var(--geo-space-4);">${Icons.shield}</div>
515                    <div class="geo-h3 geo-gradient-text">Secure & Private</div>
516                    <p class="geo-text-muted geo-text-sm geo-mt-4">Your data is encrypted and never shared. JWT auth with bcrypt password hashing.</p>
517                </div>
518            </div>
519        </div>
520    </section>
521
522    <!-- Android App Section -->
523    <section class="geo-section video-section" id="android-app">
524        <div class="geo-container">
525            <div class="geo-section-header">
526                <div class="geo-animate geo-badge">Android App</div>
527                <h2 class="geo-animate geo-animate-delay-1 geo-h2 geo-mt-4">Take Scrapify With You</h2>
528                <p class="geo-animate geo-animate-delay-2">Scrape and export supplier catalogues from your phone or tablet. Sign in with the same account and your plan limits apply.</p>
529            </div>
530
531            <div class="geo-animate" style="max-width: 560px; margin: var(--geo-space-8) auto 0; padding: var(--geo-space-8); border-radius: var(--geo-radius-xl); border: 1px solid var(--geo-border); background: linear-gradient(135deg, rgba(125,86,171,0.12), rgba(6,182,212,0.04));">
532                <div style="display: flex; align-items: center; justify-content: center; gap: var(--geo-space-3); flex-wrap: wrap;">
533                    <span style="width: 56px; height: 56px; border-radius: 16px; background: linear-gradient(135deg, var(--geo-primary), var(--geo-primary-dark)); display: inline-flex; align-items: center; justify-content: center; color: white; box-shadow: var(--geo-shadow-lg);"><svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 24 24" fill="#fff"><path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/></svg></span>
534                    <div style="text-align: left;">
535                        <div style="font-weight: 700; font-size: 1.1rem;">Scrapify for Android</div>
536                        <div class="geo-text-sm geo-text-dim">Free to download · Google sign-in · your plan, synced</div>
537                    </div>
538                </div>
539                <a href="/scrapify-android.apk" class="geo-btn geo-btn-primary geo-btn-lg" download style="margin-top: var(--geo-space-6); width: 100%;">
540                    ${Icons.android}
541                    Download Scrapify for Android
542                </a>
543                <p class="geo-text-sm geo-text-dim" style="margin-top: var(--geo-space-4);">Direct APK download. Install it on any Android phone or tablet.</p>
544            </div>
545        </div>
546    </section>
547
548    <!-- Testimonials -->
549    <section class="geo-section" id="trusted" style="position: relative;">
550        <div class="blur-circle blur-circle-purple" style="width: 200px; height: 200px; top: -50px; left: 50%; transform: translateX(-50%);"></div>
551        <div class="geo-container">
552            <div class="geo-section-header">
553                <div class="geo-animate geo-badge">Customer Stories</div>
554                <h2 class="geo-animate geo-animate-delay-1 geo-h2 geo-mt-4">Trusted by Thousands</h2>
555            </div>
556            
557            <div class="geo-grid geo-grid-3" id="testimonialsGrid">
558                <!-- Populated dynamically; fallback cards shown while loading -->
559                <div class="geo-animate testimonial-card">
560                    <div style="display: flex; align-items: center; gap: var(--geo-space-3);">
561                        <div class="avatar">GL</div>
562                        <div>
563                            <div style="font-weight: 600; font-size: 0.9375rem;">Grégoire L.</div>
564                            <div class="geo-text-xs geo-text-dim">Paris, France</div>
565                        </div>
566                    </div>
567                    <div class="stars">${Icons.star}${Icons.star}${Icons.star}${Icons.star}${Icons.star}<span style="color: var(--geo-text-dim); font-size: 0.75rem; margin-left: 4px;">4.5</span></div>
568                    <blockquote>"Wow... This scraping tool is so fast. I love it!"</blockquote>
569                </div>
570                <div class="geo-animate geo-animate-delay-1 testimonial-card">
571                    <div style="display: flex; align-items: center; gap: var(--geo-space-3);">
572                        <div class="avatar">
572KW</div>
573                        <div>
574                            <div style="font-weight: 600; font-size: 0.9375rem;">Kim W.</div>
575                            <div class="geo-text-xs geo-text-dim">New York, USA</div>
576                        </div>
577                    </div>
578                    <div class="stars">${Icons.star}${Icons.star}${Icons.star}${Icons.star}${Icons.star}<span style="color: var(--geo-text-dim); font-size: 0.75rem; margin-left: 4px;">5.0</span></div>
579                    <blockquote>"The team is very responsive and helpful. Very fast scraping tool."</blockquote>
580                </div>
581                <div class="geo-animate geo-animate-delay-2 testimonial-card">
582                    <div style="display: flex; align-items: center; gap: var(--geo-space-3);">
583                        <div class="avatar">GC</div>
584                        <div>
585                            <div style="font-weight: 600; font-size: 0.9375rem;">Georgios C.</div>
586                            <div class="geo-text-xs geo-text-dim">Athens, Greece</div>
587                        </div>
588                    </div>
589                    <div class="stars">${Icons.star}${Icons.star}${Icons.star}${Icons.star}${Icons.star}<span style="color: var(--geo-text-dim); font-size: 0.75rem; margin-left: 4px;">5.0</span></div>
590                    <blockquote>"Great tool! It works like a charm."</blockquote>
591                </div>
592            </div>
593        </div>
594    </section>
595
596    <!-- Migration Service Callout -->
597    <section class="geo-section" style="position: relative;">
598        <div class="blur-circle blur-circle-purple" style="width: 300px; height: 300px; top: -100px; left: -40px;"></div>
599        <div class="blur-circle blur-circle-cyan" style="width: 250px; height: 250px; bottom: -50px; right: -40px;"></div>
600        <div class="geo-container">
601            <div class="geo-section-header">
602                <div class="geo-animate geo-badge">${Icons.sparkle} Done-for-You Service</div>
603                <h2 class="geo-animate geo-animate-delay-1 geo-h2 geo-mt-4">Don't want to do it yourself?</h2>
604                <p class="geo-animate geo-animate-delay-2" style="max-width: 560px; margin-left: auto; margin-right: auto; line-height: 1.7;">
605                    Skip the spreadsheet. Our team handles your full e-commerce migration: products, images, categories, and more. You don't have to lift a finger.
606                </p>
607            </div>
608            <div class="geo-animate" style="display: flex; justify-content: center; gap: var(--geo-space-8); flex-wrap: wrap; margin-top: var(--geo-space-6); margin-bottom: var(--geo-space-8);">
609                <div class="newsletter-feature">${Icons.check} No spreadsheets</div>
610                <div class="newsletter-feature">${Icons.check} All major platforms</div>
611                <div class="newsletter-feature">${Icons.check} Verified before handover</div>
612            </div>
613            <div class="geo-animate" style="text-align: center;">
614                <a href="/migration" class="geo-btn geo-btn-primary geo-btn-lg">${Icons.zap} Learn About Migration</a>
615            </div>
616        </div>
617    </section>
618
619    <!-- FAQ -->
620    <section class="geo-section" id="faq">
621        <div class="geo-container">
622            <div class="geo-section-header">
623                <div class="geo-animate geo-badge">FAQ</div>
624                <h2 class="geo-animate geo-animate-delay-1 geo-h2 geo-mt-4">Frequently Asked Questions</h2>
625            </div>
626            
627            <div class="faq-grid">
628                <div class="faq-item">
629                    <button class="faq-question">How to install WooCommerce Scraper?</button>
630                    <div class="faq-answer">Install via the Chrome Web Store, find the icon in your toolbar, search any WooCommerce website, and click the scraper icon to follow the instructions in the popup.</div>
631                </div>
632                <div class="faq-item">
633                    <button class="faq-question">Does WooCommerce Scraper work on protected websites?</button>
634                    <div class="faq-answer">Yes, it can export content regardless of security apps. Contact support if you encounter any scraping issues with specific websites.</div>
635                </div>
636                <div class="faq-item">
637                    <button class="faq-question">How do I cancel my subscription?</button>
638                    <div class="faq-answer">You can cancel at any time from your profile page. Your access is retained until the end of the current billing period.</div>
639                </div>
640                <div class="faq-item">
641                    <button class="faq-question">Do you have customer support?</button>
642                    <div class="faq-answer">
642Yes, we offer customer support via email at <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="c1a8afa7ae81b1b3aea5b4a2b5b2a2b3a0b1a4b3efaeafada8afa4">[email&#160;protected]</a> or through the contact area on our website.</div>
643                </div>
644                <div class="faq-item">
645                    <button class="faq-question">How do I import into WooCommerce, and what is the Live Feed?</button>
646                    <div class="faq-answer">The Live Feed keeps a store you have scraped continuously up to date, refreshing every hour. It is available on every account: a free account includes 1 live feed capped at 15 products, and Premium removes that cap and allows up to 5 feeds. The feed is built for our free WooCommerce import plugin: connect your account and import products, variations, images, categories and prices directly into WooCommerce, with automatic re-sync. It is an import feed for your own store, not a shopping or advertising feed.</div>
647                </div>
648                <div class="faq-item">
649                    <button class="faq-question">How do I migrate products from WooCommerce to Shopify?</button>
650                    <div class="faq-answer">With Scrapify, migrating from WooCommerce to Shopify takes just a few steps. Enter the WooCommerce store URL, let Scrapify extract all products, then download the file in Shopify CSV format. Go to your Shopify admin, navigate to Products &gt; Import, and upload the file. All product titles, prices, descriptions, images, and variants will be ready to go. No coding or manual data entry required.</div>
651                </div>
652                <div class="faq-item">
653                    <button class="faq-question">How do I migrate products from Shopify to WooCommerce?</button>
654                    <div class="faq-answer">To migrate from Shopify to WooCommerce, paste the Shopify store URL into Scrapify and run the scraper. Once complete, download the export in WooCommerce CSV format. In your WordPress dashboard, go to WooCommerce &gt; Products &gt; Import and upload the file. Scrapify automatically formats the data to match WooCommerce's import requirements, so products appear correctly without any manual reformatting.</div>
655                </div>
656                <div class="faq-item">
657                    <button class="faq-question">Can I export all WooCommerce products to CSV or Excel?</button>
658                    <div class="faq-answer">Yes. Scrapify can export the full product catalogue from any WooCommerce store to CSV or Excel with one click. The free plan supports up to 10 products per export, and every export format is included on every plan: WooCommerce CSV, Shopify CSV and Excel. A free account raises that to 15 products and adds the hourly auto-refreshing live feed plus the WordPress import plugin that syncs from it, both capped at the same 15 products. A Premium account raises that ceiling to 100,000 products so you can export and sync a full catalogue, and allows up to 5 live feeds.</div>
659                </div>
660                <div class="faq-item">
661                    <button class="faq-question">Is there a free WooCommerce product scraper?</button>
662                    <div class="faq-answer">Yes, Scrapify has a free plan that requires no account and no credit card. You can extract up to 10 products from any WooCommerce or Shopify store, from as many stores as you like, and download them instantly as WooCommerce CSV, Shopify CSV or Excel. Creating a free account raises this to 15 products and unlocks the hourly auto-refreshing live feed and the WordPress import plugin at that same 15 product cap. For bigger jobs of up to 100,000 products per scrape and 5 live feeds, the Premium plan is $9.99 per month.</div>
663                </div>
664                <div class="faq-item">
665                    <button class="faq-question">What product data does Scrapify export?</button>
666                    <div class="faq-answer">Scrapify exports product titles, prices, descriptions, images, stock levels, categories, and variants. On the Premium plan you also get access to a full product details view, making it easy to check the original listing during or after migration.</div>
667                </div>
668                <div class="faq-item">
669                    <button class="faq-question">Can I copy products from one WooCommerce store to another?</button>
670                    <div class="faq-answer">Yes. Scrapify is the fastest way to copy products between WooCommerce stores. Scrape the source store, download the export in WooCommerce CSV format, then import it directly into the destination store via WooCommerce's built-in product importer. The entire process takes minutes regardless of catalog size.</div>
671                </div>
672                <div class="faq-item">
673                    <button class="faq-question">How long does it take to scrape a large WooCommerce store?</button>
674                    <div class="faq-answer">Scrapify uses a direct API approach rather than page-by-page crawling, so even large stores with thousands of products are typically extracted in seconds to a few minutes. You can watch products being pulled in real time and download the export as soon as it completes.</div>
675                </div>
676                <div class="faq-item">
677                    <button class="faq-question">What is the difference between the Free and Premium plan?</button>
678                    <div class="faq-answer">The Free plan (no account needed) lets you extract up to 10 products from as many stores as you like, and download them as WooCommerce CSV, Shopify CSV or Excel. A free registered account raises this to 15 products, adds 3 private email aliases, and includes 1 hourly auto-refreshing live product feed plus the WordPress 
678import plugin that syncs from it, both capped at those same 15 products. The Premium plan at $9.99 per month raises the product cap to 100,000 so you can export and sync an entire catalogue, raises the feed limit to 5, and includes 20 private email aliases.</div>
679                </div>
680                <div class="faq-item">
681                    <button class="faq-question">Why am I charged in ZAR at checkout instead of USD?</button>
682                    <div class="faq-answer">Scrapify's payment processor, PayFast, is based in South Africa and processes all transactions in South African Rand (ZAR). The USD price shown on our pricing page is a display price for international reference, and your bank or card provider will convert the ZAR charge at the current exchange rate at the time of payment. The final amount in your local currency may vary slightly depending on your bank's conversion rate. If you have any questions about a charge, contact us at <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="563f3830391626243932233522253524372633247839383a3f3833">[email&#160;protected]</a>.</div>
683                </div>
684            </div>
685        </div>
686    </section>
687
688    <!-- Newsletter Section -->
689    <section class="geo-section newsletter-section">
690        <div class="geo-container">
691            <div class="geo-animate newsletter-card">
692                <div class="blur-circle blur-circle-purple" style="width: 200px; height: 200px; top: -50px; right: -40px;"></div>
693                <div class="blur-circle blur-circle-cyan" style="width: 150px; height: 150px; bottom: -30px; left: -40px;"></div>
694                <h2 class="geo-h2">Stay Ahead of the Curve</h2>
695                <p class="geo-text-muted geo-mt-4" style="max-width: 480px; margin-left: auto; margin-right: auto;">Subscribe to our newsletter and get the latest updates, tips, and exclusive offers.</p>
696                <div class="newsletter-form">
697                    <input type="email" class="geo-input" id="newsletterEmail" placeholder="Enter your email address" onkeydown="if(event.key==='Enter')subscribeNewsletter()">
698                    <button class="geo-btn geo-btn-primary" id="newsletterBtn" onclick="subscribeNewsletter()">Subscribe</button>
699                </div>
700                <div class="newsletter-features">
701                    <div class="newsletter-feature">${Icons.check} Weekly Updates</div>
702                    <div class="newsletter-feature">${Icons.check} Pro Tips</div>
703                    <div class="newsletter-feature">${Icons.check} Exclusive Offers</div>
704                </div>
705            </div>
706        </div>
707    </section>
708
709    <!-- Footer (rendered by components.js) -->
710    <footer class="geo-footer"></footer>
vendor: 114 bytes, lines 710-712
710
711
712    <script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script>
712<script>
713        async function subscribeNewsletter() {
714            const email = document.getElementById('newsletterEmail').value.trim();
715            const btn = document.getElementById('newsletterBtn');
716            if (!email) return showToast('Please enter your email address.', 'warning');
717            btn.disabled = true;
718            btn.innerHTML = '<span class="geo-spinner"></span> Subscribing...';
719            try {
720                const res = await fetch('/api/newsletter/subscribe', {
721                    method: 'POST',
722                    headers: { 'Content-Type': 'application/json' },
723                    body: JSON.stringify({ email })
724                });
725                const data = await res.json();
726                if (res.ok) {
727                    showToast(data.message, 'success');
728                    document.getElementById('newsletterEmail').value = '';
729                } else {
730                    showToast(data.error || 'Failed to subscribe', 'error');
731                }
732            } catch {
733                showToast('Network error. Please try again.', 'error');
734            } finally {
735                btn.disabled = false;
736                btn.textContent = 'Subscribe';
737            }
738        }
739
740        // Load approved user reviews; replace fallback cards if any exist
741        (async function loadTestimonials() {
742            try {
743                const res = await fetch('/api/reviews');
744                if (!res.ok) return;
745                const reviews = await res.json();
746                if (reviews.length === 0) return;
747                const grid = document.getElementById('testimonialsGrid');
748                if (!grid) return;
749                const starsHtml = (rating) => {
750                    const full = Math.floor(rating);
751                    const half = rating % 1 >= 0.5;
752                    let s = '★'.repeat(full);
753                    if (half) s += '½';
754                    return `${s}<span style="color:var(--geo-text-dim);font-size:0.75rem;margin-left:4px;">${rating.toFixed(1)}</span>`;
755                };
756                const initials = (name) => name.split(' ').map(w => w[0]).join('').toUpperCase().slice(0, 2);
757                const escH = (str) => { const d = document.createElement('div'); d.textContent = str; return d.innerHTML; };
758                grid.innerHTML = reviews.map((r, i) => `
759                    <div class="testimonial-card visible">
760                        <div style="display:flex;align-items:center;gap:var(--geo-space-3);">
761                            <div class="avatar">${r.avatar && r.avatar.startsWith('http') ? `<img src="${r.avatar}" alt="" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">` : escH(initials(r.display_name))}</div>
762                            <div>
763                                <div style="font-weight:600;font-size:0.9375rem;">${escH(r.display_name)}</div>
764                                ${r.location ? `<div class="geo-text-xs geo-text-dim">${escH(r.location)}</div>` : ''}
765                            </div>
766                        </div>
767                        <div class="stars">${starsHtml(r.rating)}</div>
768                        <blockquote>"${escH(r.body)}"</blockquote>
769                    </div>`).join('');
770            } catch {}
771        })();
772
773        async function startCheckout(kind) {
774            let user = null;
775            try {
776                const me = await fetch('/api/me');
777                if (me.ok) user = (await me.json()).user;
778            } catch {}
779
780            if (!user) {
781                showToast('Please sign in or create an account to upgrade.', 'info');
782                showModal('register');
783                return;
784            }
785            if (user.plan === 'agency' || (user.plan === 'premium' && kind !== 'agency')) {
786                showToast(user.plan === 'agency'
787                    ? 'You are already on the Agency plan!'
788                    : 'You already have a Premium account. Choose Agency to upgrade further.', 'info');
789                return;
790            }
791
792            // Preselect the option they clicked so the checkout opens on it.
793            window.location.href = kind ? `/checkout?kind=${encodeURIComponent(kind)}` : '/checkout';
794        }
795
796        // Per-tier detail panels. Only one is open at a time, and it closes on Escape or
797        // a click outside the card, so it never sits over the table unnoticed.
798        (function initPlanInfo() {
799            const pops = [...document.querySelectorAll('.plan-info-pop')];
800            // Reparent to <body> so no card's overflow or hover transform can affect them.
801            pops.forEach(pop =>
801 document.body.appendChild(pop));
802
803            const GAP = 10, EDGE = 12;
804
805            // Anchored to the dot: right-aligned to it, below it, flipping above when
806            // there is no room, and always clamped inside the viewport.
807            function place(pop, btn) {
808                const r = btn.getBoundingClientRect();
809                const w = pop.offsetWidth, h = pop.offsetHeight;
810                let left = Math.min(r.right - w, window.innerWidth - w - EDGE);
811                left = Math.max(EDGE, left);
812                let top = r.bottom + GAP;
813                // Prefer above when it will not fit below, then clamp regardless: the dot
814                // can sit outside the viewport entirely, and "above" is then still off
815                // screen, so preferring it is not by itself enough.
816                if (top + h > window.innerHeight - EDGE) top = r.top - h - GAP;
817                top = Math.max(EDGE, Math.min(top, window.innerHeight - h - EDGE));
818                pop.style.left = left + 'px';
819                pop.style.top = top + 'px';
820            }
821
822            function openerFor(pop) {
823                return document.querySelector(`[aria-controls="${pop.id}"]`);
824            }
825
826            function close(pop) {
827                if (pop.hidden) return;
828                pop.hidden = true;
829                const btn = openerFor(pop);
830                if (btn) btn.setAttribute('aria-expanded', 'false');
831            }
832
833            const closeAll = () => pops.forEach(close);
834            const openPop = () => pops.find(p => !p.hidden);
835
836            document.querySelectorAll('.plan-info-btn').forEach(btn => {
837                btn.addEventListener('click', e => {
838                    e.stopPropagation();
839                    const pop = document.getElementById(btn.getAttribute('aria-controls'));
840                    const wasOpen = !pop.hidden;
841                    closeAll();
842                    if (!wasOpen) {
843                        // Measure while laid out but not yet painted, so it never
844                        // appears at 0,0 for a frame before being positioned.
845                        pop.style.visibility = 'hidden';
846                        pop.hidden = false;
847                        place(pop, btn);
848                        pop.style.visibility = '';
849                        btn.setAttribute('aria-expanded', 'true');
850                        // Focus the close button so keyboard users land inside the popup.
851                        pop.querySelector('.plan-info-close').focus();
852                    }
853                });
854            });
855
856            // Keep it glued to its dot while the page moves under it.
857            const reposition = () => {
858                const pop = openPop();
859                if (pop) place(pop, openerFor(pop));
860            };
861            window.addEventListener('scroll', reposition, { passive: true });
862            window.addEventListener('resize', reposition);
863
864            document.querySelectorAll('.plan-info-close').forEach(btn => {
865                btn.addEventListener('click', e => {
866                    e.stopPropagation();
867                    const pop = btn.closest('.plan-info-pop');
868                    close(pop);
869                    const opener = document.querySelector(`[aria-controls="${pop.id}"]`);
870                    if (opener) opener.focus();
871                });
872            });
873
874            // A click inside the panel must not count as an outside click.
875            pops.forEach(pop => pop.addEventListener('click', e => e.stopPropagation()));
876            document.addEventListener('click', closeAll);
877            document.addEventListener('keydown', e => { if (e.key === 'Escape') closeAll(); });
878        })();
879
880        // Keep the advertised pass price in step with the server's configured price.
881        fetch('/api/pay-methods').then(r => r.json()).then(m => {
882            if (m?.pass) {
883                document.getElementById('passPriceLabel').textContent = '$' + Math.round(parseFloat(m.pass.amount));
884                document.getElementById('passDaysLabel').textContent  = m.pass.days;
885            }
886            if (m?.agency) {
887                document.getElementById('agencyPrice').innerHTML = '$' + Math.round(parseFloat(m.agency.amount)) + '<span>/mo</span>';
888                document.getElementById('agencyFeedsLabel').textContent = m.agency.feeds;
889                document.getElementById('agencyAliasLabel').textContent = m.agency.aliases;
890                // The detail panel repeats these figures, so it tracks the same source.
891                document.getElementById('agencyFeedsPop').textContent = m.agency.feeds;
892                document.getElementById('agencyAliasPop').textContent = m.agency.aliases;
893            }
894        }).catch(() => {});
895    </script>
vendor: 1 bytes, line 895
895
896<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":"437da16a5e794fc585211333232af63e","r":1,"spa":2}' crossorigin="anonymous"></script>
896
897</body>
898</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.