diff --git a/components/trial_modal.js b/components/trial_modal.js new file mode 100644 index 0000000..db94d08 --- /dev/null +++ b/components/trial_modal.js @@ -0,0 +1,644 @@ +/** + * 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) + "
", + ' ', + "