Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Antigravity SEO Scanner</title> | |
| <style> | |
| :root { | |
| --primary: #6366f1; | |
| --primary-dark: #4f46e5; | |
| --bg: #0f172a; | |
| --surface: #1e293b; | |
| --text: #f8fafc; | |
| --text-muted: #94a3b8; | |
| --success: #22c55e; | |
| --error: #ef4444; | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| font-family: 'Inter', system-ui, sans-serif; | |
| } | |
| body { | |
| background-color: var(--bg); | |
| color: var(--text); | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| min-height: 100vh; | |
| padding: 2rem; | |
| } | |
| .container { | |
| width: 100%; | |
| max-width: 800px; | |
| } | |
| header { | |
| margin-bottom: 2rem; | |
| text-align: center; | |
| } | |
| h1 { | |
| font-size: 2.5rem; | |
| font-weight: 800; | |
| background: linear-gradient(135deg, #818cf8, #c084fc); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| margin-bottom: 0.5rem; | |
| } | |
| .card { | |
| background: var(--surface); | |
| border-radius: 1rem; | |
| padding: 2rem; | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); | |
| border: 1px solid rgba(255, 255, 255, 0.05); | |
| margin-bottom: 1.5rem; | |
| } | |
| .input-group { | |
| display: flex; | |
| gap: 1rem; | |
| margin-bottom: 1rem; | |
| } | |
| input { | |
| flex: 1; | |
| padding: 0.75rem 1rem; | |
| border-radius: 0.5rem; | |
| border: 1px solid #334155; | |
| background: #0f172a; | |
| color: white; | |
| font-size: 1rem; | |
| outline: none; | |
| transition: all 0.2s; | |
| } | |
| input:focus { | |
| border-color: var(--primary); | |
| box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2); | |
| } | |
| button { | |
| padding: 0.75rem 1.5rem; | |
| background: var(--primary); | |
| color: white; | |
| border: none; | |
| border-radius: 0.5rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| button:hover { | |
| background: var(--primary-dark); | |
| transform: translateY(-1px); | |
| } | |
| button:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| } | |
| /* Progress Section */ | |
| #progress-section { | |
| display: none; | |
| } | |
| .progress-container { | |
| margin-top: 1rem; | |
| } | |
| .progress-labels { | |
| display: flex; | |
| justify-content: space-between; | |
| margin-bottom: 0.5rem; | |
| font-size: 0.875rem; | |
| color: var(--text-muted); | |
| } | |
| .progress-bar-bg { | |
| width: 100%; | |
| height: 8px; | |
| background: #334155; | |
| border-radius: 4px; | |
| overflow: hidden; | |
| } | |
| .progress-bar { | |
| height: 100%; | |
| background: linear-gradient(90deg, var(--primary), #a855f7); | |
| width: 0%; | |
| transition: width 0.3s ease; | |
| } | |
| .stats-grid { | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| gap: 1rem; | |
| margin-top: 1.5rem; | |
| } | |
| .stat-box { | |
| background: rgba(255, 255, 255, 0.03); | |
| padding: 1rem; | |
| border-radius: 0.5rem; | |
| text-align: center; | |
| } | |
| .stat-value { | |
| font-size: 1.5rem; | |
| font-weight: 700; | |
| margin-bottom: 0.25rem; | |
| } | |
| .stat-label { | |
| font-size: 0.75rem; | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| color: var(--text-muted); | |
| } | |
| .status-msg { | |
| margin-top: 1.5rem; | |
| padding: 1rem; | |
| background: rgba(99, 102, 241, 0.1); | |
| border-left: 3px solid var(--primary); | |
| color: #c7d2fe; | |
| font-family: monospace; | |
| font-size: 0.9rem; | |
| } | |
| /* Results Section */ | |
| #result-section { | |
| display: none; | |
| } | |
| pre { | |
| background: #0f172a; | |
| padding: 1rem; | |
| border-radius: 0.5rem; | |
| overflow-x: auto; | |
| color: #a5b4fc; | |
| font-size: 0.85rem; | |
| } | |
| .json-key { | |
| color: #9cdcfe; | |
| } | |
| .json-string { | |
| color: #ce9178; | |
| } | |
| .json-number { | |
| color: #b5cea8; | |
| } | |
| .json-boolean { | |
| color: #569cd6; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <header> | |
| <h1>SEO Scaler</h1> | |
| <p style="color: var(--text-muted);">Antigravity Async Scanner Interface</p> | |
| </header> | |
| <div class="card"> | |
| <div class="input-group"> | |
| <input type="text" id="domainInput" placeholder="Enter Domain (e.g. https://example.com)" | |
| value="https://example.com"> | |
| <input type="number" id="limitInput" placeholder="Limit" value="25" style="max-width: 100px;"> | |
| <button id="scanBtn" onclick="startScan()">Start Scan</button> | |
| </div> | |
| </div> | |
| <!-- Progress Card --> | |
| <div id="progress-section" class="card"> | |
| <div class="progress-labels"> | |
| <span id="status-badge">Initializing...</span> | |
| <span id="percent-badge">0%</span> | |
| </div> | |
| <div class="progress-bar-bg"> | |
| <div id="progress-fill" class="progress-bar"></div> | |
| </div> | |
| <div class="stats-grid"> | |
| <div class="stat-box"> | |
| <div id="pages-stat" class="stat-value">0</div> | |
| <div class="stat-label">Pages Scanned</div> | |
| </div> | |
| <div class="stat-box"> | |
| <div id="images-stat" class="stat-value">0</div> | |
| <div class="stat-label">Images Found</div> | |
| </div> | |
| <div class="stat-box"> | |
| <div id="time-stat" class="stat-value">0s</div> | |
| <div class="stat-label">Elapsed Time</div> | |
| </div> | |
| </div> | |
| <div id="log-msg" class="status-msg"> | |
| Waiting to start... | |
| </div> | |
| </div> | |
| <!-- Result Card --> | |
| <div id="result-section" class="card"> | |
| <h3 style="margin-bottom: 1rem;">Scan Results</h3> | |
| <pre id="json-output"></pre> | |
| </div> | |
| </div> | |
| <script> | |
| let jobId = null; | |
| let pollInterval = null; | |
| async function startScan() { | |
| const domain = document.getElementById('domainInput').value; | |
| const limit = parseInt(document.getElementById('limitInput').value); | |
| if (!domain) return alert("Please enter a domain"); | |
| // UI Reset | |
| document.getElementById('scanBtn').disabled = true; | |
| document.getElementById('progress-section').style.display = 'block'; | |
| document.getElementById('result-section').style.display = 'none'; | |
| try { | |
| // 1. Start Job | |
| const res = await fetch('/api/scanstart', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ domain, limit }) | |
| }); | |
| const data = await res.json(); | |
| jobId = data.job_id; | |
| // 2. Start Polling | |
| pollInterval = setInterval(pollProgress, 1000); | |
| } catch (e) { | |
| alert("Error starting scan: " + e); | |
| document.getElementById('scanBtn').disabled = false; | |
| } | |
| } | |
| async function pollProgress() { | |
| if (!jobId) return; | |
| try { | |
| const res = await fetch(`/api/progress/${jobId}`); | |
| const data = await res.json(); | |
| // Update UI | |
| document.getElementById('percent-badge').innerText = data.percent + '%'; | |
| document.getElementById('progress-fill').style.width = data.percent + '%'; | |
| document.getElementById('status-badge').innerText = data.status.toUpperCase(); | |
| document.getElementById('pages-stat').innerText = data.pages_scanned; | |
| document.getElementById('images-stat').innerText = data.images_found; | |
| document.getElementById('time-stat').innerText = data.elapsed_seconds + 's'; | |
| document.getElementById('log-msg').innerText = "> " + data.message; | |
| if (data.status === 'done' || data.status === 'error') { | |
| clearInterval(pollInterval); | |
| document.getElementById('scanBtn').disabled = false; | |
| if (data.status === 'done') { | |
| fetchResult(); | |
| } else { | |
| document.getElementById('log-msg').innerHTML += '<br><strong style="color:var(--error)">ERROR: ' + data.error + '</strong>'; | |
| } | |
| } | |
| } catch (e) { | |
| console.error("Poll error", e); | |
| } | |
| } | |
| async function fetchResult() { | |
| const res = await fetch(`/api/result/${jobId}`); | |
| const data = await res.json(); | |
| document.getElementById('result-section').style.display = 'block'; | |
| document.getElementById('json-output').innerText = JSON.stringify(data, null, 2); | |
| } | |
| // Auto-fill from query params | |
| window.onload = () => { | |
| const urlParams = new URLSearchParams(window.location.search); | |
| const domain = urlParams.get('domain'); | |
| const limit = urlParams.get('limit'); | |
| if (domain) { | |
| document.getElementById('domainInput').value = domain; | |
| if (limit) document.getElementById('limitInput').value = limit; | |
| // Auto start | |
| startScan(); | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> |