Staging the Zuniga Shoals "Zuniga Days" Event Section: Additive Deployment Pattern with S3 Prefix Isolation

What Was Done

We deployed a new event marketing section ("Zuniga Days") to the zunigashoals.com staging environment without touching production. The approach used S3 prefix-based isolation — staging content lives at s3://zunigashoals.com/_staging/index.html while production remains at s3://zunigashoals.com/index.html. This allowed us to iterate rapidly, validate design and email capture integration, and keep a clean rollback path.

The deployment added ~212 lines of HTML and CSS across a single file, introduced a sticky navigation ribbon, six activity tiles with styled callouts, email capture via Google Apps Script, and wired up three open questions (naming, event dates, domain strategy) as draft placeholders for stakeholder input.

Technical Details: File Structure and Deployment Flow

Source and Target

  • Source file: /tmp/zuniga-staging.html — working copy with prod snapshot merged + new section HTML + new CSS rules
  • Production snapshot: /tmp/zunigashoals.com__index.html__2026-05-26T17-31-10Z.prod-snapshot — timestamped backup taken before any changes
  • Target (S3): s3://zunigashoals.com/_staging/index.html
  • Production (untouched): s3://zunigashoals.com/index.html

Why Prefix Isolation?

Using a /_staging/ prefix keeps staging and production on the same CloudFront distribution (no DNS flip, no distribution swap needed). The S3 bucket routing rules already resolve zunigashoals.com/_staging/* to the staging prefix, and zunigashoals.com/ (root path) to production. This eliminates:

  • CloudFront cache invalidation for the production path
  • Route53 CNAME swapping
  • Dual bucket management
  • Risk of accidental production overwrites

A developer can visit https://zunigashoals.com/_staging/ to see staged changes immediately while prod traffic is unaffected.

What Was Added: Section Architecture

Sticky Ribbon

<div class="ribbon">
  Zuniga Days · the last free open anchorage in San Diego
</div>

CSS rule: .ribbon — position fixed, top of viewport, z-index layered above hero content. Uses backdrop-filter: blur() for semi-transparent effect; no performance hit on modern browsers.

New Section: <section id="zd">

Inserted between the hero banner and shop section. Contains:

  • Pull quote: "Who's in charge here?" — NO ONE. — Reinforces the "no permits, no fees, no rules" positioning
  • Three-pillar callout: PERMIT none required · COST free to anchor · RULES see above — Simple, scannable grid
  • Six activity tiles: Each a <div class="zd-tile"> with title and brief description:
    • Unsanctioned Dinghy Races
    • Zuniga Shoals T-Shirt Contest
    • Boat-Diving Contest
    • Raftup & Rage
    • Anchor & Anthem
    • Flag Off Your Stern
  • Value prop callout: "What you get for free" — Hotel Del / Coronado / Point Loma views
  • Secondary CTA: "No boat? No problem" — encourages logistics (find a dinghy, crew up)
  • Email capture form: Input + CTA button "Tell me the day. I'll be there."

Email Capture Integration

The form submits to the existing Google Apps Script endpoint (no new infrastructure). JavaScript handler:

document.querySelector('form.zd-email').addEventListener('submit', async (e) => {
  e.preventDefault();
  const email = e.target.querySelector('input[type="email"]').value;
  const payload = {
    action: "zuniga_days_signup",
    email: email,
    timestamp: new Date().toISOString()
  };
  // POST to existing GAS endpoint
  await fetch(GAS_ENDPOINT, {
    method: 'POST',
    body: JSON.stringify(payload)
  });
  // Clear form / show confirmation
});

The action: "zuniga_days_signup" key allows the backend to bucket signups separately from other form submissions. No new Google Sheet, no new Apps Script — reuses existing infrastructure.

Infrastructure: S3, CloudFront, and Bucket Policy

S3 Bucket Configuration

The bucket zunigashoals.com uses routing rules in its static website configuration. When CloudFront receives a request to /_staging/index.html, the bucket resolves it correctly because:

  • Both files exist in the same bucket root
  • CloudFront origin is set to the S3 website endpoint (not the REST endpoint)
  • S3 website routing does not require custom rules for prefix paths — the key is simply _staging/index.html

CloudFront Distribution

No changes to distribution ID or behavior rules were needed. The distribution already caches both paths separately because CloudFront treats /index.html and /_staging/index.html as distinct cache keys. This means:

  • Staging and production have independent TTLs and invalidation cycles
  • An invalidation on /_staging/* does not affect /index.html caching
  • Staging can be deployed, tested, and redeployed without cache-busting prod

No Route53 Changes Required

zunigashoals.com A-record already points to the CloudFront distribution. The CNAME and distribution ID remain unchanged; traffic routing is handled at the S3 object-key level.

Key Decisions and Rationale

1. Additive-Only Changes to Source

Rather than editing production HTML in-place, we kept prod snapshot and merged all new markup into a staging copy. This ensures:

  • Rollback is one S3 copy: If staging breaks, we redeploy the snapshot or the current prod file.
  • Code review clarity: A diff between snapshot and staging shows exactly what was added.