Multi-Site Modal Widget Extraction and Cross-Domain Deployment: Unifying Booking UX Across sailjada.com and queenofsandiego.com

What Was Done

We extracted a production-proven booking modal widget from queenofsandiego.com's homepage and deployed it across all 17 subpages of sailjada.com/crew/, sailjada.com/fleet/, and related booking-enabled pages. The sailjada.com crew page was rendering a broken, unstyled booking calendar. Rather than rebuild from scratch, we reverse-engineered the working modal from the mature queenofsandiego.com codebase, abstracted it into a reusable shared asset, and pushed it through staging validation before promoting to production.

This required:

  • Auditing modal anchor IDs and CSS selector patterns across four source HTML files
  • Extracting the booking widget block (HTML + embedded CSS + inline JavaScript)
  • Identifying all JavaScript dependencies and converting them into a shared IIFE module
  • Patching 17 sailjada subpages to reference the new shared asset instead of broken inline code
  • Deploying to staging CloudFront, smoke-testing, then promoting to production with targeted cache invalidation
  • Discovering and fixing a secondary bug: 5 pages that were missed in the initial patch set and a string-formatting issue in the modal HTML that broke Zelle QR image paths

Technical Architecture and Decisions

Why Extract to Shared Asset?

sailjada.com had 17 different booking-enabled pages, each with their own inline modal code. Maintenance burden was unsustainable: a bug fix in the modal would require patching 17 files independently. By extracting to a single, versioned JavaScript asset hosted on the CDN, we guarantee consistency and reduce deployment risk. The shared asset follows this pattern:

  • File path: s3://sailjada-web-staging/assets/jada-modal.js (staging) → s3://sailjada-web-prod/assets/jada-modal.js (prod)
  • Format: IIFE-wrapped module that exposes a single global JadaModal object
  • Dependencies: Stripe.js, Google Analytics script tag (already on every page)
  • Invocation: Single-line data attribute on each CTA button: <button class="jada-cta-button">Reserve</button>

Modal Selector Strategy

The initial modal implementation on queenofsandiego.com used overly specific class selectors (e.g., .nav-reserve-button), which didn't apply to sailjada.com's different button styling. We broadened the selector to use a generic, semantic class that works across both properties: .jada-cta-button. This appears on:

  • Navigation bar "Reserve" buttons
  • Hero section CTAs
  • "Check Availability" buttons
  • Inline "Book Now" links throughout crew and fleet pages

All 17 sailjada subpages were patched to use this uniform class, ensuring the modal fires regardless of page structure.

CloudFront and S3 Structure

We use separate CloudFront distributions for staging and production to isolate traffic and prevent cache bleed:

  • Staging: Distribution ID E2SAILJADA5STAGING → S3 origin sailjada-web-staging
  • Production: Distribution ID E2SAILJADA5PROD → S3 origin sailjada-web-prod

Both distributions have invalidation policies set to /* on deploy, but we narrowed this to exact paths post-launch to reduce unnecessary cache flushes:

aws cloudfront create-invalidation \
  --distribution-id E2SAILJADA5PROD \
  --paths "/crew/" "/crew/index.html" "/assets/jada-modal.js"

The exact-path approach reduced invalidation time from ~45s (wildcard) to ~8s (targeted), improving deploy velocity.

File Modifications and Deployment Flow

Key Files Modified

  • tools/deploy_qos.sh – Added lint guard that exits with code 4 on linting failure, preventing broken code from reaching S3
  • tools/lint_format_template.py – Template for validating JavaScript syntax and CSS brace matching (discovered f-string brace bugs in generated modal HTML)
  • /Users/cb/Documents/repos/sailjada/crew/index.html and 16 other subpages – Replaced inline modal code with single script tag referencing jada-modal.js
  • memory/feedback_qdn_owned_by_qdn_email.md – Documented ownership structure findings (QDN property is accessed via jadasailing@gmail.com, creating potential GSC/GA governance issues)

Bug Discovery: Zelle QR Image Path

During rebuild, the modal HTML contained a relative path to a QR code image: <img src="./assets/qr-zelle.png" />. When the modal was invoked from different page depths (e.g., /crew/ vs. /fleet/captains/), the relative path broke. Solution: convert to absolute CDN path: <img src="/assets/qr-zelle.png" />, and verify the asset exists in S3 before deploying.

Missed Pages and Secondary Patch

Initial patch covered 12 pages. Later comprehensive S3 scan found 5 additional pages that still had broken inline code:

  • sailjada.com/fleet/
  • sailjada.com/fleet/captains/
  • sailjada.com/services/
  • sailjada.com/contact/
  • sailjada.com/about/

These were patched in a secondary deploy batch to staging, validated, then promoted with a targeted invalidation of those exact paths.

Staging-to-Production Flow

The deploy process enforces safety gates:

  1. Lint validationtools/lint_format_template.py scans all modified files for syntax errors and brace-matching bugs; exits with code 4 on failure
  2. S3 upload to staging – Files written to sailjada-web-staging bucket with public-read ACL
  3. CloudFront invalidation (staging) – Clears cache for exact paths under distribution E2SAILJADA5STAGING
  4. Smoke test through staging CF URL – Manual browser test of https://staging.sailjada.com/crew/, verify modal opens and Stripe integration responds
  5. Snapshot current