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 • 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 & 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 & Shopify sources</li> 375 <li>${Icons.check} Woo CSV, Shopify CSV & Excel export</li> 376 <li>${Icons.check} All categories, tags & 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 • No credit card</div> 385 <div class="price-divider"></div> 386 <ul> 387 <li>${Icons.check} 15 products per export & 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 & Shopify sources</li> 411 <li>${Icons.check} Woo CSV, Shopify CSV & Excel export</li> 412 <li>${Icons.check} All categories, tags & 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 • 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 & Shopify sources</li> 450 <li>${Icons.check} Woo CSV, Shopify CSV & Excel export</li> 451 <li>${Icons.check} All categories, tags & 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 • 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 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 > 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 > 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.</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 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.