feat: add newsletter signup section to landing page

Adds a Listmonk-powered newsletter form at the bottom of the rSpace
landing page, matching the dark theme and gradient styling of existing
sections. Uses rSpace list UUID for subscriber routing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-02-21 17:36:17 -07:00
parent 91d600ed93
commit 095d6e1eb9
1 changed files with 124 additions and 0 deletions

View File

@ -285,7 +285,66 @@
border-color: rgba(124, 58, 237, 0.6);
}
/* Newsletter */
.newsletter-form {
max-width: 440px;
margin: 0 auto;
}
.newsletter-row {
display: flex;
gap: 0.75rem;
}
.newsletter-input {
flex: 1;
padding: 12px 16px;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.15);
background: rgba(255, 255, 255, 0.05);
color: white;
font-size: 1rem;
outline: none;
transition: border-color 0.2s;
}
.newsletter-input:focus {
border-color: #14b8a6;
}
.newsletter-input::placeholder {
color: #64748b;
}
.newsletter-btn {
padding: 12px 24px;
white-space: nowrap;
}
.newsletter-status {
font-size: 0.875rem;
margin-top: 0.75rem;
min-height: 1.25em;
}
.newsletter-status.success {
color: #22c55e;
}
.newsletter-status.error {
color: #ef4444;
}
.newsletter-privacy {
font-size: 0.8rem;
color: #64748b;
margin-top: 0.5rem;
}
@media (max-width: 600px) {
.newsletter-row {
flex-direction: column;
}
.pillars {
grid-template-columns: 1fr;
}
@ -517,6 +576,29 @@
</a>
</div>
<div class="section">
<div class="section-divider"></div>
<h2>Stay Connected</h2>
<p class="section-subtitle">
Get updates on rSpace development, new ecosystem modules, and community features.
</p>
<form class="newsletter-form" id="newsletter-form">
<div class="newsletter-row">
<input
type="email"
id="newsletter-email"
placeholder="your@email.com"
required
class="newsletter-input"
/>
<button type="submit" class="newsletter-btn" id="newsletter-btn">Subscribe</button>
</div>
<p class="newsletter-status" id="newsletter-status"></p>
<p class="newsletter-privacy">No spam, unsubscribe anytime.</p>
</form>
</div>
<script type="module">
import { RStackIdentity } from "@shared/components/rstack-identity";
import { RStackAppSwitcher } from "@shared/components/rstack-app-switcher";
@ -529,6 +611,48 @@
fetch("/api/modules").then(r => r.json()).then(data => {
document.querySelector("rstack-app-switcher")?.setModules(data.modules || []);
}).catch(() => {});
// Newsletter signup
const newsletterForm = document.getElementById("newsletter-form");
const newsletterEmail = document.getElementById("newsletter-email");
const newsletterBtn = document.getElementById("newsletter-btn");
const newsletterStatus = document.getElementById("newsletter-status");
newsletterForm.addEventListener("submit", async (e) => {
e.preventDefault();
const email = newsletterEmail.value.trim();
if (!email) return;
newsletterBtn.disabled = true;
newsletterBtn.textContent = "Subscribing...";
newsletterStatus.textContent = "";
newsletterStatus.className = "newsletter-status";
try {
const res = await fetch("https://newsletter.jeffemmett.com/subscribe", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email,
list_uuid: "2d247234-34cf-4ee6-858f-2b5e24e2e5dc",
}),
});
if (res.ok) {
newsletterStatus.textContent = "Welcome to the network. The space expands.";
newsletterStatus.className = "newsletter-status success";
newsletterEmail.value = "";
} else {
throw new Error("Subscription failed");
}
} catch {
newsletterStatus.textContent = "Something went wrong. Please try again.";
newsletterStatus.className = "newsletter-status error";
} finally {
newsletterBtn.disabled = false;
newsletterBtn.textContent = "Subscribe";
}
});
</script>
</body>
</html>