/** * trial_modal.js — Reusable Free-Trial Modal Component * ====================================================== * Drop this script into any page. Configure via window.TrialModalConfig * before the script loads, or pass nothing and accept the defaults. * * CONFIGURATION (all keys optional) * ---------------------------------- * window.TrialModalConfig = { * webhook : string — POST endpoint (multipart/form-data) * product : string — hidden field value (default: "odoo_19") * title : string — modal heading (default: "Start Your 4 Weeks Free Trial") * subtitle : string — modal sub-heading (default: "...") * submitLabel : string — submit button label (default: "Start Free Trial") * confirmTitle : string — success heading (default: "Thank you! 🎉") * confirmSub : string — success body (default: "...") * triggerSelector : string — CSS selector(s) for open triggers * (default: "#open-modal, .open-modal") * locations : Array<{ value: string, label: string }> * (default: 6 preset server locations) * utm_source : string — overridden by ?utm_source (default: "homepage") * utm_medium : string — overridden by ?utm_medium (default: "direct") * utm_campaign : string — overridden by ?utm_campaign (default: "none") * utm_term : string (default: "") * utm_content : string (default: "") * } * * PUBLIC API (window.TrialModal) * -------------------------------- * TrialModal.open(plan?) — open the modal, optionally pre-filling plan * TrialModal.close() — close the modal * * DESIGN TOKENS * ------------- * The modal reads CSS custom properties from the host page when available. * Fallback values match the my-biz.app warm coffee / burnt-orange theme. * Override any token on your :root to re-theme without touching this file. * * TOKEN MAP * --tm-bg modal overlay tint (rgba(62,39,35,0.84)) * --tm-card modal box background (#fff3e0) * --tm-white heading / body text on light bg (#3e2723) * --tm-silver muted body text (#6d4c41) * --tm-grey placeholder / label text (#a1887f) * --tm-gold primary accent / CTA (#e65100) * --tm-gold-hi hover highlight (#fb8c00) * --tm-gold-lo gradient end-stop (#bf360c) * --tm-gold-glow focus ring glow (rgba(230,81,0,0.18)) * --tm-border field border (rgba(230,81,0,0.18)) * --tm-radius box border-radius (28px) */ (function () { "use strict"; /* ------------------------------------------------------------------ */ /* 1. DEFAULTS */ /* ------------------------------------------------------------------ */ var DEFAULTS = { webhook: "", product: "odoo_19", title: "Start Your 4 Weeks Free Trial", subtitle: "Test drive your back-office in a 4-week free trial.", submitLabel: "Start Free Trial", confirmTitle: "Thank you! \uD83C\uDF89", confirmSub: "We\u2019ve received your request and a specialist will be in touch within one business day.", triggerSelector: "#open-modal, .open-modal", locations: [ { value: "boston", label: "\uD83C\uDDFA\uD83C\uDDF8 Boston" }, { value: "manchester", label: "\uD83C\uDDEC\uD83C\uDDE7 Manchester" }, { value: "mumbai", label: "\uD83C\uDDEE\uD83C\uDDF3 Mumbai" }, { value: "saopaulo", label: "\uD83C\uDDE7\uD83C\uDDF7 S\u00e3o Paulo" }, { value: "meppel", label: "\uD83C\uDDF3\uD83C\uDDF1 Meppel" }, { value: "sydney", label: "\uD83C\uDDE6\uD83C\uDDFA Sydney" }, ], utm_source: "homepage", utm_medium: "direct", utm_campaign: "none", utm_term: "", utm_content: "", }; /* ------------------------------------------------------------------ */ /* 2. MERGE CONFIG */ /* ------------------------------------------------------------------ */ var cfg = {}; for (var k in DEFAULTS) { if (Object.prototype.hasOwnProperty.call(DEFAULTS, k)) { cfg[k] = DEFAULTS[k]; } } var userCfg = window.TrialModalConfig || {}; for (var k in userCfg) { if (Object.prototype.hasOwnProperty.call(userCfg, k)) { cfg[k] = userCfg[k]; } } /* Resolve UTM params from URL query string — URL wins over config */ var urlParams = new URLSearchParams(window.location.search); ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"].forEach(function (p) { var v = urlParams.get(p); if (v) cfg[p] = v; }); /* ------------------------------------------------------------------ */ /* 3. INJECT CSS */ /* ------------------------------------------------------------------ */ var CSS = [ /* --- Token fallbacks scoped to the overlay (inherited by children) --- Host-page :root tokens take precedence automatically. */ "#tm-overlay {", " --tm-bg: rgba(62,39,35,0.84);", " --tm-card: #fff3e0;", " --tm-white: #3e2723;", " --tm-silver: #6d4c41;", " --tm-grey: #a1887f;", " --tm-gold: #e65100;", " --tm-gold-hi: #fb8c00;", " --tm-gold-lo: #bf360c;", " --tm-gold-glow: rgba(230,81,0,0.18);", " --tm-border: rgba(230,81,0,0.18);", " --tm-radius: 28px;", "}", /* --- Overlay --- */ "#tm-overlay {", " position: fixed;", " inset: 0;", " z-index: 9900;", " display: none;", " align-items: center;", " justify-content: center;", " padding: 20px;", " background: var(--tm-bg);", " backdrop-filter: blur(14px);", " -webkit-backdrop-filter: blur(14px);", " box-sizing: border-box;", "}", /* --- Box --- */ "#tm-box {", " position: relative;", " width: 100%;", " max-width: 640px;", " padding: 44px;", " background: var(--tm-card);", " border-radius: var(--tm-radius);", " border: 1px solid rgba(230,81,0,0.24);", " box-shadow: 0 30px 90px rgba(0,0,0,0.3);", " box-sizing: border-box;", "}", /* --- Close button --- */ "#tm-close {", " position: absolute;", " top: 18px;", " right: 18px;", " width: 40px;", " height: 40px;", " border-radius: 50%;", " color: var(--tm-grey);", " background: rgba(93,64,55,0.05);", " font-size: 1.2rem;", " border: none;", " cursor: pointer;", " line-height: 1;", "}", "#tm-close:hover { color: var(--tm-gold); }", /* --- Title --- */ "#tm-box .tm-title {", " font-family: Georgia, 'Times New Roman', serif;", " font-size: 2.2rem;", " font-weight: 700;", " line-height: 1.05;", " margin: 0 0 10px;", " background: linear-gradient(135deg, var(--tm-gold-lo) 0%, var(--tm-gold) 38%, var(--tm-gold-hi) 55%, var(--tm-gold) 75%, var(--tm-gold-lo) 100%);", " -webkit-background-clip: text;", " -webkit-text-fill-color: transparent;", " background-clip: text;", "}", /* --- Subtitle --- */ "#tm-box .tm-subtitle {", " font-size: 0.9rem;", " color: var(--tm-silver);", " margin: 0 0 26px;", "}", /* --- Grid --- */ ".tm-grid {", " display: grid;", " grid-template-columns: repeat(2, minmax(0, 1fr));", " gap: 0 16px;", "}", ".tm-field-group {", " margin-bottom: 18px;", "}", ".tm-grid .tm-field-group.full {", " grid-column: 1 / -1;", "}", /* --- Field label --- */ ".tm-label {", " display: block;", " margin-bottom: 8px;", " font-size: 0.62rem;", " font-family: system-ui, -apple-system, sans-serif;", " text-transform: uppercase;", " letter-spacing: 0.08em;", " color: var(--tm-gold);", "}", /* --- Field (input / select / custom button) --- */ ".tm-field {", " width: 100%;", " padding: 14px 16px;", " border-radius: 16px;", " background: rgba(93,64,55,0.05);", " border: 1px solid var(--tm-border);", " color: var(--tm-white);", " box-sizing: border-box;", " font: inherit;", " outline: none;", "}", ".tm-field:focus {", " border-color: var(--tm-gold);", " box-shadow: 0 0 0 3px var(--tm-gold-glow);", "}", ".tm-field::placeholder { color: var(--tm-grey); }", /* --- Custom select --- */ ".tm-select { position: relative; }", ".tm-select-btn {", " position: relative;", " text-align: left;", " cursor: pointer;", " padding-right: 42px;", " background: none;", " border: none;", "}", ".tm-select-btn::after {", " content: '';", " position: absolute;", " right: 16px;", " top: 50%;", " width: 14px;", " height: 8px;", " transform: translateY(-50%);", " background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='8'%3E%3Cpath d='M1 1l6 6 6-6' stroke='%23E65100' stroke-width='1.8' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");", " background-repeat: no-repeat;", " background-position: center;", " pointer-events: none;", "}", ".tm-select-btn.is-placeholder { color: var(--tm-grey); }", ".tm-select-menu {", " position: absolute;", " top: calc(100% + 8px);", " left: 0;", " right: 0;", " z-index: 3;", " max-height: 220px;", " padding: 8px;", " background: var(--tm-card);", " border: 1px solid var(--tm-border);", " border-radius: 16px;", " box-shadow: 0 18px 40px rgba(62,39,35,0.16);", " overflow-y: auto;", " display: none;", "}", ".tm-select.open .tm-select-menu { display: block; }", ".tm-select-option {", " width: 100%;", " padding: 10px 12px;", " border: none;", " border-radius: 12px;", " background: transparent;", " color: var(--tm-white);", " text-align: left;", " font: inherit;", " cursor: pointer;", " transition: background 0.2s, color 0.2s;", "}", ".tm-select-option:hover,", ".tm-select-option:focus-visible,", ".tm-select-option.is-selected {", " outline: none;", " background: rgba(230,81,0,0.1);", " color: var(--tm-gold);", "}", /* --- Submit button --- */ ".tm-submit {", " width: 100%;", " margin-top: 8px;", " padding: 15px 20px;", " border-radius: 50px;", " border: none;", " cursor: pointer;", " font-family: system-ui, -apple-system, sans-serif;", " font-size: 0.95rem;", " font-weight: 600;", " letter-spacing: 0.02em;", " color: var(--tm-card);", " background: linear-gradient(135deg, var(--tm-gold-lo) 0%, var(--tm-gold) 38%, var(--tm-gold-hi) 55%, var(--tm-gold) 75%, var(--tm-gold-lo) 100%);", " transition: opacity 0.2s;", "}", ".tm-submit:hover { opacity: 0.88; }", ".tm-submit:disabled { opacity: 0.56; cursor: not-allowed; }", /* --- Confirmation panel --- */ "#tm-confirm {", " display: none;", " text-align: center;", " padding-top: 8px;", "}", "#tm-confirm .tm-confirm-title {", " font-family: Georgia, 'Times New Roman', serif;", " font-size: 1.6rem;", " color: var(--tm-gold-hi);", " margin: 0 0 12px;", "}", "#tm-confirm .tm-confirm-sub {", " color: var(--tm-silver);", " font-size: 0.9rem;", " margin: 0 0 24px;", "}", /* --- Responsive --- */ "@media (max-width: 860px) {", " #tm-box { padding: 34px 22px; }", " .tm-grid { grid-template-columns: 1fr; }", "}", "@media (max-width: 480px) {", " #tm-box .tm-title { font-size: 1.8rem; }", "}", ].join("\n"); var styleEl = document.createElement("style"); styleEl.id = "tm-styles"; styleEl.textContent = CSS; document.head.appendChild(styleEl); /* ------------------------------------------------------------------ */ /* 4. BUILD & INJECT HTML */ /* ------------------------------------------------------------------ */ function escapeHtml(str) { return String(str) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function buildLocationOptions() { return cfg.locations .map(function (loc) { return [ '", ].join(""); }) .join("\n"); } var html = [ '
' + escapeHtml(cfg.subtitle) + "
", ' ", /* Confirmation */ '' + escapeHtml(cfg.confirmTitle) + "
", '' + escapeHtml(cfg.confirmSub) + "
", ' ', "