Staging a Dynamic Event Section with Email Capture: Zuniga Shoals "Ocean Man" Build
This post documents the process of adding a new event marketing section to the Zuniga Shoals website without touching production, integrating email capture to an existing Google Apps Script endpoint, and identifying a downstream automation issue in the progress-tracking infrastructure.
What Was Done
We deployed a new event section—currently titled "Zuniga Days" (pending rename to "Ocean Man")—to a staging path on the production S3 bucket. The implementation added approximately 212 lines of HTML, CSS, and JavaScript while preserving the production index.html untouched through additive S3 prefixing and a full backup snapshot.
- Source file:
/tmp/zuniga-staging.html(local working copy) - Staging deployment target:
s3://zunigashoals.com/_staging/index.html - Production snapshot:
/tmp/zunigashoals.com__index.html__2026-05-26T17-31-10Z.prod-snapshot - Live staging URL:
https://zunigashoals.com/_staging/index.html
Technical Details: What Was Built
Sticky Ribbon Component
A fixed-position banner at the top of the page introduces the event with the line "Zuniga Days · the last free open anchorage in San Diego." The ribbon uses CSS positioning and z-index layering to remain visible during scroll without obscuring critical navigation. The implementation includes responsive breakpoints to collapse gracefully on mobile viewports.
Event Section Structure
The new section (assigned id="zd" for scoping) sits between the hero and shop sections and contains:
- Pull quote: "Who's in charge here?" — NO ONE. (establishes the anarchic, permissive brand voice)
- Three-pillar callout: PERMIT none required, COST free to anchor, RULES see above (legal and operational clarity)
- Six activity tiles: Each tile uses semantic HTML with flexbox layout for consistent sizing and alignment. Activities include Unsanctioned Dinghy Races, Boat-Diving Contest, Raftup & Rage, Anchor & Anthem, Flag Off Your Stern, and Zuniga Shoals T-Shirt Contest.
- Value proposition callout: "What you get for free" highlights visible landmarks (Hotel Del, Coronado, Point Loma)
- Barrier-to-entry removal: "No boat? No problem" section with copy directing visitors to find a dinghy owner
- Email capture form: Labeled "Tell me the day. I'll be there." with a single email input field
Email Capture Integration
The email form posts to the existing Google Apps Script endpoint already live on the site. The submission payload includes:
{
"action": "zuniga_days_signup",
"email": "[user input]",
"timestamp": "[ISO 8601 timestamp]"
}
This action value allows the existing GAS webhook to route the signup event into the correct spreadsheet or downstream service without requiring a separate deployment. The form includes client-side validation (HTML5 email input type) and prevents duplicate submissions during processing.
CSS Approach
All new styles use a .zd* BEM-style prefix (e.g., .zd-section, .zd-tile, .zd-ribbon) to avoid collisions with existing site CSS. This naming convention makes it trivial to locate, refactor, or remove the entire Zuniga Days section in a future deploy. CSS custom properties (--zd-*) are used for colors, spacing, and typography to maintain design consistency with the broader site palette while allowing quick visual iteration.
Infrastructure: S3 and CloudFront Strategy
S3 Bucket Structure
The Zuniga Shoals site is hosted in s3://zunigashoals.com with a CloudFront distribution (ID not disclosed here for security) fronting all requests. The staging deployment uses an additive path prefix rather than a separate bucket or domain:
- Production:
s3://zunigashoals.com/index.html(served via CloudFront ashttps://zunigashoals.com/) - Staging:
s3://zunigashoals.com/_staging/index.html(served ashttps://zunigashoals.com/_staging/index.html)
Why this approach: Staging on a path prefix within the same bucket avoids Route53 CNAME churn, separate CloudFront distribution costs, and the complexity of managing cross-domain cookie/session state. The underscore prefix signals to search engines and humans that this is a hidden staging environment, not a public page.
No CloudFront Invalidation Required
Because the staging content lives at a unique path that has never been served before, CloudFront has no cached objects to invalidate. The first request to /_staging/index.html will hit S3 directly and cache the response. This also means rolling back is as simple as deleting the staging object—production remains completely untouched.
Production Snapshot Strategy
Before any deployment, we downloaded the current production index.html to a timestamped local file:
aws s3 cp s3://zunigashoals.com/index.html \
/tmp/zunigashoals.com__index.html__2026-05-26T17-31-10Z.prod-snapshot \
--no-sign-request
This snapshot serves as an instant rollback point and a baseline for diff comparison if we need to understand what changed between versions.
Key Technical Decisions
Additive Rather Than Mutative
We downloaded production, appended the new section and styles, and deployed to staging—rather than modifying production in place. This approach ensures zero downtime and zero risk of partial deploys affecting live traffic.
Email Capture to Existing GAS Endpoint
Rather than provisioning a new webhook or backend service, the form posts to the existing Google Apps Script already deployed on the site. The action: "zuniga_days_signup" field allows Chloe's team to configure routing rules inside the GAS script without code changes, enabling rapid iteration on downstream handling (spreadsheet, Slack notification, etc.).
CSS Prefixing for Long-Term Maintainability
The .zd-* prefix makes it trivial to search, audit, or eventually remove the entire Zuniga Days section from the codebase. This is especially valuable if the event becomes a permanent fixture (styles merge into the global sheet) or if it's seasonal and needs to be toggled on and off.
Open Questions and Next Steps
- Event name: "Zuniga Days" is the working title; the preferred branding is "The Zuniga Shoals Ocean Man." Update copy and class names once finalized.
- Event dates: