```html

Extracting a Shared Modal Widget Across 17 Multi-Domain Subpages: Cross-Site Asset Deduplication at Scale

What We Did

We discovered that a sophisticated booking modal—originally built and tested on queenofsandiego.com—needed to be ported to 17 subpages across sailjada.com without duplicating its CSS, HTML, and JavaScript. The challenge: each subpage was independently generated from a template system, meaning the modal code existed in separate inline copies on every page. We extracted it into a shared, cacheable asset and deployed it across both staging and production with zero downtime.

Technical Details: The Problem

The modal was initially embedded inline on the queenofsandiego.com homepage. When we needed the same modal on sailjada.com's crew page and booking flow, a naive copy-paste would have created maintenance debt: any future modal fix would require patching across multiple files and deployments.

Worse, the initial subpage audit revealed:

  • An f-string brace bug in the modal's embedded CSS (malformed template variable escaping)
  • Relative path issues on QR code references (./qr/zelle-qr.png vs. absolute paths)
  • Missing global function scope for Stripe.js and Google Analytics callbacks
  • 17 sailjada subpages with distinct byte-range locations for the broken modal block

Inline fixes would fail because the template generator—likely inject_structured_data.py or a similar bulk-injection script—was re-creating these pages on each deploy, overwriting manual patches.

Solution: Shared Asset Extraction

Step 1: Map the Modal Source

We pulled the full booking widget block from the queenofsandiego.com prod homepage and mapped its component structure:

  • CSS block: ~180 lines of modal styling (flexbox, z-index layering, animation keyframes)
  • HTML block: modal wrapper, date picker input, CTA buttons, QR code image tags
  • JavaScript block: event listeners, Stripe integration, GA event firing, date validation

The JavaScript relied on three global functions that had to be available at runtime:

  • window.openBookingModal() — triggered by CTA button clicks
  • window.handleDateSelect(dateString) — calendar date selection handler
  • window.submitBookingForm() — Stripe redirect and GA tracking

Step 2: Build the Shared Asset

We created /assets/jada-modal.js as a single, minifiable JavaScript file that:

  • Declared all modal CSS as a template literal (avoiding parse-time template variable expansion)
  • Injected a <style> tag into the DOM on first load
  • Defined the modal HTML structure and appended it to document.body
  • Exposed the three global functions as an IIFE (Immediately Invoked Function Expression) to avoid polluting the global scope more than necessary
  • Handled absolute paths for media assets (QR code image → /assets/qr/zelle-qr.png)

The key insight: by injecting markup dynamically, we avoided the f-string brace bug entirely. The CSS and HTML were strings in JavaScript, not template variables.

Step 3: Update All 17 Subpages

We patched each of the 17 sailjada subpages by:

  • Removing the inline modal block (CSS + HTML + broken JS)
  • Adding a single <script src="/assets/jada-modal.js"></script> tag
  • Verifying all CTA button elements matched the selector button[data-action="book"] or equivalent class
  • Ensuring Stripe.js and Google Analytics were already loaded before the modal script fired

Affected subpages included /crew/, /book/, /service-areas/ (and child depth-1 subpages), and others totaling 17 HTML files on the sailjada.com S3 bucket.

Infrastructure & Deployment

S3 & CloudFront Strategy

The asset file was deployed to:

  • S3 bucket: sailjada-www (same bucket as subpages)
  • Key path: assets/jada-modal.js
  • CloudFront distribution: The primary sailjada.com distribution (found via a targeted query on all CF distributions)

We staged the asset first, validated syntax via Node.js, then promoted. Critical step: we used precise path invalidation instead of wildcards:

aws cloudfront create-invalidation \
  --distribution-id XXXXXXXXXXXXXX \
  --paths /assets/jada-modal.js \
           /crew/ /crew/index.html \
           /book/ /book/index.html \
           ... (all 17 subpage paths)

Wildcard invalidations (/*) would have been cheaper in principle but risky; precise paths guaranteed we only cleared what we modified and avoided unnecessary cache thrashing.

Staging Validation

Before prod promotion, we:

  • Deployed to a staging S3 bucket and CF distribution
  • Verified https://staging-sailjada.com/assets/jada-modal.js returned 200 and valid JavaScript syntax
  • Smoke-tested a crew page subpage through CF DNS, confirming modal opened and date picker worked
  • Checked browser console for JavaScript errors, GA event fires, and Stripe.js availability
  • Verified QR image paths resolved correctly (no 404s)

Key Decisions & Trade-offs

Why a Single Shared Asset vs. Web Components?

We chose a traditional IIFE + DOM injection over Web Components because:

  • Sailjada subpages run on older browsers (GA indicated some IE11 traffic)
  • No build pipeline exists for subpage templates; IIFE injection is backward-compatible
  • Web Components would require a separate jada-modal-element.js and custom element registration; added complexity for minimal benefit

Absolute vs. Relative Paths

The original modal used relative paths (./qr/zelle-qr.png), which worked on the homepage but broke on subpages in subdirectories like /crew/. We switched to absolute paths (/assets/qr/zelle-qr.png) and verified the S3 bucket structure matched the web root.

CSS Injection Timing

Rather than include <link rel="stylesheet" href="..."> (which