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.html to /tmp/zunigashoals.com__index.html__2026-05-26T17-31-10Z.prod-snapshot for rollback safety.
  • Working file: Modified /tmp/zuniga-staging.html through 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.html was 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.html remained 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"