newsletter: site-wide signup modal with 7-day dismiss

Renders on every page via root layout. Behavior:
- 5s delay after first paint so it doesn't slam the user.
- Animates from a pill into the full card (bb-modal-pop keyframe);
  respects prefers-reduced-motion.
- Heading "Get the latest in broadcast & media", email field,
  Subscribe + "Not now" + close (X).
- Subscribe POSTs to the existing /api/newsletter/subscribe route
  (native system — newsletter_subscribers table + SMTP welcome
  via Mailcow/SMTP2GO). Listmonk wasn't installed anywhere on the
  infra; switching to it later is a one-line URL change.
- "Not now" + backdrop click + Escape + X all dismiss for 7 days
  via localStorage["bb_newsletter_dismissed"].
- On success → "Check your email" panel + auto-close at 4s;
  subscribed flag stored in localStorage so the modal never
  re-prompts that visitor.

Styling matches the site's dark-navy + electric-blue palette
(NOT the neon ticker palette). No layout/page shifts — overlay
only.
This commit is contained in:
Ryan Salazar
2026-05-20 17:55:29 +00:00
parent 418027b469
commit 8f8eaa5fe8
3 changed files with 453 additions and 1 deletions

View File

@@ -1118,4 +1118,246 @@ h1, h2, h3 {
.bb-browse-item[aria-expanded="true"] .bb-browse-caret {
transform: rotate(180deg) translateY(1px);
}
}
/* ─── Newsletter modal (site-wide popup) ──────────────────────────────────── */
.bb-modal-backdrop {
position: fixed;
inset: 0;
background: rgba(5, 10, 15, 0.62);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
z-index: 9000;
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
animation: bb-modal-backdrop-fade 0.35s ease both;
}
@keyframes bb-modal-backdrop-fade {
from { opacity: 0; }
to { opacity: 1; }
}
.bb-modal-card {
position: relative;
width: 100%;
max-width: 440px;
background:
radial-gradient(120% 70% at 10% -10%, rgba(59,130,246,0.10), transparent 60%),
linear-gradient(180deg, #11192a 0%, #0c121e 100%);
border: 1px solid rgba(59,130,246,0.22);
border-radius: 14px;
padding: 28px 28px 24px;
box-shadow:
0 24px 60px rgba(0,0,0,0.55),
0 0 0 1px rgba(255,255,255,0.02) inset;
color: #e6ecf3;
font-family: var(--font-body);
}
/* Small-to-large open animation — scales the card up from a 65% pill and
fades in. Honors prefers-reduced-motion. */
.bb-modal-enter {
animation: bb-modal-pop 0.42s cubic-bezier(0.22, 1, 0.36, 1) both;
transform-origin: center bottom;
}
@keyframes bb-modal-pop {
0% { opacity: 0; transform: translateY(28px) scale(0.62); border-radius: 999px; }
55% { opacity: 1; transform: translateY(0) scale(1.02); border-radius: 18px; }
100% { opacity: 1; transform: translateY(0) scale(1.00); border-radius: 14px; }
}
@media (prefers-reduced-motion: reduce) {
.bb-modal-enter { animation: bb-modal-fade 0.18s ease both; }
}
@keyframes bb-modal-fade { from { opacity: 0; } to { opacity: 1; } }
.bb-modal-close {
position: absolute;
top: 12px;
right: 12px;
width: 30px;
height: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 999px;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.06);
color: #98a3b3;
cursor: pointer;
transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.bb-modal-close:hover {
background: rgba(59,130,246,0.18);
color: #ffffff;
border-color: rgba(59,130,246,0.45);
}
.bb-modal-eyebrow {
display: inline-flex;
align-items: center;
gap: 8px;
font-family: var(--font-mono);
font-size: 10px;
font-weight: 700;
letter-spacing: 0.2em;
text-transform: uppercase;
color: #3b82f6;
margin-bottom: 14px;
}
.bb-modal-eyebrow-dot {
display: inline-block;
width: 7px;
height: 7px;
border-radius: 50%;
background: #3b82f6;
box-shadow: 0 0 10px rgba(59,130,246,0.65);
animation: bb-modal-pulse 2.2s ease-in-out infinite;
}
@keyframes bb-modal-pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.55; transform: scale(0.85); }
}
.bb-modal-heading {
font-family: var(--font-heading);
font-size: 22px;
font-weight: 800;
line-height: 1.18;
margin: 0 0 8px;
color: #ffffff;
letter-spacing: -0.005em;
}
.bb-modal-sub {
margin: 0 0 18px;
font-size: 13.5px;
line-height: 1.55;
color: #98a3b3;
}
.bb-modal-form { margin: 0; }
.bb-modal-label {
display: block;
font-family: var(--font-mono);
font-size: 10px;
font-weight: 700;
letter-spacing: 0.14em;
text-transform: uppercase;
color: #6b7585;
margin-bottom: 6px;
}
.bb-modal-input {
width: 100%;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.10);
border-radius: 8px;
color: #ffffff;
font-size: 14px;
font-family: var(--font-body);
padding: 11px 13px;
outline: 0;
transition: border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
}
.bb-modal-input:focus {
border-color: #3b82f6;
background: rgba(59,130,246,0.06);
box-shadow: 0 0 0 3px rgba(59,130,246,0.18);
}
.bb-modal-input::placeholder { color: #4f5763; }
.bb-modal-input:disabled { opacity: 0.5; cursor: not-allowed; }
.bb-modal-err {
margin-top: 8px;
padding: 8px 10px;
background: rgba(239,68,68,0.10);
border: 1px solid rgba(239,68,68,0.32);
border-radius: 6px;
color: #fca5a5;
font-size: 12.5px;
}
.bb-modal-actions {
display: flex;
gap: 10px;
margin-top: 16px;
}
.bb-modal-btn-primary,
.bb-modal-btn-secondary {
flex: 1;
padding: 11px 16px;
border-radius: 8px;
font-family: var(--font-body);
font-size: 13.5px;
font-weight: 700;
letter-spacing: 0.01em;
cursor: pointer;
transition: background 0.15s ease, color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
border: 1px solid transparent;
}
.bb-modal-btn-primary {
background: linear-gradient(180deg, #3b82f6 0%, #2563eb 100%);
color: #ffffff;
box-shadow: 0 6px 18px rgba(59,130,246,0.28);
}
.bb-modal-btn-primary:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 10px 22px rgba(59,130,246,0.40);
}
.bb-modal-btn-primary:disabled {
opacity: 0.6;
cursor: not-allowed;
box-shadow: none;
}
.bb-modal-btn-secondary {
background: transparent;
color: #98a3b3;
border-color: rgba(255,255,255,0.10);
}
.bb-modal-btn-secondary:hover:not(:disabled) {
color: #ffffff;
border-color: rgba(255,255,255,0.22);
background: rgba(255,255,255,0.04);
}
.bb-modal-btn-secondary:disabled { opacity: 0.5; cursor: not-allowed; }
.bb-modal-fineprint {
margin: 14px 0 0;
font-size: 11px;
color: #5a6470;
line-height: 1.45;
}
/* Success state */
.bb-modal-success {
text-align: center;
padding: 6px 0 8px;
}
.bb-modal-success-icon {
width: 56px;
height: 56px;
margin: 0 auto 14px;
border-radius: 50%;
background: rgba(59,130,246,0.14);
border: 1px solid rgba(59,130,246,0.45);
display: inline-flex;
align-items: center;
justify-content: center;
color: #3b82f6;
}
.bb-modal-success-title {
font-family: var(--font-heading);
font-size: 19px;
font-weight: 800;
color: #ffffff;
margin: 0 0 6px;
}
.bb-modal-success-msg {
color: #98a3b3;
font-size: 13.5px;
line-height: 1.55;
margin: 0;
}
.bb-modal-success-msg strong {
color: #ffffff;
font-weight: 600;
}