screenshot-api/landing.html

569 lines
24 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SnapAPI - Screenshots, PDFs & Markdown Conversion</title>
<meta name="description" content="Capture screenshots, generate PDFs, and convert Markdown to beautiful documents via a simple REST API.">
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<meta property="og:title" content="SnapAPI - Screenshots, PDFs & Markdown Conversion">
<meta property="og:description" content="Capture screenshots, generate PDFs, and convert Markdown to beautiful documents via a simple REST API.">
<meta property="og:type" content="website">
<meta property="og:locale" content="en_US">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="SnapAPI - Screenshots, PDFs & Markdown Conversion">
<meta name="twitter:description" content="Capture screenshots, generate PDFs, and convert Markdown to beautiful documents via a simple REST API.">
<style>
*{margin:0;padding:0;box-sizing:border-box}
:root{--bg:#0a0a0f;--surface:#12121a;--border:#1e1e2e;--text:#e4e4e7;--muted:#71717a;--accent:#6366f1;--accent2:#818cf8;--green:#22c55e;--red:#ef4444;--orange:#f59e0b}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:var(--bg);color:var(--text);line-height:1.6;-webkit-font-smoothing:antialiased}
a{color:var(--accent2);text-decoration:none}
a:hover{text-decoration:underline}
.container{max-width:900px;margin:0 auto;padding:0 24px}
header{padding:32px 0 0;border-bottom:1px solid var(--border)}
.hero{text-align:center;padding:48px 0 40px}
.hero h1{font-size:2.5rem;font-weight:700;letter-spacing:-0.03em;margin-bottom:8px}
.hero h1 span{color:var(--accent)}
.hero p{font-size:1.15rem;color:var(--muted);max-width:560px;margin:0 auto 24px}
.badge{display:inline-block;background:var(--surface);border:1px solid var(--border);border-radius:20px;padding:4px 14px;font-size:0.8rem;color:var(--muted);margin:0 4px}
.badge.green{border-color:#22c55e33;color:var(--green)}
nav{display:flex;justify-content:center;gap:8px;padding-bottom:0;flex-wrap:wrap}
nav a{padding:8px 16px;border-radius:6px 6px 0 0;font-size:0.9rem;color:var(--muted);border:1px solid transparent;border-bottom:none;transition:all 0.2s}
nav a:hover{color:var(--text);text-decoration:none;background:var(--surface)}
nav a.active{color:var(--text);background:var(--surface);border-color:var(--border)}
.tab-content{display:none;padding:32px 0 48px}
.tab-content.active{display:block}
section{margin-bottom:40px}
section h2{font-size:1.4rem;font-weight:600;margin-bottom:16px;letter-spacing:-0.02em}
section h3{font-size:1.1rem;font-weight:600;margin-bottom:12px;color:var(--accent2)}
pre{background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:16px 20px;overflow-x:auto;font-family:'SF Mono',Consolas,monospace;font-size:0.85rem;line-height:1.7;margin-bottom:16px}
code{font-family:'SF Mono',Consolas,monospace;font-size:0.85rem}
.inline-code{background:var(--surface);padding:2px 6px;border-radius:4px;border:1px solid var(--border)}
table{width:100%;border-collapse:collapse;margin-bottom:16px;font-size:0.9rem}
th{text-align:left;padding:10px 12px;border-bottom:2px solid var(--border);font-weight:600;color:var(--muted);font-size:0.8rem;text-transform:uppercase;letter-spacing:0.05em}
td{padding:10px 12px;border-bottom:1px solid var(--border)}
td code{color:var(--accent2)}
.method{display:inline-block;background:#6366f122;color:var(--accent2);padding:2px 8px;border-radius:4px;font-weight:600;font-size:0.8rem;font-family:'SF Mono',Consolas,monospace}
.method.post{background:#22c55e22;color:var(--green)}
.try-it{background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:24px;margin-bottom:16px}
.try-it label{display:block;font-size:0.85rem;color:var(--muted);margin-bottom:4px}
.try-it input,.try-it select,.try-it textarea{width:100%;padding:8px 12px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:0.9rem;margin-bottom:12px;font-family:inherit}
.try-it textarea{font-family:'SF Mono',Consolas,monospace;font-size:0.85rem;resize:vertical;line-height:1.5}
.try-it input:focus,.try-it select:focus,.try-it textarea:focus{outline:none;border-color:var(--accent)}
.row{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.row3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px}
button{padding:10px 20px;background:var(--accent);color:#fff;border:none;border-radius:6px;font-size:0.9rem;font-weight:600;cursor:pointer;font-family:inherit;transition:background 0.2s}
button:hover{background:var(--accent2)}
button:disabled{opacity:0.5;cursor:not-allowed}
.result{margin-top:16px;display:none}
.result img{max-width:100%;border:1px solid var(--border);border-radius:8px}
.result .meta{font-size:0.8rem;color:var(--muted);margin-top:8px}
.pricing{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.plan{background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:24px}
.plan h3{margin-bottom:4px}
.plan .price{font-size:1.8rem;font-weight:700;margin-bottom:12px}
.plan .price span{font-size:0.9rem;color:var(--muted);font-weight:400}
.plan ul{list-style:none;font-size:0.9rem}
.plan ul li{padding:4px 0;color:var(--muted)}
.plan ul li::before{content:"+ ";color:var(--green)}
.plan.pro{border-color:var(--accent)}
.status-dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--green);margin-right:6px;animation:pulse 2s infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.5}}
.feature-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:32px}
.feature{background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:20px}
.feature h4{font-size:1rem;margin-bottom:6px}
.feature p{font-size:0.85rem;color:var(--muted)}
footer{text-align:center;padding:32px 0;border-top:1px solid var(--border);color:var(--muted);font-size:0.85rem}
@media(max-width:640px){.hero h1{font-size:1.8rem}.pricing,.row,.row3,.feature-grid{grid-template-columns:1fr}}
</style>
</head>
<body>
<div class="container">
<header>
<div class="hero">
<h1>Snap<span>API</span></h1>
<p>Screenshots, PDFs, and Markdown conversion. One API for all your document generation needs.</p>
<span class="badge green"><span class="status-dot"></span>Online</span>
<span class="badge">v2.0</span>
<span class="badge">PNG / JPEG / PDF</span>
<span class="badge">Markdown</span>
</div>
<nav>
<a href="#" class="active" data-tab="quickstart">Quick Start</a>
<a href="#" data-tab="docs">API Docs</a>
<a href="#" data-tab="try">Try It</a>
<a href="#" data-tab="markdown">Markdown</a>
<a href="#" data-tab="pricing">Pricing</a>
</nav>
</header>
<!-- Quick Start -->
<div id="quickstart" class="tab-content active">
<section>
<h2>Document Generation Suite</h2>
<div class="feature-grid">
<div class="feature">
<h4>URL Screenshots</h4>
<p>Capture any webpage as PNG, JPEG, or PDF. Custom viewports, full-page support.</p>
</div>
<div class="feature">
<h4>Markdown to PDF</h4>
<p>Convert Markdown to beautifully styled PDFs. Light and dark themes, custom margins.</p>
</div>
<div class="feature">
<h4>Markdown to Image</h4>
<p>Render Markdown as PNG or JPEG images. Perfect for social cards and previews.</p>
</div>
<div class="feature">
<h4>Markdown to HTML</h4>
<p>Convert Markdown to styled HTML. Free endpoint, no auth required.</p>
</div>
</div>
<h2>Get started in 30 seconds</h2>
<h3>1. Get your API key</h3>
<p style="color:var(--muted);margin-bottom:16px">Contact us or use the demo key below for testing (100 captures/month).</p>
<h3>2. Capture a screenshot</h3>
<pre>curl "https://YOUR_HOST/screenshot-api/api/capture?url=https://example.com" \
-H "X-API-Key: YOUR_API_KEY" \
-o screenshot.png</pre>
<h3>3. Convert Markdown to PDF</h3>
<pre>curl -X POST "https://YOUR_HOST/screenshot-api/api/md2pdf" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"markdown": "# My Document\n\nHello **world**!"}' \
-o document.pdf</pre>
<h3>Node.js Example</h3>
<pre>// Screenshot
const resp = await fetch(
'https://YOUR_HOST/screenshot-api/api/capture?url=https://example.com',
{ headers: { 'X-API-Key': 'YOUR_API_KEY' } }
);
fs.writeFileSync('screenshot.png', Buffer.from(await resp.arrayBuffer()));
// Markdown to PDF
const pdf = await fetch('https://YOUR_HOST/screenshot-api/api/md2pdf', {
method: 'POST',
headers: { 'X-API-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify({ markdown: '# Hello\n\nWorld!', theme: 'light' })
});
fs.writeFileSync('doc.pdf', Buffer.from(await pdf.arrayBuffer()));</pre>
<h3>Python Example</h3>
<pre>import requests
# Screenshot
resp = requests.get(
'https://YOUR_HOST/screenshot-api/api/capture',
params={'url': 'https://example.com'},
headers={'X-API-Key': 'YOUR_API_KEY'}
)
with open('screenshot.png', 'wb') as f:
f.write(resp.content)
# Markdown to PDF
resp = requests.post(
'https://YOUR_HOST/screenshot-api/api/md2pdf',
headers={'X-API-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json'},
json={'markdown': '# Hello\n\nWorld!', 'theme': 'light'}
)
with open('doc.pdf', 'wb') as f:
f.write(resp.content)</pre>
</section>
</div>
<!-- API Docs -->
<div id="docs" class="tab-content">
<section>
<h2>Endpoints</h2>
<h3><span class="method">GET</span> /api/capture</h3>
<p style="color:var(--muted);margin-bottom:16px">Capture a screenshot or PDF from a URL.</p>
<table>
<tr><th>Parameter</th><th>Type</th><th>Description</th></tr>
<tr><td><code>url</code></td><td>string</td><td><strong>Required.</strong> The URL to capture.</td></tr>
<tr><td><code>format</code></td><td>string</td><td><code>png</code> (default), <code>jpeg</code>, or <code>pdf</code></td></tr>
<tr><td><code>width</code></td><td>int</td><td>Viewport width, 320-3840. Default: 1280</td></tr>
<tr><td><code>height</code></td><td>int</td><td>Viewport height, 200-2160. Default: 800</td></tr>
<tr><td><code>fullPage</code></td><td>bool</td><td>Capture the full scrollable page</td></tr>
<tr><td><code>delay</code></td><td>int</td><td>Wait ms after page load (max 10000)</td></tr>
<tr><td><code>quality</code></td><td>int</td><td>JPEG quality, 1-100. Default: 80</td></tr>
<tr><td><code>paperSize</code></td><td>string</td><td>PDF paper size: A4, Letter, Legal, etc.</td></tr>
<tr><td><code>landscape</code></td><td>bool</td><td>PDF landscape orientation</td></tr>
<tr><td><code>userAgent</code></td><td>string</td><td>Custom User-Agent header</td></tr>
<tr><td><code>timeout</code></td><td>int</td><td>Navigation timeout ms (5000-60000)</td></tr>
</table>
<h3><span class="method post">POST</span> /api/md2pdf</h3>
<p style="color:var(--muted);margin-bottom:16px">Convert Markdown to a styled PDF document. Requires authentication.</p>
<table>
<tr><th>Body Field</th><th>Type</th><th>Description</th></tr>
<tr><td><code>markdown</code></td><td>string</td><td><strong>Required.</strong> Markdown content to convert.</td></tr>
<tr><td><code>theme</code></td><td>string</td><td><code>light</code> (default) or <code>dark</code></td></tr>
<tr><td><code>paperSize</code></td><td>string</td><td>A4 (default), Letter, Legal, Tabloid</td></tr>
<tr><td><code>landscape</code></td><td>bool</td><td>Landscape orientation. Default: false</td></tr>
<tr><td><code>fontSize</code></td><td>string</td><td>Base font size. Default: "16px"</td></tr>
<tr><td><code>margins</code></td><td>object</td><td><code>{ top, bottom, left, right }</code> in CSS units</td></tr>
</table>
<h3><span class="method post">POST</span> /api/md2png</h3>
<p style="color:var(--muted);margin-bottom:16px">Convert Markdown to a PNG or JPEG image. Requires authentication.</p>
<table>
<tr><th>Body Field</th><th>Type</th><th>Description</th></tr>
<tr><td><code>markdown</code></td><td>string</td><td><strong>Required.</strong> Markdown content to convert.</td></tr>
<tr><td><code>theme</code></td><td>string</td><td><code>light</code> (default) or <code>dark</code></td></tr>
<tr><td><code>format</code></td><td>string</td><td><code>png</code> (default) or <code>jpeg</code></td></tr>
<tr><td><code>width</code></td><td>int</td><td>Image width, 320-3840. Default: 1280</td></tr>
<tr><td><code>fontSize</code></td><td>string</td><td>Base font size. Default: "16px"</td></tr>
<tr><td><code>quality</code></td><td>int</td><td>JPEG quality, 1-100. Default: 85</td></tr>
</table>
<h3><span class="method post">POST</span> /api/md2html</h3>
<p style="color:var(--muted);margin-bottom:16px">Convert Markdown to styled HTML. <strong>No authentication required.</strong></p>
<table>
<tr><th>Body Field</th><th>Type</th><th>Description</th></tr>
<tr><td><code>markdown</code></td><td>string</td><td><strong>Required.</strong> Markdown content to convert.</td></tr>
<tr><td><code>theme</code></td><td>string</td><td><code>light</code> (default) or <code>dark</code></td></tr>
<tr><td><code>fontSize</code></td><td>string</td><td>Base font size. Default: "16px"</td></tr>
<tr><td><code>width</code></td><td>string</td><td>Max content width. Default: "800px"</td></tr>
</table>
<h3>Authentication</h3>
<p style="color:var(--muted);margin-bottom:16px">Pass your API key via the <code class="inline-code">X-API-Key</code> header or the <code class="inline-code">api_key</code> query parameter. The <code class="inline-code">/api/md2html</code> endpoint does not require authentication.</p>
<h3>Error Responses</h3>
<table>
<tr><th>Status</th><th>Meaning</th></tr>
<tr><td>400</td><td>Missing/invalid parameters, body too large (max 1MB)</td></tr>
<tr><td>401</td><td>Missing or invalid API key</td></tr>
<tr><td>429</td><td>Rate limit or monthly limit exceeded</td></tr>
<tr><td>500</td><td>Conversion failed</td></tr>
<tr><td>503</td><td>Server busy, max concurrent tasks reached</td></tr>
</table>
<h3><span class="method">GET</span> /api/status</h3>
<p style="color:var(--muted)">Service health and statistics. No authentication required.</p>
</section>
</div>
<!-- Try It - Screenshot -->
<div id="try" class="tab-content">
<section>
<h2>Try Screenshot Capture</h2>
<div class="try-it">
<label for="try-url">URL to capture</label>
<input type="url" id="try-url" placeholder="https://example.com" value="https://example.com">
<div class="row">
<div>
<label for="try-format">Format</label>
<select id="try-format">
<option value="png">PNG</option>
<option value="jpeg">JPEG</option>
<option value="pdf">PDF</option>
</select>
</div>
<div>
<label for="try-width">Width</label>
<input type="number" id="try-width" value="1280" min="320" max="3840">
</div>
</div>
<div class="row">
<div>
<label for="try-height">Height</label>
<input type="number" id="try-height" value="800" min="200" max="2160">
</div>
<div>
<label for="try-fullpage">Full Page</label>
<select id="try-fullpage">
<option value="false">No</option>
<option value="true">Yes</option>
</select>
</div>
</div>
<label for="try-key">API Key</label>
<input type="text" id="try-key" placeholder="Enter your API key">
<button id="try-btn" onclick="tryCapture()">Capture</button>
<div class="result" id="try-result"></div>
</div>
</section>
</div>
<!-- Markdown -->
<div id="markdown" class="tab-content">
<section>
<h2>Try Markdown Conversion</h2>
<div class="try-it">
<label for="md-input">Markdown</label>
<textarea id="md-input" rows="12"># Project Report
**Author**: Engineering Team
**Date**: February 2026
## Summary
This is a sample document demonstrating the **Markdown to PDF** conversion API.
### Features
- Full [GitHub Flavored Markdown](https://github.github.com/gfm/) support
- Light and dark themes
- Tables, code blocks, and more
| Feature | Status |
|---------|--------|
| Headers | Done |
| Tables | Done |
| Code | Done |
| Lists | Done |
### Code Example
```javascript
const response = await fetch('/api/md2pdf', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ markdown: '# Hello' })
});
```
---
> This PDF was generated by SnapAPI's Markdown conversion endpoint.
</textarea>
<div class="row3">
<div>
<label for="md-format">Output Format</label>
<select id="md-format">
<option value="pdf">PDF</option>
<option value="png">PNG Image</option>
<option value="html">HTML (free)</option>
</select>
</div>
<div>
<label for="md-theme">Theme</label>
<select id="md-theme">
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</div>
<div>
<label for="md-paper">Paper Size</label>
<select id="md-paper">
<option value="A4">A4</option>
<option value="Letter">Letter</option>
<option value="Legal">Legal</option>
</select>
</div>
</div>
<label for="md-key">API Key <span style="color:var(--muted);font-weight:400">(not needed for HTML)</span></label>
<input type="text" id="md-key" placeholder="Enter your API key">
<button id="md-btn" onclick="tryMarkdown()">Convert</button>
<div class="result" id="md-result"></div>
</div>
</section>
</div>
<!-- Pricing -->
<div id="pricing" class="tab-content">
<section>
<h2>Pricing</h2>
<div class="pricing">
<div class="plan">
<h3>Free</h3>
<div class="price">$0<span>/mo</span></div>
<ul>
<li>100 captures / month</li>
<li>Screenshots: PNG, JPEG, PDF</li>
<li>Markdown: PDF, PNG, HTML</li>
<li>10 requests / minute</li>
<li>Light &amp; dark themes</li>
<li>md2html: unlimited, no auth</li>
</ul>
</div>
<div class="plan pro">
<h3>Pro</h3>
<div class="price">$19<span>/mo</span></div>
<ul>
<li>5,000 captures / month</li>
<li>Screenshots: PNG, JPEG, PDF</li>
<li>Markdown: PDF, PNG, HTML</li>
<li>60 requests / minute</li>
<li>Custom viewport up to 4K</li>
<li>Custom margins &amp; paper sizes</li>
<li>Priority support</li>
</ul>
</div>
</div>
<p style="color:var(--muted);margin-top:16px;font-size:0.9rem">Contact us to get started with a Pro plan or discuss enterprise needs.</p>
</section>
</div>
<footer>
<p>Built by an AI Agent (Claude) &mdash; transparent about AI authorship.</p>
<p style="margin-top:4px">SnapAPI v2.0 &middot; <a href="/api">JSON API Docs</a> &middot; <a href="/api/status">Status</a></p>
</footer>
</div>
<script>
// Tab navigation
document.querySelectorAll('nav a').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
document.querySelectorAll('nav a').forEach(l => l.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
link.classList.add('active');
document.getElementById(link.dataset.tab).classList.add('active');
});
});
// Screenshot try-it
async function tryCapture() {
const btn = document.getElementById('try-btn');
const result = document.getElementById('try-result');
const url = document.getElementById('try-url').value;
const format = document.getElementById('try-format').value;
const width = document.getElementById('try-width').value;
const height = document.getElementById('try-height').value;
const fullPage = document.getElementById('try-fullpage').value;
const apiKey = document.getElementById('try-key').value;
if (!url) { alert('Please enter a URL'); return; }
if (!apiKey) { alert('Please enter an API key'); return; }
btn.disabled = true;
btn.textContent = 'Capturing...';
result.style.display = 'none';
try {
const params = new URLSearchParams({ url, format, width, height, fullPage });
const resp = await fetch(`/api/capture?${params}`, {
headers: { 'X-API-Key': apiKey }
});
if (!resp.ok) {
const err = await resp.json();
result.innerHTML = `<pre style="color:var(--red)">${JSON.stringify(err, null, 2)}</pre>`;
result.style.display = 'block';
return;
}
const blob = await resp.blob();
const used = resp.headers.get('X-Captures-Used');
const limit = resp.headers.get('X-Captures-Limit');
if (format === 'pdf') {
const pdfUrl = URL.createObjectURL(blob);
result.innerHTML = `<a href="${pdfUrl}" target="_blank">Download PDF (${(blob.size/1024).toFixed(1)} KB)</a>
<div class="meta">Captures used: ${used}/${limit}</div>`;
} else {
const imgUrl = URL.createObjectURL(blob);
result.innerHTML = `<img src="${imgUrl}" alt="Screenshot">
<div class="meta">${(blob.size/1024).toFixed(1)} KB &middot; Captures: ${used}/${limit}</div>`;
}
result.style.display = 'block';
} catch (e) {
result.innerHTML = `<pre style="color:var(--red)">Error: ${e.message}</pre>`;
result.style.display = 'block';
} finally {
btn.disabled = false;
btn.textContent = 'Capture';
}
}
// Markdown try-it
async function tryMarkdown() {
const btn = document.getElementById('md-btn');
const result = document.getElementById('md-result');
const markdown = document.getElementById('md-input').value;
const format = document.getElementById('md-format').value;
const theme = document.getElementById('md-theme').value;
const paperSize = document.getElementById('md-paper').value;
const apiKey = document.getElementById('md-key').value;
if (!markdown.trim()) { alert('Please enter some Markdown'); return; }
if (format !== 'html' && !apiKey) { alert('API key required for PDF/PNG output'); return; }
btn.disabled = true;
btn.textContent = 'Converting...';
result.style.display = 'none';
try {
const endpoint = format === 'html' ? '/api/md2html'
: format === 'png' ? '/api/md2png' : '/api/md2pdf';
const body = { markdown, theme };
if (format === 'pdf') body.paperSize = paperSize;
const headers = { 'Content-Type': 'application/json' };
if (format !== 'html' && apiKey) headers['X-API-Key'] = apiKey;
const resp = await fetch(endpoint, {
method: 'POST',
headers,
body: JSON.stringify(body)
});
if (!resp.ok) {
const err = await resp.json();
result.innerHTML = `<pre style="color:var(--red)">${JSON.stringify(err, null, 2)}</pre>`;
result.style.display = 'block';
return;
}
if (format === 'html') {
const html = await resp.text();
const iframe = document.createElement('iframe');
iframe.style.cssText = 'width:100%;height:500px;border:1px solid var(--border);border-radius:8px;background:#fff';
result.innerHTML = '';
result.appendChild(iframe);
iframe.contentDocument.open();
iframe.contentDocument.write(html);
iframe.contentDocument.close();
result.innerHTML += `<div class="meta">${(html.length/1024).toFixed(1)} KB HTML</div>`;
} else if (format === 'pdf') {
const blob = await resp.blob();
const pdfUrl = URL.createObjectURL(blob);
result.innerHTML = `<iframe src="${pdfUrl}" style="width:100%;height:500px;border:1px solid var(--border);border-radius:8px"></iframe>
<div class="meta"><a href="${pdfUrl}" target="_blank">Download PDF</a> (${(blob.size/1024).toFixed(1)} KB) &middot; Captures: ${resp.headers.get('X-Captures-Used')}/${resp.headers.get('X-Captures-Limit')}</div>`;
} else {
const blob = await resp.blob();
const imgUrl = URL.createObjectURL(blob);
result.innerHTML = `<img src="${imgUrl}" alt="Markdown render">
<div class="meta">${(blob.size/1024).toFixed(1)} KB &middot; Captures: ${resp.headers.get('X-Captures-Used')}/${resp.headers.get('X-Captures-Limit')}</div>`;
}
result.style.display = 'block';
} catch (e) {
result.innerHTML = `<pre style="color:var(--red)">Error: ${e.message}</pre>`;
result.style.display = 'block';
} finally {
btn.disabled = false;
btn.textContent = 'Convert';
}
}
</script>
</body>
</html>