| <!DOCTYPE html> |
| <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 { |
| 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; |
| } |
| |
| |
| #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> |
|
|
| |
| <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> |
|
|
| |
| <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"); |
| |
| |
| document.getElementById('scanBtn').disabled = true; |
| document.getElementById('progress-section').style.display = 'block'; |
| document.getElementById('result-section').style.display = 'none'; |
| |
| try { |
| |
| 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; |
| |
| |
| 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(); |
| |
| |
| 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); |
| } |
| |
| |
| 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; |
| |
| |
| startScan(); |
| } |
| } |
| </script> |
|
|
| </body> |
|
|
| </html> |