diff --git a/public/app.js b/public/app.js
new file mode 100644
index 0000000..886d72f
--- /dev/null
+++ b/public/app.js
@@ -0,0 +1,83 @@
+function openSignup() {
+ document.getElementById('signupModal').classList.add('active');
+ document.getElementById('signupForm').style.display = 'block';
+ document.getElementById('keyResult').style.display = 'none';
+ document.getElementById('signupEmail').value = '';
+ document.getElementById('signupError').style.display = 'none';
+ setTimeout(function() { document.getElementById('signupEmail').focus(); }, 100);
+}
+
+function closeSignup() {
+ document.getElementById('signupModal').classList.remove('active');
+}
+
+// Close on overlay click
+document.getElementById('signupModal').addEventListener('click', function(e) {
+ if (e.target === this) closeSignup();
+});
+
+// Submit on Enter
+document.getElementById('signupEmail').addEventListener('keydown', function(e) {
+ if (e.key === 'Enter') submitSignup();
+});
+
+async function submitSignup() {
+ var email = document.getElementById('signupEmail').value.trim();
+ var errEl = document.getElementById('signupError');
+ var btn = document.getElementById('signupBtn');
+
+ if (!email) {
+ errEl.textContent = 'Please enter your email.';
+ errEl.style.display = 'block';
+ return;
+ }
+
+ btn.textContent = 'Creating...';
+ btn.disabled = true;
+
+ try {
+ var res = await fetch('/v1/signup/free', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ email: email })
+ });
+ var data = await res.json();
+
+ if (!res.ok) {
+ errEl.textContent = data.error || 'Something went wrong.';
+ errEl.style.display = 'block';
+ btn.textContent = 'Get API Key';
+ btn.disabled = false;
+ return;
+ }
+
+ // Show key
+ document.getElementById('signupForm').style.display = 'none';
+ document.getElementById('keyResult').style.display = 'block';
+ document.getElementById('apiKeyDisplay').textContent = data.apiKey;
+ } catch (err) {
+ errEl.textContent = 'Network error. Please try again.';
+ errEl.style.display = 'block';
+ btn.textContent = 'Get API Key';
+ btn.disabled = false;
+ }
+}
+
+function copyKey() {
+ var key = document.getElementById('apiKeyDisplay').textContent;
+ navigator.clipboard.writeText(key).then(function() {
+ document.querySelector('.copy-hint').textContent = '✓ Copied!';
+ setTimeout(function() { document.querySelector('.copy-hint').textContent = 'Click to copy'; }, 2000);
+ });
+}
+
+async function checkout() {
+ try {
+ var res = await fetch('/v1/billing/checkout', { method: 'POST' });
+ var data = await res.json();
+ if (data.url) window.location.href = data.url;
+ else alert('Something went wrong. Please try again.');
+ } catch (err) {
+ alert('Something went wrong. Please try again.');
+ }
+}
diff --git a/public/index.html b/public/index.html
index b7fd8fc..8dbfadc 100644
--- a/public/index.html
+++ b/public/index.html
@@ -238,90 +238,6 @@ footer { padding: 40px 0; text-align: center; color: var(--muted); font-size: 0.
-
+