Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,29 @@ <h3 id="tooltip-modal-title"></h3>
<div class="container connect-container">
<section id="links">
<h2>Connect</h2>

<div class="newsletter-card">
<h3>Get occasional product notes</h3>
<p>Short updates on AI product strategy, growth experiments, and things I'm learning while building.</p>
<!-- TODO: Replace placeholder newsletter action URL with your real provider endpoint (Buttondown/ConvertKit/Mailchimp form action). -->
<form id="newsletter-form" class="newsletter-form" action="https://example.com/subscribe" method="POST" novalidate>
<label for="newsletter-email">Email address</label>
<div class="newsletter-input-row">
<input
id="newsletter-email"
name="email"
type="email"
inputmode="email"
autocomplete="email"
placeholder="you@company.com"
required
/>
<button type="submit">Subscribe</button>
</div>
<p id="newsletter-status" class="newsletter-status" role="status" aria-live="polite"></p>
</form>
</div>

<a class="link-card" href="https://github.com/brainsparker" rel="noopener noreferrer" target="_blank" onclick="gtag('event', 'click', {'event_category': 'Connect', 'event_label': 'GitHub'});">
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='%23ffffff'%3E%3Cpath d='M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z'/%3E%3C/svg%3E" alt="GitHub" />
<span>GitHub</span>
Expand Down
74 changes: 74 additions & 0 deletions script.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ window.addEventListener('load', () => {
initScrollEffects();
initFlipCards();
initTooltips();
initNewsletterSignup();
});

function initScrollEffects() {
Expand Down Expand Up @@ -394,3 +395,76 @@ function initTooltips() {
}
});
}

function initNewsletterSignup() {
const form = document.getElementById('newsletter-form');
const emailInput = document.getElementById('newsletter-email');
const status = document.getElementById('newsletter-status');

if (!form || !emailInput || !status) return;

const submitButton = form.querySelector('button[type="submit"]');
const action = (form.getAttribute('action') || '').trim();
const isPlaceholderEndpoint = action === '' || action.includes('example.com/subscribe');

const setStatus = (message, type = '') => {
status.textContent = message;
status.classList.remove('is-success', 'is-error');
if (type) status.classList.add(type);
};

form.addEventListener('submit', async (event) => {
event.preventDefault();

if (!emailInput.value || !emailInput.checkValidity()) {
setStatus('Please enter a valid email address.', 'is-error');
emailInput.focus();
return;
}

if (isPlaceholderEndpoint) {
setStatus('Signup is almost ready. Newsletter provider endpoint still needs to be configured.', 'is-error');
return;
}

const formData = new FormData(form);

try {
if (submitButton) {
submitButton.disabled = true;
submitButton.setAttribute('aria-busy', 'true');
}
setStatus('Subscribing...');

const response = await fetch(action, {
method: form.method || 'POST',
body: formData,
headers: {
'Accept': 'application/json'
}
});

if (!response.ok) {
throw new Error(`Request failed with status ${response.status}`);
}

form.reset();
setStatus('Thanks for subscribing — you are on the list.', 'is-success');

if (typeof gtag !== 'undefined') {
gtag('event', 'newsletter_subscribe', {
event_category: 'Connect',
event_label: 'Newsletter'
});
}
} catch (error) {
setStatus('Could not subscribe right now. Please try again in a moment.', 'is-error');
console.error('Newsletter signup failed:', error);
} finally {
if (submitButton) {
submitButton.disabled = false;
submitButton.removeAttribute('aria-busy');
}
}
});
}
102 changes: 102 additions & 0 deletions styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,96 @@ a.work-card h3::after {
animation: fadeInUp 1s ease-out 0.4s backwards;
}

.newsletter-card {
border: 1px solid rgba(255,255,255,0.1);
border-radius: 12px;
background: rgba(255,255,255,0.03);
backdrop-filter: blur(10px);
padding: 24px;
}

.newsletter-card h3 {
margin: 0 0 8px;
color: #f3f3f3;
font-size: 1.2rem;
}

.newsletter-card p {
margin: 0 0 16px;
color: #bcbcbc;
line-height: 1.6;
}

.newsletter-form {
display: flex;
flex-direction: column;
gap: 10px;
}

.newsletter-form label {
font-size: 0.95rem;
color: #e3e3e3;
font-weight: 500;
}

.newsletter-input-row {
display: flex;
gap: 10px;
}

.newsletter-form input {
flex: 1;
min-width: 0;
border: 1px solid rgba(255,255,255,0.2);
background: rgba(255,255,255,0.06);
color: #ffffff;
border-radius: 10px;
padding: 12px 14px;
font-size: 1rem;
}

.newsletter-form input::placeholder {
color: #9f9f9f;
}

.newsletter-form input:focus-visible,
.newsletter-form button:focus-visible {
outline: 2px solid rgba(255,255,255,0.7);
outline-offset: 2px;
}

.newsletter-form button {
border: 1px solid rgba(255,255,255,0.3);
border-radius: 10px;
background: rgba(255,255,255,0.1);
color: #ffffff;
padding: 12px 18px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}

.newsletter-form button:hover {
background: rgba(255,255,255,0.2);
border-color: rgba(255,255,255,0.5);
}

.newsletter-status {
margin: 2px 0 0;
min-height: 1.2em;
color: #c9c9c9;
font-size: 0.92rem;
}

.newsletter-status.is-success {
color: #7dffa3;
}

.newsletter-status.is-error {
color: #ff9a9a;
}

.link-card {
display: flex;
align-items: center;
Expand Down Expand Up @@ -894,6 +984,18 @@ a.work-card h3::after {
#bio p {
font-size: 1rem;
}

.newsletter-card {
padding: 20px;
}

.newsletter-input-row {
flex-direction: column;
}

.newsletter-form button {
width: 100%;
}
}

@media (max-width: 480px) {
Expand Down