Staging a Dynamic Event Section with Email Capture: The Zuniga Days Build
What Was Done
We deployed an additive event-marketing section to the Zuniga Shoals website staging environment without touching production. The build includes a sticky ribbon announcement, a six-tile activity grid, email capture integration with Google Apps Script, and supporting CSS—all merged into the existing site structure and deployed to s3://zunigashoals.com/_staging/index.html while keeping the production index.html untouched.
Technical Details
File Structure and Staging Strategy
The workflow followed a snapshot-and-merge pattern:
- Source snapshot: Downloaded current production
index.htmlto/tmp/zunigashoals.com__index.html__2026-05-26T17-31-10Z.prod-snapshotfor rollback safety. - Working file: Modified
/tmp/zuniga-staging.htmlthrough 42 iterative edits, adding ~212 lines of CSS and markup. - Deployment target:
s3://zunigashoals.com/_staging/index.html(prefix-based isolation from production).
This isolation pattern is critical: CloudFront does not require invalidation for new staging paths, and the S3 bucket policy allows separate read access to /_staging/* without affecting live traffic. Production remains on the root path.
Markup and CSS Architecture
The new section uses a semantic structure with a single-letter namespace (.zd* for "Zuniga Days") to avoid class-name collisions:
<section id="zd">
<div class="zd-container">
<h2 class="zd-title">Zuniga Days</h2>
<p class="zd-quote">"Who's in charge here?" — NO ONE.</p>
<div class="zd-pillars">
<!-- Three-column: permit, cost, rules -->
</div>
<div class="zd-tiles">
<!-- Six activity cards: Dinghy Races, T-Shirt Contest, Boat Diving, etc. -->
</div>
<form class="zd-email-capture" id="zuniga-days-signup">
<input type="email" name="email" placeholder="Tell me the day. I'll be there.">
<button type="submit">Count Me In</button>
</form>
</div>
</section>
The sticky ribbon is positioned at the top of the viewport using position: sticky; top: 0; z-index: 100;, ensuring it doesn't interfere with the existing nav stack while remaining visible during scroll.
Email Capture Integration
The form posts to an existing Google Apps Script endpoint already configured for Zuniga Shoals. The JavaScript handler:
document.getElementById('zuniga-days-signup').addEventListener('submit', function(e) {
e.preventDefault();
const email = this.querySelector('input[type="email"]').value;
fetch(GAS_ENDPOINT, {
method: 'POST',
body: JSON.stringify({
action: 'zuniga_days_signup',
email: email,
timestamp: new Date().toISOString()
})
})
.then(response => response.json())
.then(data => {
// Success feedback
alert('Thanks for signing up. See you at the shoals.');
})
.catch(err => console.error('Signup error:', err));
});
The action: 'zuniga_days_signup' label allows the GAS script to route this separately from other form submissions (e.g., shop orders, contact requests). No new backend infrastructure was required.
Infrastructure and Deployment
S3 Bucket Layout
The Zuniga Shoals S3 bucket (zunigashoals.com) uses a flat structure for HTML and a prefix-based staging isolation:
/index.html— production (untouched)/_staging/index.html— staging (newly deployed)/assets/*— shared CSS, JS, images (no changes needed)
Both production and staging reference the same asset paths, so CSS and images serve from cache regardless of which version is live. This reduces redundancy and ensures consistency.
CloudFront Distribution
The CloudFront distribution ID remains constant; no invalidation was required because:
- Staging content is on a new path that has no prior cache entries.
- Production
/index.htmlwas not modified, so its cache remains valid. - Asset paths are unchanged.
If staging were to be promoted to production, only /index.html would need invalidation via:
aws cloudfront create-invalidation --distribution-id DIST_ID --paths "/index.html"
Verification Steps
Before declaring the build live, we confirmed:
- Staging URL served the correct file:
curl https://zunigashoals.com/_staging/index.html | grep "Zuniga Days" - Content integrity: verified the six activity tiles, email form, and ribbon were present.
- No production breakage: production
index.htmlremained byte-identical to the pre-deployment snapshot.
Key Decisions and Rationale
Additive-Only Approach
We chose to add the section between the hero and shop rather than replacing content. This reduces risk—if the section underperforms or needs rapid iteration, we can modify or hide it without touching the rest of the page. It also preserves SEO value for existing sections.
Prefix-Based Staging
Instead of using a subdomain (e.g., staging.zunigashoals.com) or a separate bucket, we used the /_staging/ path prefix. This approach:
- Keeps content in a single bucket, reducing operational complexity.
- Avoids CloudFront distribution duplication and DNS management overhead.
- Allows easy A/B testing by swapping S3 routing rules if needed.
GAS Endpoint Reuse
Rather than building a new backend service or Lambda function, we integrated the email capture with the existing Google Apps Script handler. This minimizes infrastructure sprawl and keeps email routing centralized. The GAS script can be updated to distinguish between event signups and other actions via the action field.
Open Questions and Next Steps
Several decisions require stakeholder input:
- Event name: "Zuniga Days" is the working title. "Pirate Days"