Podcast — Newsletter Signup
A bold, audio-first newsletter signup section for a podcast, built with a glowing dark card, gradient waveform accents, and an email capture form. Shows a live subscriber count, animated waveform bars, trust badges and recent-subscriber avatars, then flips into a celebratory success state with confetti-style ticks after a validated submit. Fully keyboard-accessible, responsive down to 360px, and driven entirely by vanilla JS.
MCP
Code
:root {
--bg: #0c0c10;
--surface: #15151c;
--surface-2: #1d1d27;
--violet: #8b5cf6;
--violet-d: #7c3aed;
--cyan: #22d3ee;
--pink: #f472b6;
--ink: #f4f4f8;
--muted: #9a9aab;
--line: rgba(255, 255, 255, 0.1);
--line-2: rgba(255, 255, 255, 0.18);
--white: #fff;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
}
* {
box-sizing: border-box;
}
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
margin: 0;
min-height: 100vh;
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
color: var(--ink);
background:
radial-gradient(60% 55% at 15% 0%, rgba(139, 92, 246, 0.22), transparent 60%),
radial-gradient(55% 50% at 90% 100%, rgba(34, 211, 238, 0.16), transparent 60%),
var(--bg);
display: grid;
place-items: center;
padding: 32px 16px;
}
.wrap {
width: 100%;
max-width: 560px;
}
/* Card */
.card {
position: relative;
overflow: hidden;
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.04), transparent 40%),
var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 34px 32px 30px;
box-shadow:
0 30px 70px -30px rgba(0, 0, 0, 0.8),
0 0 0 1px rgba(255, 255, 255, 0.02) inset;
}
.card::before {
content: "";
position: absolute;
inset: 0 0 auto 0;
height: 3px;
background: linear-gradient(90deg, var(--violet), var(--cyan), var(--pink));
opacity: 0.9;
}
.eyebrow {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--muted);
background: var(--surface-2);
border: 1px solid var(--line);
padding: 6px 12px;
border-radius: 999px;
}
.eyebrow .dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--cyan);
box-shadow: 0 0 0 4px rgba(34, 211, 238, 0.18);
animation: pulse 1.8s ease-in-out infinite;
}
@keyframes pulse {
50% {
opacity: 0.45;
transform: scale(0.8);
}
}
/* Waveform */
.wave {
display: flex;
align-items: center;
gap: 3px;
height: 44px;
margin: 22px 0 20px;
}
.wave span {
flex: 1;
min-width: 2px;
border-radius: 999px;
background: linear-gradient(180deg, var(--violet), var(--cyan));
transform-origin: center;
animation: eq 1.1s ease-in-out infinite;
opacity: 0.85;
}
.wave span:nth-child(3n) {
background: linear-gradient(180deg, var(--cyan), var(--pink));
}
.wave span:nth-child(2n) {
animation-duration: 1.5s;
}
.wave span:nth-child(4n) {
animation-duration: 0.9s;
}
.wave span:nth-child(5n) {
animation-duration: 1.7s;
}
@keyframes eq {
0%,
100% {
transform: scaleY(0.22);
}
50% {
transform: scaleY(1);
}
}
h1 {
margin: 0 0 10px;
font-size: clamp(26px, 6vw, 36px);
font-weight: 800;
letter-spacing: -0.02em;
line-height: 1.12;
}
.grad {
background: linear-gradient(100deg, var(--violet), var(--cyan) 60%, var(--pink));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.lede {
margin: 0 0 22px;
color: var(--muted);
font-size: 15px;
max-width: 46ch;
}
/* Form */
.form {
display: flex;
gap: 10px;
}
.field {
position: relative;
flex: 1;
}
.mail-ico {
position: absolute;
left: 14px;
top: 50%;
transform: translateY(-50%);
color: var(--muted);
font-size: 15px;
pointer-events: none;
}
.field input {
width: 100%;
height: 50px;
padding: 0 14px 0 38px;
font: inherit;
font-size: 15px;
color: var(--ink);
background: var(--surface-2);
border: 1px solid var(--line-2);
border-radius: var(--r-md);
transition: border-color 0.18s, box-shadow 0.18s, background 0.18s;
}
.field input::placeholder {
color: #6b6b7c;
}
.field input:hover {
border-color: rgba(255, 255, 255, 0.28);
}
.field input:focus-visible {
outline: none;
border-color: var(--violet);
background: #20202c;
box-shadow: 0 0 0 4px rgba(139, 92, 246, 0.28);
}
.card.invalid .field input {
border-color: var(--pink);
box-shadow: 0 0 0 4px rgba(244, 114, 182, 0.22);
animation: shake 0.4s ease;
}
@keyframes shake {
25% {
transform: translateX(-6px);
}
50% {
transform: translateX(5px);
}
75% {
transform: translateX(-3px);
}
}
.btn {
position: relative;
height: 50px;
padding: 0 22px;
font: inherit;
font-weight: 700;
font-size: 15px;
color: var(--white);
white-space: nowrap;
cursor: pointer;
border: 0;
border-radius: var(--r-md);
background: linear-gradient(135deg, var(--violet), var(--violet-d));
box-shadow:
0 8px 22px -8px rgba(139, 92, 246, 0.9),
0 0 0 1px rgba(255, 255, 255, 0.08) inset;
transition: transform 0.14s, box-shadow 0.18s, filter 0.18s;
}
.btn:hover {
transform: translateY(-1px);
filter: brightness(1.08);
box-shadow:
0 12px 28px -8px rgba(139, 92, 246, 1),
0 0 0 1px rgba(255, 255, 255, 0.12) inset;
}
.btn:active {
transform: translateY(0);
}
.btn:focus-visible {
outline: none;
box-shadow:
0 0 0 3px var(--bg),
0 0 0 6px var(--cyan);
}
.spinner {
display: none;
position: absolute;
inset: 0;
margin: auto;
width: 18px;
height: 18px;
border: 2px solid rgba(255, 255, 255, 0.35);
border-top-color: var(--white);
border-radius: 50%;
animation: spin 0.7s linear infinite;
}
.btn.loading .btn-label {
opacity: 0;
}
.btn.loading .spinner {
display: block;
}
.btn.loading {
cursor: progress;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.msg {
margin: 10px 2px 0;
min-height: 18px;
font-size: 13px;
font-weight: 500;
color: var(--pink);
}
/* Social proof */
.proof {
display: flex;
align-items: center;
gap: 12px;
margin-top: 22px;
padding-top: 20px;
border-top: 1px solid var(--line);
}
.avatars {
display: flex;
}
.avatars span {
display: grid;
place-items: center;
width: 34px;
height: 34px;
margin-left: -10px;
border-radius: 50%;
font-size: 11px;
font-weight: 700;
color: var(--white);
background: var(--g);
border: 2px solid var(--surface);
}
.avatars span:first-child {
margin-left: 0;
}
.proof-text {
margin: 0;
font-size: 14px;
color: var(--muted);
}
.proof-text strong {
color: var(--ink);
}
.stars {
display: block;
margin-top: 2px;
font-size: 12px;
font-weight: 600;
color: #fbbf24;
letter-spacing: 0.04em;
}
/* Badges */
.badges {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 18px 0 0;
padding: 0;
list-style: none;
}
.badges li {
font-size: 12px;
font-weight: 600;
color: var(--muted);
background: var(--surface-2);
border: 1px solid var(--line);
border-radius: 999px;
padding: 5px 11px;
}
.badges li::before {
content: "✓ ";
color: var(--cyan);
}
/* Success state */
.success {
position: absolute;
inset: 0;
z-index: 3;
display: grid;
place-content: center;
text-align: center;
gap: 6px;
padding: 30px;
background:
radial-gradient(70% 60% at 50% 30%, rgba(139, 92, 246, 0.28), transparent 65%),
var(--surface);
animation: fadeUp 0.4s ease;
}
.success[hidden] {
display: none;
}
@keyframes fadeUp {
from {
opacity: 0;
transform: translateY(10px);
}
}
.check {
position: relative;
justify-self: center;
margin-bottom: 10px;
}
.check svg circle {
stroke: var(--cyan);
stroke-width: 3;
stroke-dasharray: 151;
stroke-dashoffset: 151;
animation: draw 0.5s ease forwards;
}
.check svg path {
stroke: var(--white);
stroke-width: 4;
stroke-linecap: round;
stroke-linejoin: round;
stroke-dasharray: 40;
stroke-dashoffset: 40;
animation: draw 0.35s 0.35s ease forwards;
}
@keyframes draw {
to {
stroke-dashoffset: 0;
}
}
.spark {
position: absolute;
top: 50%;
left: 50%;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--pink);
opacity: 0;
animation: spark 0.6s 0.4s ease-out forwards;
}
.spark.s1 {
--x: -34px;
--y: -24px;
background: var(--violet);
}
.spark.s2 {
--x: 36px;
--y: -20px;
background: var(--cyan);
}
.spark.s3 {
--x: -30px;
--y: 26px;
background: var(--pink);
}
.spark.s4 {
--x: 32px;
--y: 28px;
background: var(--cyan);
}
@keyframes spark {
0% {
opacity: 1;
transform: translate(-50%, -50%) scale(0.4);
}
100% {
opacity: 0;
transform: translate(calc(-50% + var(--x)), calc(-50% + var(--y))) scale(1);
}
}
.success h2 {
margin: 0;
font-size: 24px;
font-weight: 800;
letter-spacing: -0.01em;
}
.success-sub {
margin: 0;
color: var(--muted);
font-size: 14px;
max-width: 34ch;
}
.ghost {
margin-top: 12px;
justify-self: center;
font: inherit;
font-weight: 600;
font-size: 14px;
color: var(--ink);
cursor: pointer;
background: var(--surface-2);
border: 1px solid var(--line-2);
border-radius: var(--r-md);
padding: 10px 18px;
transition: border-color 0.16s, background 0.16s;
}
.ghost:hover {
border-color: var(--violet);
background: #20202c;
}
.ghost:focus-visible {
outline: none;
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.4);
}
/* Toast */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translate(-50%, 120%);
max-width: 90vw;
padding: 12px 18px;
font-size: 14px;
font-weight: 600;
color: var(--ink);
background: var(--surface-2);
border: 1px solid var(--line-2);
border-radius: var(--r-md);
box-shadow: 0 18px 40px -16px rgba(0, 0, 0, 0.9);
opacity: 0;
transition: transform 0.35s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.35s;
z-index: 20;
}
.toast.show {
transform: translate(-50%, 0);
opacity: 1;
}
.toast::before {
content: "♪ ";
color: var(--cyan);
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
/* Responsive */
@media (max-width: 520px) {
.card {
padding: 28px 20px 24px;
}
.form {
flex-direction: column;
}
.btn {
width: 100%;
}
.proof {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
}
}(function () {
"use strict";
var form = document.getElementById("signup");
var input = document.getElementById("email");
var msg = document.getElementById("msg");
var btn = document.getElementById("submit");
var card = document.querySelector(".card");
var success = document.getElementById("success");
var successSub = document.getElementById("success-sub");
var resetBtn = document.getElementById("reset");
var countEl = document.getElementById("count");
var toastEl = document.getElementById("toast");
var BASE_COUNT = 18420;
var current = 0;
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
// Toast helper
var toastTimer;
function toast(text) {
toastEl.textContent = text;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("show");
}, 2800);
}
// Animate the subscriber count up to a target
function animateCount(to, duration) {
var from = current;
var start = null;
var diff = to - from;
function step(ts) {
if (start === null) start = ts;
var p = Math.min((ts - start) / duration, 1);
var eased = 1 - Math.pow(1 - p, 3);
current = Math.round(from + diff * eased);
countEl.textContent = current.toLocaleString("en-US");
if (p < 1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
// Count up on load
animateCount(BASE_COUNT, 1400);
function clearError() {
card.classList.remove("invalid");
msg.textContent = "";
msg.style.color = "";
}
input.addEventListener("input", function () {
if (card.classList.contains("invalid")) clearError();
});
form.addEventListener("submit", function (e) {
e.preventDefault();
if (btn.classList.contains("loading")) return;
var value = input.value.trim();
if (!value) {
showError("Please enter your email to subscribe.");
return;
}
if (!EMAIL_RE.test(value)) {
showError("That email looks off — double-check it.");
return;
}
clearError();
btn.classList.add("loading");
btn.setAttribute("aria-busy", "true");
// Simulated async subscribe
setTimeout(function () {
btn.classList.remove("loading");
btn.removeAttribute("aria-busy");
var name = value.split("@")[0];
successSub.textContent =
"Confirmation sent to " + value + ". Your first Dispatch lands this Friday, " + name + ".";
success.hidden = false;
success.setAttribute("role", "status");
animateCount(current + 1, 500);
toast("You're subscribed — welcome aboard!");
}, 900);
});
function showError(text) {
card.classList.add("invalid");
msg.style.color = "var(--pink)";
msg.textContent = text;
input.focus();
// Retrigger shake animation
var el = input;
el.style.animation = "none";
void el.offsetWidth;
el.style.animation = "";
toast(text);
}
resetBtn.addEventListener("click", function () {
success.hidden = true;
form.reset();
clearError();
input.focus();
toast("Ready for another email.");
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Frequency Drift — Newsletter Signup</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="wrap">
<section class="card" aria-labelledby="nl-title">
<span class="eyebrow">
<span class="dot" aria-hidden="true"></span>
The Frequency Drift Dispatch
</span>
<div class="wave" aria-hidden="true">
<span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span>
</div>
<h1 id="nl-title">
Never miss a <span class="grad">drop</span>.
</h1>
<p class="lede">
One email each Friday: new episodes, raw show notes, guest reveals and
the tracks that scored them. Read by curious ears in 40+ countries.
</p>
<form class="form" id="signup" novalidate>
<div class="field">
<label class="sr-only" for="email">Email address</label>
<span class="mail-ico" aria-hidden="true">✉</span>
<input
type="email"
id="email"
name="email"
inputmode="email"
autocomplete="email"
placeholder="[email protected]"
aria-describedby="msg"
required
/>
</div>
<button type="submit" class="btn" id="submit">
<span class="btn-label">Subscribe</span>
<span class="spinner" aria-hidden="true"></span>
</button>
</form>
<p class="msg" id="msg" role="status" aria-live="polite"></p>
<div class="proof">
<div class="avatars" aria-hidden="true">
<span style="--g:linear-gradient(135deg,#8b5cf6,#22d3ee)">AR</span>
<span style="--g:linear-gradient(135deg,#f472b6,#8b5cf6)">MK</span>
<span style="--g:linear-gradient(135deg,#22d3ee,#7c3aed)">JL</span>
<span style="--g:linear-gradient(135deg,#7c3aed,#f472b6)">TN</span>
</div>
<p class="proof-text">
Join <strong><span id="count">0</span></strong> subscribers
<span class="stars" aria-label="Rated 4.9 out of 5">★★★★★ 4.9</span>
</p>
</div>
<ul class="badges">
<li>No spam</li>
<li>Weekly cadence</li>
<li>Unsubscribe anytime</li>
</ul>
<!-- Success overlay -->
<div class="success" id="success" hidden>
<div class="check" aria-hidden="true">
<svg viewBox="0 0 52 52" width="52" height="52">
<circle cx="26" cy="26" r="24" fill="none" />
<path fill="none" d="M14 27l8 8 16-17" />
</svg>
<i class="spark s1"></i><i class="spark s2"></i>
<i class="spark s3"></i><i class="spark s4"></i>
</div>
<h2>You're on the list!</h2>
<p class="success-sub" id="success-sub">
Confirmation sent. Your first Dispatch lands this Friday.
</p>
<button type="button" class="ghost" id="reset">Add another email</button>
</div>
</section>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>A punchy, audio-first newsletter signup block designed for a fictional podcast, Frequency Drift. The layout centers on a glowing dark card with a violet-to-cyan gradient headline, an animated waveform strip that pulses like an equalizer, and an inline email form. A live subscriber counter animates up from zero on load, and a row of stacked avatars plus a five-star trust badge reinforce social proof before the visitor commits.
The form validates the email on submit: an empty or malformed address shows an inline error message and a shake micro-interaction, while a valid address triggers a loading spinner on the button, then flips the whole card into a celebratory success state with a checkmark burst and a personalized confirmation. A small toast helper echoes the result, and the subscriber count ticks up by one to acknowledge the new signup.
Everything is vanilla — no frameworks or build step. Controls are keyboard-usable with visible focus rings, ARIA live regions announce validation and success messages, colors meet WCAG AA contrast on the dark surface, and a dedicated mobile breakpoint reflows the form into a stacked column at narrow widths.