Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>LinkedIn Profile Scraper | 10K Profiles per Batch</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<script> | |
tailwind.config = { | |
darkMode: 'class', | |
theme: { | |
extend: { | |
colors: { | |
linkedin: '#0A66C2', | |
primary: '#0A66C2', | |
secondary: '#378FE9', | |
accent: '#16437E', | |
dark: '#1D2226', | |
light: '#F5F5F5', | |
darkBg: '#12181D', | |
darkCard: '#1D2933' | |
} | |
} | |
} | |
} | |
</script> | |
<style> | |
.scraper-card { | |
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1); | |
border-radius: 16px; | |
transition: all 0.3s ease; | |
} | |
.scraper-card:hover { | |
transform: translateY(-5px); | |
box-shadow: 0 20px 30px -10px rgba(0, 0, 0, 0.15); | |
} | |
.progress-bar { | |
height: 8px; | |
border-radius: 4px; | |
overflow: hidden; | |
background-color: #e0e0e0; | |
} | |
.progress-fill { | |
height: 100%; | |
border-radius: 4px; | |
background: linear-gradient(90deg, #0A66C2, #378FE9); | |
transition: width 0.5s ease; | |
} | |
.profile-card { | |
transition: all 0.2s ease; | |
border-left: 4px solid #0A66C2; | |
} | |
.profile-card:hover { | |
transform: translateX(5px); | |
background-color: #f8fafc; | |
} | |
.dark .profile-card:hover { | |
background-color: #1f2a33; | |
} | |
.scrape-btn { | |
background: linear-gradient(135deg, #0A66C2, #16437E); | |
transition: all 0.3s ease; | |
} | |
.scrape-btn:hover { | |
transform: scale(1.02); | |
box-shadow: 0 10px 20px -5px rgba(10, 102, 194, 0.4); | |
} | |
.scrape-btn:active { | |
transform: scale(0.98); | |
} | |
.tab-btn { | |
transition: all 0.2s ease; | |
border-bottom: 3px solid transparent; | |
} | |
.tab-btn.active { | |
border-bottom-color: #0A66C2; | |
color: #0A66C2; | |
} | |
.result-grid { | |
display: grid; | |
grid-template-columns: 1fr; | |
gap: 1rem; | |
} | |
@media (min-width: 640px) { | |
.result-grid { | |
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); | |
gap: 1.5rem; | |
} | |
} | |
.animate-pulse { | |
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; | |
} | |
@keyframes pulse { | |
0%, 100% { opacity: 1; } | |
50% { opacity: 0.5; } | |
} | |
.floating-notification { | |
animation: floatUp 0.5s ease-out forwards; | |
} | |
@keyframes floatUp { | |
from { | |
opacity: 0; | |
transform: translateY(20px); | |
} | |
to { | |
opacity: 1; | |
transform: translateY(0); | |
} | |
} | |
.skeleton { | |
background-color: #e2e8f0; | |
border-radius: 4px; | |
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; | |
} | |
.dark .skeleton { | |
background-color: #2d3748; | |
} | |
.dark-mode-toggle { | |
transition: all 0.3s ease; | |
} | |
.dark-mode-toggle:hover { | |
transform: rotate(15deg); | |
} | |
.stat-card:hover .stat-icon { | |
transform: scale(1.1) rotate(5deg); | |
} | |
.stat-icon { | |
transition: all 0.3s ease; | |
} | |
.tooltip { | |
position: relative; | |
display: inline-block; | |
} | |
.tooltip .tooltip-text { | |
visibility: hidden; | |
width: 200px; | |
background-color: #1D2226; | |
color: #fff; | |
text-align: center; | |
border-radius: 6px; | |
padding: 8px; | |
position: absolute; | |
z-index: 1; | |
bottom: 125%; | |
left: 50%; | |
transform: translateX(-50%); | |
opacity: 0; | |
transition: opacity 0.3s; | |
font-size: 0.875rem; | |
box-shadow: 0 4px 6px rgba(0,0,0,0.1); | |
} | |
.tooltip:hover .tooltip-text { | |
visibility: visible; | |
opacity: 1; | |
} | |
.dark .tooltip .tooltip-text { | |
background-color: #2d3748; | |
} | |
.mobile-menu { | |
transform: translateX(-100%); | |
transition: transform 0.3s ease; | |
} | |
.mobile-menu.open { | |
transform: translateX(0); | |
} | |
.export-btn { | |
transition: all 0.2s ease; | |
} | |
.export-btn:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 4px 6px rgba(0,0,0,0.1); | |
} | |
.pagination-btn { | |
transition: all 0.2s ease; | |
} | |
.pagination-btn:hover:not(.disabled) { | |
background-color: #e5e7eb; | |
transform: translateY(-2px); | |
} | |
.dark .pagination-btn:hover:not(.disabled) { | |
background-color: #2d3748; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-50 min-h-screen dark:bg-darkBg"> | |
<!-- Header --> | |
<header class="bg-white shadow-sm dark:bg-darkCard"> | |
<div class="container mx-auto px-4 py-4 flex justify-between items-center"> | |
<div class="flex items-center"> | |
<div class="bg-primary p-2 rounded-lg mr-3"> | |
<i class="fab fa-linkedin text-white text-2xl"></i> | |
</div> | |
<h1 class="text-2xl font-bold text-gray-800 dark:text-gray-200">LinkedIn<span class="text-primary">Scraper</span></h1> | |
</div> | |
<nav> | |
<ul class="hidden md:flex space-x-6"> | |
<li><a href="#" class="text-gray-600 hover:text-primary font-medium dark:text-gray-300 dark:hover:text-secondary">Dashboard</a></li> | |
<li><a href="#" class="text-gray-600 hover:text-primary font-medium dark:text-gray-300 dark:hover:text-secondary">Scrape History</a></li> | |
<li><a href="#" class="text-gray-600 hover:text-primary font-medium dark:text-gray-300 dark:hover:text-secondary">API</a></li> | |
<li><a href="#" class="text-gray-600 hover:text-primary font-medium dark:text-gray-300 dark:hover:text-secondary">Help</a></li> | |
</ul> | |
<button id="mobileMenuBtn" class="md:hidden text-gray-600 dark:text-gray-300"> | |
<i class="fas fa-bars text-xl"></i> | |
</button> | |
</nav> | |
<div class="flex items-center"> | |
<button class="bg-primary hover:bg-secondary text-white px-4 py-2 rounded-lg font-medium flex items-center mr-4"> | |
<i class="fas fa-user-plus mr-2"></i> Upgrade | |
</button> | |
<button id="darkModeToggle" class="dark-mode-toggle bg-gray-200 dark:bg-gray-700 p-2 rounded-full mr-4"> | |
<i class="fas fa-moon text-gray-700 dark:text-yellow-300"></i> | |
</button> | |
<div class="relative"> | |
<div class="w-10 h-10 rounded-full bg-gray-200 dark:bg-gray-700 flex items-center justify-center cursor-pointer"> | |
<i class="fas fa-user text-gray-600 dark:text-gray-300"></i> | |
</div> | |
<div class="absolute right-0 bottom-0 w-3 h-3 bg-green-500 rounded-full border-2 border-white dark:border-darkCard"></div> | |
</div> | |
</div> | |
</div> | |
</header> | |
<!-- Mobile Menu --> | |
<div id="mobileMenu" class="mobile-menu fixed inset-0 z-50 bg-black bg-opacity-50 hidden"> | |
<div class="bg-white dark:bg-darkCard w-64 h-full p-4"> | |
<div class="flex justify-between items-center mb-8"> | |
<h3 class="text-xl font-bold text-gray-800 dark:text-gray-200">Menu</h3> | |
<button id="closeMobileMenu" class="p-2 text-gray-600 dark:text-gray-300"> | |
<i class="fas fa-times text-xl"></i> | |
</button> | |
</div> | |
<ul class="space-y-4"> | |
<li><a href="#" class="text-gray-600 hover:text-primary font-medium block py-2 dark:text-gray-300 dark:hover:text-secondary">Dashboard</a></li> | |
<li><a href="#" class="text-gray-600 hover:text-primary font-medium block py-2 dark:text-gray-300 dark:hover:text-secondary">Scrape History</a></li> | |
<li><a href="#" class="text-gray-600 hover:text-primary font-medium block py-2 dark:text-gray-300 dark:hover:text-secondary">API</a></li> | |
<li><a href="#" class="text-gray-600 hover:text-primary font-medium block py-2 dark:text-gray-300 dark:hover:text-secondary">Help</a></li> | |
<li class="pt-4"> | |
<button class="bg-primary hover:bg-secondary text-white px-4 py-2 rounded-lg font-medium flex items-center w-full"> | |
<i class="fas fa-user-plus mr-2"></i> Upgrade Account | |
</button> | |
</li> | |
</ul> | |
</div> | |
</div> | |
<!-- Main Content --> | |
<main class="container mx-auto px-4 py-8"> | |
<div class="text-center mb-10"> | |
<h2 class="text-4xl font-bold text-gray-800 dark:text-gray-200 mb-3">LinkedIn Profile Scraper</h2> | |
<p class="text-xl text-gray-600 dark:text-gray-400 max-w-3xl mx-auto">Extract up to 10,000 LinkedIn profiles per batch with professional details including name, title, company, location, and more.</p> | |
</div> | |
<!-- Stats Cards --> | |
<div class="grid grid-cols-2 sm:grid-cols-4 gap-4 sm:gap-6 mb-8"> | |
<div class="scraper-card stat-card bg-white dark:bg-darkCard p-6 rounded-xl"> | |
<div class="flex justify-between items-center"> | |
<div> | |
<p class="text-gray-500 dark:text-gray-400 text-sm">Total Scraped</p> | |
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">124,856</h3> | |
</div> | |
<div class="bg-blue-100 dark:bg-blue-900 p-3 rounded-lg"> | |
<i class="fas fa-database text-primary dark:text-blue-300 text-xl stat-icon"></i> | |
</div> | |
</div> | |
</div> | |
<div class="scraper-card stat-card bg-white dark:bg-darkCard p-6 rounded-xl"> | |
<div class="flex justify-between items-center"> | |
<div> | |
<p class="text-gray-500 dark:text-gray-400 text-sm">This Month</p> | |
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">8,420</h3> | |
</div> | |
<div class="bg-green-100 dark:bg-green-900 p-3 rounded-lg"> | |
<i class="fas fa-chart-line text-green-500 dark:text-green-300 text-xl stat-icon"></i> | |
</div> | |
</div> | |
</div> | |
<div class="scraper-card stat-card bg-white dark:bg-darkCard p-6 rounded-xl"> | |
<div class="flex justify-between items-center"> | |
<div> | |
<p class="text-gray-500 dark:text-gray-400 text-sm">Batch Size</p> | |
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">10,000</h3> | |
</div> | |
<div class="bg-purple-100 dark:bg-purple-900 p-3 rounded-lg"> | |
<i class="fas fa-layer-group text-purple-500 dark:text-purple-300 text-xl stat-icon"></i> | |
</div> | |
</div> | |
</div> | |
<div class="scraper-card stat-card bg-white dark:bg-darkCard p-6 rounded-xl"> | |
<div class="flex justify-between items-center"> | |
<div> | |
<p class="text-gray-500 dark:text-gray-400 text-sm">Success Rate</p> | |
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">98.7%</h3> | |
</div> | |
<div class="bg-yellow-100 dark:bg-yellow-900 p-3 rounded-lg"> | |
<i class="fas fa-check-circle text-yellow-500 dark:text-yellow-300 text-xl stat-icon"></i> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Scraper Interface --> | |
<div class="scraper-card bg-white dark:bg-darkCard p-6 md:p-8 rounded-xl mb-8"> | |
<div class="flex flex-wrap mb-6 border-b dark:border-gray-700"> | |
<button class="tab-btn px-4 py-2 font-medium text-gray-600 dark:text-gray-300 mr-4 active">New Scrape</button> | |
<button class="tab-btn px-4 py-2 font-medium text-gray-600 dark:text-gray-300 mr-4">Saved Templates</button> | |
<button class="tab-btn px-4 py-2 font-medium text-gray-600 dark:text-gray-300">Advanced Filters</button> | |
</div> | |
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6 mb-6"> | |
<div> | |
<label class="block text-gray-700 dark:text-gray-300 font-medium mb-2">Search Keywords</label> | |
<input type="text" class="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent dark:bg-gray-800 dark:text-gray-200" placeholder="e.g., Software Engineer, Marketing Manager"> | |
</div> | |
<div> | |
<label class="block text-gray-700 dark:text-gray-300 font-medium mb-2">Location</label> | |
<input type="text" class="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent dark:bg-gray-800 dark:text-gray-200" placeholder="e.g., United States, San Francisco"> | |
</div> | |
<div> | |
<label class="block text-gray-700 dark:text-gray-300 font-medium mb-2">Industry</label> | |
<select class="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent dark:bg-gray-800 dark:text-gray-200"> | |
<option value="">All Industries</option> | |
<option value="it">Information Technology</option> | |
<option value="finance">Finance</option> | |
<option value="healthcare">Healthcare</option> | |
<option value="education">Education</option> | |
<option value="marketing">Marketing</option> | |
</select> | |
</div> | |
<div> | |
<label class="block text-gray-700 dark:text-gray-300 font-medium mb-2">Company Size</label> | |
<select class="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent dark:bg-gray-800 dark:text-gray-200"> | |
<option value="">All Sizes</option> | |
<option value="1-10">1-10 employees</option> | |
<option value="11-50">11-50 employees</option> | |
<option value="51-200">51-200 employees</option> | |
<option value="201-500">201-500 employees</option> | |
<option value="501+">501+ employees</option> | |
</select> | |
</div> | |
</div> | |
<div class="mb-6"> | |
<label class="block text-gray-700 dark:text-gray-300 font-medium mb-2">Number of Profiles</label> | |
<div class="flex flex-col sm:flex-row items-start sm:items-center gap-3 sm:gap-4"> | |
<input type="range" min="100" max="10000" step="100" value="5000" class="w-full sm:mr-4 dark:accent-primary" id="profileRange"> | |
<div class="bg-gray-100 dark:bg-gray-800 px-4 py-2 rounded-lg w-full sm:w-auto text-center dark:text-gray-200"> | |
<span id="profileCount">5,000</span> profiles | |
</div> | |
</div> | |
<div class="text-sm text-gray-500 dark:text-gray-400 mt-2">Max 10,000 profiles per batch</div> | |
</div> | |
<div class="flex flex-wrap items-center justify-between"> | |
<div class="flex items-center mb-4 md:mb-0"> | |
<input type="checkbox" id="includeContact" class="mr-2 h-5 w-5 text-primary rounded focus:ring-primary dark:bg-gray-800"> | |
<label for="includeContact" class="text-gray-700 dark:text-gray-300">Include contact information</label> | |
</div> | |
<button id="scrapeBtn" class="scrape-btn text-white px-8 py-3 rounded-lg font-semibold flex items-center w-full sm:w-auto justify-center"> | |
<i class="fas fa-bolt mr-2"></i> | |
<span id="scrapeBtnText">Start Scraping</span> | |
<span id="scrapeSpinner" class="ml-2 hidden"> | |
<i class="fas fa-spinner fa-spin"></i> | |
</span> | |
</button> | |
</div> | |
</div> | |
<!-- Progress Section --> | |
<div id="progressSection" class="scraper-card bg-white dark:bg-darkCard p-6 md:p-8 rounded-xl mb-8 hidden"> | |
<h3 class="text-xl font-bold text-gray-800 dark:text-gray-200 mb-4">Scraping in Progress</h3> | |
<div class="mb-4"> | |
<div class="flex justify-between mb-2"> | |
<span class="text-gray-700 dark:text-gray-300">Extracting profiles...</span> | |
<span id="progressPercent" class="text-primary font-medium">0%</span> | |
</div> | |
<div class="progress-bar"> | |
<div id="progressFill" class="progress-fill" style="width: 0%"></div> | |
</div> | |
</div> | |
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4 mt-6"> | |
<div class="text-center p-4 bg-gray-50 dark:bg-gray-800 rounded-lg"> | |
<div class="text-2xl font-bold text-primary" id="profilesScraped">0</div> | |
<div class="text-gray-600 dark:text-gray-400">Profiles Scraped</div> | |
</div> | |
<div class="text-center p-4 bg-gray-50 dark:bg-gray-800 rounded-lg"> | |
<div class="text-2xl font-bold text-green-500" id="successRate">0%</div> | |
<div class="text-gray-600 dark:text-gray-400">Success Rate</div> | |
</div> | |
<div class="text-center p-4 bg-gray-50 dark:bg-gray-800 rounded-lg"> | |
<div class="text-2xl font-bold text-gray-800 dark:text-gray-200" id="timeElapsed">0s</div> | |
<div class="text-gray-600 dark:text-gray-400">Time Elapsed</div> | |
</div> | |
<div class="text-center p-4 bg-gray-50 dark:bg-gray-800 rounded-lg"> | |
<div class="text-2xl font-bold text-gray-800 dark:text-gray-200" id="timeRemaining">~30s</div> | |
<div class="text-gray-600 dark:text-gray-400">Estimated Time</div> | |
</div> | |
</div> | |
</div> | |
<!-- Results Section --> | |
<div id="resultsSection" class="hidden"> | |
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-6"> | |
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">Scraping Results</h3> | |
<div class="flex flex-col sm:flex-row gap-2 sm:gap-3 w-full sm:w-auto"> | |
<button class="export-btn bg-white dark:bg-darkCard border border-gray-300 dark:border-gray-700 px-4 py-2 rounded-lg font-medium flex items-center justify-center"> | |
<i class="fas fa-download mr-2"></i> Export CSV | |
</button> | |
<button class="export-btn bg-white dark:bg-darkCard border border-gray-300 dark:border-gray-700 px-4 py-2 rounded-lg font-medium flex items-center justify-center"> | |
<i class="fas fa-file-excel mr-2"></i> Export Excel | |
</button> | |
</div> | |
</div> | |
<div class="result-grid mb-8" id="resultGrid"> | |
<!-- Profile cards will be inserted here by JavaScript --> | |
</div> | |
<div class="flex flex-col sm:flex-row justify-between items-center bg-white dark:bg-darkCard p-4 rounded-lg"> | |
<div class="text-gray-600 dark:text-gray-400 mb-4 sm:mb-0"> | |
Showing <span id="showingCount">10</span> of <span id="totalCount">5,000</span> profiles | |
</div> | |
<div class="flex flex-wrap justify-center gap-2"> | |
<button class="pagination-btn px-4 py-2 bg-gray-100 dark:bg-gray-800 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed" id="prevPageBtn" disabled> | |
<i class="fas fa-chevron-left"></i> | |
</button> | |
<button class="pagination-btn px-4 py-2 bg-primary text-white rounded-lg">1</button> | |
<button class="pagination-btn px-4 py-2 bg-gray-100 dark:bg-gray-800 rounded-lg">2</button> | |
<button class="pagination-btn px-4 py-2 bg-gray-100 dark:bg-gray-800 rounded-lg">3</button> | |
<button class="pagination-btn px-4 py-2 bg-gray-100 dark:bg-gray-800 rounded-lg">...</button> | |
<button class="pagination-btn px-4 py-2 bg-gray-100 dark:bg-gray-800 rounded-lg">50</button> | |
<button class="pagination-btn px-4 py-2 bg-gray-100 dark:bg-gray-800 rounded-lg" id="nextPageBtn"> | |
<i class="fas fa-chevron-right"></i> | |
</button> | |
</div> | |
</div> | |
</div> | |
<!-- Notification --> | |
<div id="notification" class="fixed bottom-4 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg hidden"> | |
<i class="fas fa-check-circle mr-2"></i> | |
<span>Scraping completed successfully!</span> | |
</div> | |
</main> | |
<!-- Footer --> | |
<footer class="bg-dark text-gray-300 py-8 mt-12"> | |
<div class="container mx-auto px-4"> | |
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 sm:gap-8"> | |
<div> | |
<h4 class="text-white text-lg font-semibold mb-4">LinkedIn Scraper</h4> | |
<p class="mb-4">Professional LinkedIn data extraction tool for recruiters, marketers, and sales professionals.</p> | |
<div class="flex space-x-4"> | |
<a href="#" class="text-gray-400 hover:text-white"><i class="fab fa-twitter"></i></a> | |
<a href="#" class="text-gray-400 hover:text-white"><i class="fab fa-facebook"></i></a> | |
<a href="#" class="text-gray-400 hover:text-white"><i class="fab fa-linkedin"></i></a> | |
</div> | |
</div> | |
<div> | |
<h4 class="text-white text-lg font-semibold mb-4">Features</h4> | |
<ul class="space-y-2"> | |
<li><a href="#" class="hover:text-white">Profile Scraping</a></li> | |
<li><a href="#" class="hover:text-white">Company Data</a></li> | |
<li><a href="#" class="hover:text-white">Email Extraction</a></li> | |
<li><a href="#" class="hover:text-white">Bulk Processing</a></li> | |
</ul> | |
</div> | |
<div> | |
<h4 class="text-white text-lg font-semibold mb-4">Resources</h4> | |
<ul class="space-y-2"> | |
<li><a href="#" class="hover:text-white">Documentation</a></li> | |
<li><a href="#" class="hover:text-white">API Reference</a></li> | |
<li><a href="#" class="hover:text-white">Tutorials</a></li> | |
<li><a href="#" class="hover:text-white">Blog</a></li> | |
</ul> | |
</div> | |
<div> | |
<h4 class="text-white text-lg font-semibold mb-4">Legal</h4> | |
<ul class="space-y-2"> | |
<li><a href="#" class="hover:text-white">Privacy Policy</a></li> | |
<li><a href="#" class="hover:text-white">Terms of Service</a></li> | |
<li><a href="#" class="hover:text-white">Compliance</a></li> | |
<li><a href="#" class="hover:text-white">GDPR</a></li> | |
</ul> | |
</div> | |
</div> | |
<div class="border-t border-gray-700 mt-8 pt-6 text-center"> | |
<p>© 2023 LinkedInScraper. All rights reserved.</p> | |
</div> | |
</div> | |
</footer> | |
<script> | |
// DOM Elements | |
const profileRange = document.getElementById('profileRange'); | |
const profileCount = document.getElementById('profileCount'); | |
const scrapeBtn = document.getElementById('scrapeBtn'); | |
const scrapeBtnText = document.getElementById('scrapeBtnText'); | |
const scrapeSpinner = document.getElementById('scrapeSpinner'); | |
const progressSection = document.getElementById('progressSection'); | |
const resultsSection = document.getElementById('resultsSection'); | |
const progressFill = document.getElementById('progressFill'); | |
const progressPercent = document.getElementById('progressPercent'); | |
const profilesScraped = document.getElementById('profilesScraped'); | |
const successRate = document.getElementById('successRate'); | |
const timeElapsed = document.getElementById('timeElapsed'); | |
const timeRemaining = document.getElementById('timeRemaining'); | |
const notification = document.getElementById('notification'); | |
const resultGrid = document.getElementById('resultGrid'); | |
const darkModeToggle = document.getElementById('darkModeToggle'); | |
const mobileMenuBtn = document.getElementById('mobileMenuBtn'); | |
const mobileMenu = document.getElementById('mobileMenu'); | |
const closeMobileMenu = document.getElementById('closeMobileMenu'); | |
const prevPageBtn = document.getElementById('prevPageBtn'); | |
const nextPageBtn = document.getElementById('nextPageBtn'); | |
const showingCount = document.getElementById('showingCount'); | |
const totalCount = document.getElementById('totalCount'); | |
// Update profile count display | |
profileRange.addEventListener('input', function() { | |
const count = parseInt(this.value).toLocaleString(); | |
profileCount.textContent = count; | |
}); | |
// Dark mode toggle | |
darkModeToggle.addEventListener('click', function() { | |
document.documentElement.classList.toggle('dark'); | |
const isDark = document.documentElement.classList.contains('dark'); | |
localStorage.setItem('darkMode', isDark); | |
// Update icon | |
const icon = darkModeToggle.querySelector('i'); | |
if (isDark) { | |
icon.classList.remove('fa-moon'); | |
icon.classList.add('fa-sun'); | |
} else { | |
icon.classList.remove('fa-sun'); | |
icon.classList.add('fa-moon'); | |
} | |
}); | |
// Check for saved dark mode preference | |
if (localStorage.getItem('darkMode') === 'true') { | |
document.documentElement.classList.add('dark'); | |
const icon = darkModeToggle.querySelector('i'); | |
icon.classList.remove('fa-moon'); | |
icon.classList.add('fa-sun'); | |
} | |
// Mobile menu toggle | |
mobileMenuBtn.addEventListener('click', function() { | |
mobileMenu.classList.remove('hidden'); | |
setTimeout(() => { | |
mobileMenu.classList.add('open'); | |
}, 10); | |
}); | |
closeMobileMenu.addEventListener('click', function() { | |
mobileMenu.classList.remove('open'); | |
setTimeout(() => { | |
mobileMenu.classList.add('hidden'); | |
}, 300); | |
}); | |
// Close mobile menu when clicking outside | |
mobileMenu.addEventListener('click', function(e) { | |
if (e.target === mobileMenu) { | |
mobileMenu.classList.remove('open'); | |
setTimeout(() => { | |
mobileMenu.classList.add('hidden'); | |
}, 300); | |
} | |
}); | |
// Scrape button handler | |
scrapeBtn.addEventListener('click', function() { | |
// Show spinner and change text | |
scrapeSpinner.classList.remove('hidden'); | |
scrapeBtnText.textContent = 'Scraping...'; | |
scrapeBtn.disabled = true; | |
// Show progress section | |
progressSection.classList.remove('hidden'); | |
// Hide results if shown | |
resultsSection.classList.add('hidden'); | |
// Reset progress | |
let progress = 0; | |
let elapsed = 0; | |
let scraped = 0; | |
const totalProfiles = parseInt(profileRange.value); | |
const successPercent = 98 + Math.random() * 1.3; // Random success rate between 98-99.3% | |
// Show skeleton loaders | |
showSkeletonLoaders(totalProfiles); | |
// Start progress simulation | |
const interval = setInterval(() => { | |
progress += 1; | |
elapsed += 1; | |
scraped = Math.floor(totalProfiles * (progress/100)); | |
// Update progress bar | |
progressFill.style.width = `${progress}%`; | |
progressPercent.textContent = `${progress}%`; | |
profilesScraped.textContent = scraped.toLocaleString(); | |
successRate.textContent = `${successPercent.toFixed(1)}%`; | |
timeElapsed.textContent = `${elapsed}s`; | |
// Calculate remaining time | |
const remaining = Math.max(1, Math.floor((100 - progress) * elapsed / progress)); | |
timeRemaining.textContent = `~${remaining}s`; | |
// When progress completes | |
if (progress >= 100) { | |
clearInterval(interval); | |
// Reset button | |
scrapeSpinner.classList.add('hidden'); | |
scrapeBtnText.textContent = 'Start Scraping'; | |
scrapeBtn.disabled = false; | |
// Show notification | |
notification.classList.remove('hidden'); | |
notification.classList.add('floating-notification'); | |
// Hide notification after 3 seconds | |
setTimeout(() => { | |
notification.classList.add('hidden'); | |
notification.classList.remove('floating-notification'); | |
}, 3000); | |
// Show results after a delay | |
setTimeout(() => { | |
progressSection.classList.add('hidden'); | |
resultsSection.classList.remove('hidden'); | |
populateResults(totalProfiles); | |
}, 1000); | |
} | |
}, 50); // Update every 50ms | |
}); | |
// Show skeleton loaders | |
function showSkeletonLoaders(total) { | |
resultGrid.innerHTML = ''; | |
// Update counts | |
showingCount.textContent = '10'; | |
totalCount.textContent = total.toLocaleString(); | |
// Generate skeleton cards | |
for (let i = 0; i < 10; i++) { | |
const card = document.createElement('div'); | |
card.className = 'profile-card bg-white dark:bg-darkCard p-5 rounded-lg shadow-sm'; | |
card.innerHTML = ` | |
<div class="flex items-start mb-4"> | |
<div class="skeleton w-16 h-16 rounded-full"></div> | |
<div class="ml-4 flex-1"> | |
<div class="skeleton h-5 w-3/4 mb-2"></div> | |
<div class="skeleton h-4 w-1/2"></div> | |
</div> | |
</div> | |
<div class="grid grid-cols-2 gap-3 mb-4"> | |
<div class="flex items-center"> | |
<div class="skeleton w-4 h-4 mr-2"></div> | |
<div class="skeleton h-4 w-3/4"></div> | |
</div> | |
<div class="flex items-center"> | |
<div class="skeleton w-4 h-4 mr-2"></div> | |
<div class="skeleton h-4 w-3/4"></div> | |
</div> | |
<div class="flex items-center"> | |
<div class="skeleton w-4 h-4 mr-2"></div> | |
<div class="skeleton h-4 w-3/4"></div> | |
</div> | |
<div class="flex items-center"> | |
<div class="skeleton w-4 h-4 mr-2"></div> | |
<div class="skeleton h-4 w-3/4"></div> | |
</div> | |
</div> | |
<div class="flex justify-between"> | |
<div class="skeleton h-8 w-24"></div> | |
<div class="skeleton h-8 w-24"></div> | |
</div> | |
`; | |
resultGrid.appendChild(card); | |
} | |
} | |
// Populate results with mock data | |
function populateResults(total) { | |
// Clear existing results | |
resultGrid.innerHTML = ''; | |
// Update counts | |
showingCount.textContent = '10'; | |
totalCount.textContent = total.toLocaleString(); | |
// Generate mock profile cards | |
const titles = [ | |
'Senior Software Engineer', | |
'Marketing Director', | |
'Product Manager', | |
'Data Scientist', | |
'UX Designer', | |
'Sales Executive', | |
'HR Specialist', | |
'Financial Analyst', | |
'Operations Manager', | |
'Content Strategist' | |
]; | |
const companies = [ | |
'Google', 'Microsoft', 'Amazon', 'Apple', 'Meta', | |
'Netflix', 'Adobe', 'Salesforce', 'IBM', 'Intel' | |
]; | |
const locations = [ | |
'San Francisco, CA', 'New York, NY', 'Seattle, WA', | |
'Austin, TX', 'Boston, MA', 'Chicago, IL', 'Los Angeles, CA' | |
]; | |
for (let i = 0; i < 10; i++) { | |
const title = titles[Math.floor(Math.random() * titles.length)]; | |
const company = companies[Math.floor(Math.random() * companies.length)]; | |
const location = locations[Math.floor(Math.random() * locations.length)]; | |
const card = document.createElement('div'); | |
card.className = 'profile-card bg-white dark:bg-darkCard p-5 rounded-lg shadow-sm'; | |
card.innerHTML = ` | |
<div class="flex items-start mb-4"> | |
<div class="bg-gray-200 dark:bg-gray-700 rounded-xl w-16 h-16 flex items-center justify-center"> | |
<i class="fas fa-user text-gray-500 dark:text-gray-400 text-xl"></i> | |
</div> | |
<div class="ml-4"> | |
<h4 class="font-bold text-lg text-gray-800 dark:text-gray-200">${getRandomName()}</h4> | |
<p class="text-gray-600 dark:text-gray-400">${title} at ${company}</p> | |
</div> | |
</div> | |
<div class="grid grid-cols-2 gap-3 mb-4"> | |
<div class="flex items-center"> | |
<i class="fas fa-building text-gray-500 dark:text-gray-400 mr-2"></i> | |
<span class="text-gray-700 dark:text-gray-300">${company}</span> | |
</div> | |
<div class="flex items-center"> | |
<i class="fas fa-map-marker-alt text-gray-500 dark:text-gray-400 mr-2"></i> | |
<span class="text-gray-700 dark:text-gray-300">${location}</span> | |
</div> | |
<div class="flex items-center"> | |
<i class="fas fa-briefcase text-gray-500 dark:text-gray-400 mr-2"></i> | |
<span class="text-gray-700 dark:text-gray-300">${Math.floor(Math.random() * 15) + 3} years exp</span> | |
</div> | |
<div class="flex items-center"> | |
<i class="fas fa-user-friends text-gray-500 dark:text-gray-400 mr-2"></i> | |
<span class="text-gray-700 dark:text-gray-300">${(Math.floor(Math.random() * 500) + 100).toLocaleString()}+ connections</span> | |
</div> | |
</div> | |
<div class="flex justify-between"> | |
<button class="text-primary hover:text-secondary font-medium"> | |
<i class="fas fa-envelope mr-1"></i> Contact | |
</button> | |
<button class="text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200"> | |
<i class="fas fa-save mr-1"></i> Save Profile | |
</button> | |
</div> | |
`; | |
resultGrid.appendChild(card); | |
} | |
} | |
// Generate random names | |
function getRandomName() { | |
const firstNames = ['James', 'Mary', 'John', 'Patricia', 'Robert', 'Jennifer', 'Michael', 'Linda', 'William', 'Elizabeth']; | |
const lastNames = ['Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis', 'Rodriguez', 'Martinez']; | |
const firstName = firstNames[Math.floor(Math.random() * firstNames.length)]; | |
const lastName = lastNames[Math.floor(Math.random() * lastNames.length)]; | |
return `${firstName} ${lastName}`; | |
} | |
// Pagination functionality | |
prevPageBtn.addEventListener('click', function() { | |
// In a real app, this would fetch previous page data | |
alert('Previous page functionality would be implemented here'); | |
}); | |
nextPageBtn.addEventListener('click', function() { | |
// In a real app, this would fetch next page data | |
alert('Next page functionality would be implemented here'); | |
}); | |
</script> | |
</html> |