feat: split components into mybizapp.js + trial_modal.html, add show_calendar()
This commit is contained in:
@@ -0,0 +1,629 @@
|
|||||||
|
/**
|
||||||
|
* mybizapp.js — my-biz.app UI Components
|
||||||
|
* ========================================
|
||||||
|
* Provides two global modal functions:
|
||||||
|
*
|
||||||
|
* show_trial(plan?) — free-trial sign-up modal
|
||||||
|
* HTML lives in components/trial_modal.html (lazy-fetched)
|
||||||
|
*
|
||||||
|
* show_calendar() — book-a-meeting modal (Google Calendar iframe)
|
||||||
|
*
|
||||||
|
* CONFIGURATION (set before this script loads)
|
||||||
|
* ----------------------------------------------
|
||||||
|
* window.TrialModalConfig = {
|
||||||
|
* webhook : string — POST endpoint (multipart/form-data)
|
||||||
|
* product : string — hidden field value (default: "odoo_19")
|
||||||
|
* title : string — modal heading
|
||||||
|
* subtitle : string — modal sub-heading
|
||||||
|
* submitLabel : string — submit button label
|
||||||
|
* confirmTitle : string — success heading
|
||||||
|
* confirmSub : string — success body text
|
||||||
|
* calendarUrl : string — Google Calendar embed URL
|
||||||
|
* triggerSelector : string — CSS selectors for trial triggers
|
||||||
|
* (default: "#open-modal, .open-modal")
|
||||||
|
* calendarSelector : string — CSS selectors for calendar triggers
|
||||||
|
* (default: ".open-calendar")
|
||||||
|
* locations : Array<{ value: string, label: string }>
|
||||||
|
* utm_source / utm_medium / utm_campaign / utm_term / utm_content
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* AUTO-WIRING (on DOMContentLoaded)
|
||||||
|
* ------------------------------------
|
||||||
|
* #open-modal, .open-modal → show_trial(data-plan?)
|
||||||
|
* .open-calendar → show_calendar()
|
||||||
|
*
|
||||||
|
* DESIGN TOKENS (CSS custom properties, fallback to my-biz.app theme)
|
||||||
|
* --------------------------------------------------------------------
|
||||||
|
* --tm-bg, --tm-card, --tm-white, --tm-silver, --tm-grey
|
||||||
|
* --tm-gold, --tm-gold-hi, --tm-gold-lo, --tm-gold-glow, --tm-border, --tm-radius
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* 1. BASE PATH (resolved from this script's src) */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
var _scriptBase = (function () {
|
||||||
|
var s = document.currentScript;
|
||||||
|
if (s && s.src) return s.src.replace(/\/[^/]+$/, "/") + "/";
|
||||||
|
return "components/";
|
||||||
|
})();
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* 2. CONFIG */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
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.",
|
||||||
|
calendarUrl: "https://calendar.google.com/calendar/u/0/appointments/schedules/AcZssZ3DDbaiHFlhNhWySszAQoPXE_H73QLqYT3w7H9IYWC76RA_TgNIhLESjb4N7ep_D2D_OyW9q4-c",
|
||||||
|
triggerSelector: "#open-modal, .open-modal",
|
||||||
|
calendarSelector: ".open-calendar",
|
||||||
|
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: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* URL query string overrides UTM 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 SHARED CSS */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
var CSS = [
|
||||||
|
/* Token fallbacks — scoped so host-page :root overrides still win */
|
||||||
|
"#tm-overlay, #cal-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;",
|
||||||
|
"}",
|
||||||
|
|
||||||
|
/* ---- Shared overlay ---- */
|
||||||
|
"#tm-overlay, #cal-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;",
|
||||||
|
"}",
|
||||||
|
|
||||||
|
/* ================================================================ */
|
||||||
|
/* TRIAL MODAL */
|
||||||
|
/* ================================================================ */
|
||||||
|
|
||||||
|
"#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;",
|
||||||
|
"}",
|
||||||
|
|
||||||
|
"#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); }",
|
||||||
|
|
||||||
|
"#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;",
|
||||||
|
"}",
|
||||||
|
|
||||||
|
"#tm-box .tm-subtitle {",
|
||||||
|
" font-size: 0.9rem; color: var(--tm-silver); margin: 0 0 26px;",
|
||||||
|
"}",
|
||||||
|
|
||||||
|
".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; }",
|
||||||
|
|
||||||
|
".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);",
|
||||||
|
"}",
|
||||||
|
|
||||||
|
".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); }",
|
||||||
|
|
||||||
|
".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);",
|
||||||
|
"}",
|
||||||
|
|
||||||
|
".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; }",
|
||||||
|
|
||||||
|
"#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; }",
|
||||||
|
|
||||||
|
/* ================================================================ */
|
||||||
|
/* CALENDAR MODAL */
|
||||||
|
/* ================================================================ */
|
||||||
|
|
||||||
|
"#cal-box {",
|
||||||
|
" position: relative; width: 100%; max-width: 780px;",
|
||||||
|
" 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; overflow: hidden;",
|
||||||
|
"}",
|
||||||
|
|
||||||
|
"#cal-header {",
|
||||||
|
" display: flex; align-items: center; justify-content: space-between;",
|
||||||
|
" padding: 22px 32px;",
|
||||||
|
" border-bottom: 1px solid var(--tm-border);",
|
||||||
|
"}",
|
||||||
|
|
||||||
|
"#cal-header .cal-title {",
|
||||||
|
" font-family: Georgia, 'Times New Roman', serif;",
|
||||||
|
" font-size: 1.5rem; font-weight: 700; margin: 0;",
|
||||||
|
" 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;",
|
||||||
|
"}",
|
||||||
|
|
||||||
|
"#cal-close {",
|
||||||
|
" width: 40px; height: 40px; border-radius: 50%; flex-shrink: 0;",
|
||||||
|
" color: var(--tm-grey); background: rgba(93,64,55,0.05);",
|
||||||
|
" font-size: 1.2rem; border: none; cursor: pointer; line-height: 1;",
|
||||||
|
"}",
|
||||||
|
"#cal-close:hover { color: var(--tm-gold); }",
|
||||||
|
|
||||||
|
"#cal-frame {",
|
||||||
|
" width: 100%; height: 620px; border: none; display: block;",
|
||||||
|
"}",
|
||||||
|
|
||||||
|
/* ================================================================ */
|
||||||
|
/* RESPONSIVE */
|
||||||
|
/* ================================================================ */
|
||||||
|
|
||||||
|
"@media (max-width: 860px) {",
|
||||||
|
" #tm-box { padding: 34px 22px; }",
|
||||||
|
" .tm-grid { grid-template-columns: 1fr; }",
|
||||||
|
" #cal-header { padding: 18px 20px; }",
|
||||||
|
" #cal-frame { height: 540px; }",
|
||||||
|
"}",
|
||||||
|
"@media (max-width: 480px) {",
|
||||||
|
" #tm-box .tm-title { font-size: 1.8rem; }",
|
||||||
|
" #cal-header .cal-title { font-size: 1.2rem; }",
|
||||||
|
" #cal-frame { height: 480px; }",
|
||||||
|
"}",
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
var _styleEl = document.createElement("style");
|
||||||
|
_styleEl.id = "mba-styles";
|
||||||
|
_styleEl.textContent = CSS;
|
||||||
|
document.head.appendChild(_styleEl);
|
||||||
|
|
||||||
|
/* ================================================================== */
|
||||||
|
/* TRIAL MODAL */
|
||||||
|
/* ================================================================== */
|
||||||
|
|
||||||
|
var _trialLoading = false;
|
||||||
|
var _trialLoaded = false;
|
||||||
|
var _trialQueue = []; /* pending plan args while HTML is fetching */
|
||||||
|
|
||||||
|
/* --- show_trial(plan?) -------------------------------------------- */
|
||||||
|
function show_trial(plan) {
|
||||||
|
if (_trialLoaded) {
|
||||||
|
_openTrial(plan);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_trialQueue.push(plan !== undefined ? plan : null);
|
||||||
|
if (!_trialLoading) {
|
||||||
|
_trialLoading = true;
|
||||||
|
_loadTrialModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Fetch + inject trial_modal.html ------------------------------ */
|
||||||
|
function _loadTrialModal() {
|
||||||
|
var url = _scriptBase + "trial_modal.html";
|
||||||
|
fetch(url)
|
||||||
|
.then(function (r) {
|
||||||
|
if (!r.ok) throw new Error("[mybizapp] trial_modal.html fetch failed: " + r.status);
|
||||||
|
return r.text();
|
||||||
|
})
|
||||||
|
.then(function (html) {
|
||||||
|
var tmp = document.createElement("div");
|
||||||
|
tmp.innerHTML = html;
|
||||||
|
var node = tmp.firstElementChild;
|
||||||
|
document.body.appendChild(node);
|
||||||
|
|
||||||
|
_applyTrialConfig();
|
||||||
|
_wireTrialEvents();
|
||||||
|
|
||||||
|
_trialLoaded = true;
|
||||||
|
_trialLoading = false;
|
||||||
|
|
||||||
|
if (_trialQueue.length) {
|
||||||
|
var plan = _trialQueue[_trialQueue.length - 1];
|
||||||
|
_trialQueue = [];
|
||||||
|
_openTrial(plan);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error(err);
|
||||||
|
_trialLoading = false;
|
||||||
|
_trialQueue = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Apply TrialModalConfig overrides after HTML is in the DOM ---- */
|
||||||
|
function _applyTrialConfig() {
|
||||||
|
/* Text content */
|
||||||
|
var titleEl = document.getElementById("tm-title");
|
||||||
|
if (titleEl) titleEl.textContent = cfg.title;
|
||||||
|
|
||||||
|
var subEl = document.querySelector("#tm-box .tm-subtitle");
|
||||||
|
if (subEl) subEl.textContent = cfg.subtitle;
|
||||||
|
|
||||||
|
var submitEl = document.querySelector("#tm-form .tm-submit");
|
||||||
|
if (submitEl) submitEl.textContent = cfg.submitLabel;
|
||||||
|
|
||||||
|
var confirmTitleEl = document.querySelector("#tm-confirm .tm-confirm-title");
|
||||||
|
if (confirmTitleEl) confirmTitleEl.textContent = cfg.confirmTitle;
|
||||||
|
|
||||||
|
var confirmSubEl = document.querySelector("#tm-confirm .tm-confirm-sub");
|
||||||
|
if (confirmSubEl) confirmSubEl.textContent = cfg.confirmSub;
|
||||||
|
|
||||||
|
/* Locations — rebuild menu only if config provides a custom list */
|
||||||
|
if (userCfg.locations) {
|
||||||
|
var menu = document.getElementById("tm-location-menu");
|
||||||
|
if (menu) {
|
||||||
|
menu.innerHTML = cfg.locations.map(function (loc) {
|
||||||
|
return '<button type="button" class="tm-select-option" data-value="'
|
||||||
|
+ _esc(loc.value) + '">' + _esc(loc.label) + "</button>";
|
||||||
|
}).join("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* UTM hidden fields */
|
||||||
|
var form = document.getElementById("tm-form");
|
||||||
|
if (form) {
|
||||||
|
var utmMap = {
|
||||||
|
utm_source: cfg.utm_source,
|
||||||
|
utm_medium: cfg.utm_medium,
|
||||||
|
utm_campaign: cfg.utm_campaign,
|
||||||
|
utm_term: cfg.utm_term,
|
||||||
|
utm_content: cfg.utm_content,
|
||||||
|
};
|
||||||
|
Object.keys(utmMap).forEach(function (name) {
|
||||||
|
var el = form.querySelector('[name="' + name + '"]');
|
||||||
|
if (el) el.value = utmMap[name];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Wire all trial modal events ---------------------------------- */
|
||||||
|
function _wireTrialEvents() {
|
||||||
|
var overlay = document.getElementById("tm-overlay");
|
||||||
|
var box = document.getElementById("tm-box");
|
||||||
|
var closeBtn = document.getElementById("tm-close");
|
||||||
|
var form = document.getElementById("tm-form");
|
||||||
|
var confirmDiv = document.getElementById("tm-confirm");
|
||||||
|
var confirmClose = document.getElementById("tm-confirm-close");
|
||||||
|
var planField = document.getElementById("tm-plan");
|
||||||
|
var productField = document.getElementById("tm-product");
|
||||||
|
var locationField = document.getElementById("tm-location");
|
||||||
|
var locationSelect = document.getElementById("tm-location-select");
|
||||||
|
var locationBtn = document.getElementById("tm-location-btn");
|
||||||
|
var locationMenu = document.getElementById("tm-location-menu");
|
||||||
|
var defaultProduct = productField.value;
|
||||||
|
|
||||||
|
/* Location picker */
|
||||||
|
function _syncLabel() {
|
||||||
|
var val = locationField.value || "";
|
||||||
|
var selected = null;
|
||||||
|
locationMenu.querySelectorAll(".tm-select-option").forEach(function (opt) {
|
||||||
|
var isSel = opt.getAttribute("data-value") === val;
|
||||||
|
if (isSel) selected = opt;
|
||||||
|
opt.classList.toggle("is-selected", isSel);
|
||||||
|
opt.setAttribute("aria-selected", isSel ? "true" : "false");
|
||||||
|
});
|
||||||
|
locationBtn.textContent = selected ? selected.textContent.trim() : "Select your server location";
|
||||||
|
locationBtn.classList.toggle("is-placeholder", !selected);
|
||||||
|
locationBtn.setAttribute("aria-expanded", locationSelect.classList.contains("open") ? "true" : "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
function _openLoc() { locationSelect.classList.add("open"); _syncLabel(); }
|
||||||
|
function _closeLoc() { locationSelect.classList.remove("open"); _syncLabel(); }
|
||||||
|
|
||||||
|
locationBtn.addEventListener("click", function () {
|
||||||
|
locationSelect.classList.contains("open") ? _closeLoc() : _openLoc();
|
||||||
|
});
|
||||||
|
locationMenu.querySelectorAll(".tm-select-option").forEach(function (opt) {
|
||||||
|
opt.addEventListener("click", function () {
|
||||||
|
locationField.value = opt.getAttribute("data-value") || "";
|
||||||
|
_closeLoc();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
document.addEventListener("click", function (e) {
|
||||||
|
if (!locationSelect.contains(e.target)) _closeLoc();
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Store open/close functions so show_trial() can call them */
|
||||||
|
overlay._openTrial = function (plan) {
|
||||||
|
planField.value = plan || "General Free Trial";
|
||||||
|
productField.value = defaultProduct; /* always pinned */
|
||||||
|
if (window._mbsTracking) window._mbsTracking.applyToFields(form);
|
||||||
|
_closeLoc();
|
||||||
|
overlay.style.display = "flex";
|
||||||
|
document.body.style.overflow = "hidden";
|
||||||
|
};
|
||||||
|
|
||||||
|
overlay._closeTrial = function () {
|
||||||
|
overlay.style.display = "none";
|
||||||
|
document.body.style.overflow = "";
|
||||||
|
setTimeout(function () {
|
||||||
|
form.style.display = "";
|
||||||
|
confirmDiv.style.display = "none";
|
||||||
|
form.reset();
|
||||||
|
planField.value = "General Free Trial";
|
||||||
|
productField.value = defaultProduct;
|
||||||
|
locationField.value = "";
|
||||||
|
if (window._mbsTracking) window._mbsTracking.applyToFields(form);
|
||||||
|
_closeLoc();
|
||||||
|
var btn = form.querySelector(".tm-submit");
|
||||||
|
btn.textContent = cfg.submitLabel;
|
||||||
|
btn.disabled = false;
|
||||||
|
}, 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Close triggers */
|
||||||
|
closeBtn.addEventListener("click", function () { overlay._closeTrial(); });
|
||||||
|
confirmClose.addEventListener("click", function () { overlay._closeTrial(); });
|
||||||
|
overlay.addEventListener("click", function (e) {
|
||||||
|
if (e.target === overlay || !box.contains(e.target)) overlay._closeTrial();
|
||||||
|
});
|
||||||
|
document.addEventListener("keydown", function (e) {
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
if (locationSelect.classList.contains("open")) { _closeLoc(); return; }
|
||||||
|
if (overlay.style.display === "flex") overlay._closeTrial();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Form submit */
|
||||||
|
form.addEventListener("submit", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!cfg.webhook) {
|
||||||
|
console.warn("[mybizapp] No webhook URL set. Add window.TrialModalConfig.webhook.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var btn = form.querySelector(".tm-submit");
|
||||||
|
var formData = new FormData(form);
|
||||||
|
if (window._mbsTracking) {
|
||||||
|
formData = window._mbsTracking.appendToFormData(formData);
|
||||||
|
window._mbsTracking.trackEvent("Trial", { source: planField.value || "General Free Trial" });
|
||||||
|
}
|
||||||
|
btn.textContent = "Sending\u2026";
|
||||||
|
btn.disabled = true;
|
||||||
|
/* Show confirmation regardless — webhook may not return CORS headers */
|
||||||
|
fetch(cfg.webhook, { method: "POST", body: formData })
|
||||||
|
.then(function () { form.style.display = "none"; confirmDiv.style.display = "block"; })
|
||||||
|
.catch(function () { form.style.display = "none"; confirmDiv.style.display = "block"; });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Internal open helper (called after HTML is confirmed ready) -- */
|
||||||
|
function _openTrial(plan) {
|
||||||
|
var overlay = document.getElementById("tm-overlay");
|
||||||
|
if (overlay && overlay._openTrial) overlay._openTrial(plan);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================== */
|
||||||
|
/* CALENDAR MODAL */
|
||||||
|
/* ================================================================== */
|
||||||
|
|
||||||
|
var _calInjected = false;
|
||||||
|
|
||||||
|
/* --- show_calendar() ---------------------------------------------- */
|
||||||
|
function show_calendar() {
|
||||||
|
if (!_calInjected) _buildCalendarModal();
|
||||||
|
var overlay = document.getElementById("cal-overlay");
|
||||||
|
if (overlay) {
|
||||||
|
overlay.style.display = "flex";
|
||||||
|
document.body.style.overflow = "hidden";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Build and inject the calendar modal HTML --------------------- */
|
||||||
|
function _buildCalendarModal() {
|
||||||
|
var html = [
|
||||||
|
'<div id="cal-overlay" role="dialog" aria-modal="true" aria-labelledby="cal-title">',
|
||||||
|
' <div id="cal-box">',
|
||||||
|
' <div id="cal-header">',
|
||||||
|
' <h2 class="cal-title" id="cal-title">Book a Meeting</h2>',
|
||||||
|
' <button id="cal-close" aria-label="Close">×</button>',
|
||||||
|
' </div>',
|
||||||
|
' <iframe',
|
||||||
|
' id="cal-frame"',
|
||||||
|
' src="' + _esc(cfg.calendarUrl) + '"',
|
||||||
|
' title="Book a Meeting"',
|
||||||
|
' loading="lazy"',
|
||||||
|
' ></iframe>',
|
||||||
|
' </div>',
|
||||||
|
'</div>',
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
var tmp = document.createElement("div");
|
||||||
|
tmp.innerHTML = html;
|
||||||
|
document.body.appendChild(tmp.firstElementChild);
|
||||||
|
_calInjected = true;
|
||||||
|
|
||||||
|
_wireCalendarEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Wire calendar modal events ----------------------------------- */
|
||||||
|
function _wireCalendarEvents() {
|
||||||
|
var overlay = document.getElementById("cal-overlay");
|
||||||
|
var box = document.getElementById("cal-box");
|
||||||
|
var closeBtn = document.getElementById("cal-close");
|
||||||
|
|
||||||
|
function _closeCalendar() {
|
||||||
|
overlay.style.display = "none";
|
||||||
|
document.body.style.overflow = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
closeBtn.addEventListener("click", _closeCalendar);
|
||||||
|
overlay.addEventListener("click", function (e) {
|
||||||
|
if (e.target === overlay || !box.contains(e.target)) _closeCalendar();
|
||||||
|
});
|
||||||
|
document.addEventListener("keydown", function (e) {
|
||||||
|
if (e.key === "Escape" && overlay.style.display === "flex") _closeCalendar();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================== */
|
||||||
|
/* DOM AUTO-WIRING */
|
||||||
|
/* ================================================================== */
|
||||||
|
|
||||||
|
function _wireAll() {
|
||||||
|
/* Trial triggers */
|
||||||
|
var primary = document.getElementById("open-modal");
|
||||||
|
if (primary) {
|
||||||
|
primary.addEventListener("click", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
show_trial();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
document.querySelectorAll(cfg.triggerSelector).forEach(function (el) {
|
||||||
|
if (el.id === "open-modal") return; /* already handled above */
|
||||||
|
el.addEventListener("click", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
show_trial(el.getAttribute("data-plan") || undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("[mybizapp] invalid triggerSelector:", cfg.triggerSelector, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calendar triggers */
|
||||||
|
try {
|
||||||
|
document.querySelectorAll(cfg.calendarSelector).forEach(function (el) {
|
||||||
|
el.addEventListener("click", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
show_calendar();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("[mybizapp] invalid calendarSelector:", cfg.calendarSelector, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === "loading") {
|
||||||
|
document.addEventListener("DOMContentLoaded", _wireAll);
|
||||||
|
} else {
|
||||||
|
_wireAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================== */
|
||||||
|
/* UTILITIES */
|
||||||
|
/* ================================================================== */
|
||||||
|
|
||||||
|
function _esc(str) {
|
||||||
|
return String(str)
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================== */
|
||||||
|
/* PUBLIC API */
|
||||||
|
/* ================================================================== */
|
||||||
|
|
||||||
|
window.show_trial = show_trial;
|
||||||
|
window.show_calendar = show_calendar;
|
||||||
|
|
||||||
|
})();
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<div id="tm-overlay" role="dialog" aria-modal="true" aria-labelledby="tm-title">
|
||||||
|
<div id="tm-box">
|
||||||
|
<button id="tm-close" aria-label="Close">×</button>
|
||||||
|
<h2 class="tm-title" id="tm-title">Start Your 4 Weeks Free Trial</h2>
|
||||||
|
<p class="tm-subtitle">Test drive your back-office in a 4-week free trial.</p>
|
||||||
|
<form id="tm-form" novalidate>
|
||||||
|
<div class="tm-grid">
|
||||||
|
<div class="tm-field-group full">
|
||||||
|
<label class="tm-label" for="tm-email">Work Email</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
id="tm-email"
|
||||||
|
name="email"
|
||||||
|
class="tm-field"
|
||||||
|
placeholder="you@company.com"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="tm-field-group full tm-select" id="tm-location-select">
|
||||||
|
<label class="tm-label" for="tm-location">Server Location</label>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="tm-field tm-select-btn is-placeholder"
|
||||||
|
id="tm-location-btn"
|
||||||
|
aria-haspopup="listbox"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="tm-location-menu"
|
||||||
|
>
|
||||||
|
Select your server location
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
class="tm-select-menu"
|
||||||
|
id="tm-location-menu"
|
||||||
|
role="listbox"
|
||||||
|
aria-labelledby="tm-location-btn"
|
||||||
|
>
|
||||||
|
<button type="button" class="tm-select-option" data-value="boston">🇺🇸 Boston</button>
|
||||||
|
<button type="button" class="tm-select-option" data-value="manchester">🇬🇧 Manchester</button>
|
||||||
|
<button type="button" class="tm-select-option" data-value="mumbai">🇮🇳 Mumbai</button>
|
||||||
|
<button type="button" class="tm-select-option" data-value="saopaulo">🇧🇷 São Paulo</button>
|
||||||
|
<button type="button" class="tm-select-option" data-value="meppel">🇳🇱 Meppel</button>
|
||||||
|
<button type="button" class="tm-select-option" data-value="sydney">🇦🇺 Sydney</button>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" id="tm-location" name="location" value="" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" id="tm-product" name="product" value="odoo_19" />
|
||||||
|
<input type="hidden" id="tm-plan" name="plan" value="General Free Trial" />
|
||||||
|
<input type="hidden" name="utm_source" value="" />
|
||||||
|
<input type="hidden" name="utm_medium" value="" />
|
||||||
|
<input type="hidden" name="utm_campaign" value="" />
|
||||||
|
<input type="hidden" name="utm_term" value="" />
|
||||||
|
<input type="hidden" name="utm_content" value="" />
|
||||||
|
<button type="submit" class="tm-submit">Start Free Trial</button>
|
||||||
|
</form>
|
||||||
|
<div id="tm-confirm">
|
||||||
|
<p class="tm-confirm-title">Thank you! 🎉</p>
|
||||||
|
<p class="tm-confirm-sub">
|
||||||
|
We've received your request and a specialist will be in touch within one business day.
|
||||||
|
</p>
|
||||||
|
<button id="tm-confirm-close" class="tm-submit">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,644 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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, ">")
|
|
||||||
.replace(/"/g, """);
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildLocationOptions() {
|
|
||||||
return cfg.locations
|
|
||||||
.map(function (loc) {
|
|
||||||
return [
|
|
||||||
'<button type="button" class="tm-select-option"',
|
|
||||||
' data-value="' + escapeHtml(loc.value) + '">',
|
|
||||||
escapeHtml(loc.label),
|
|
||||||
"</button>",
|
|
||||||
].join("");
|
|
||||||
})
|
|
||||||
.join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
var html = [
|
|
||||||
'<div id="tm-overlay" role="dialog" aria-modal="true" aria-labelledby="tm-title">',
|
|
||||||
' <div id="tm-box">',
|
|
||||||
' <button id="tm-close" aria-label="Close">×</button>',
|
|
||||||
' <h2 class="tm-title" id="tm-title">' + escapeHtml(cfg.title) + "</h2>",
|
|
||||||
' <p class="tm-subtitle">' + escapeHtml(cfg.subtitle) + "</p>",
|
|
||||||
' <form id="tm-form" novalidate>',
|
|
||||||
' <div class="tm-grid">',
|
|
||||||
/* Email */
|
|
||||||
' <div class="tm-field-group full">',
|
|
||||||
' <label class="tm-label" for="tm-email">Work Email</label>',
|
|
||||||
' <input type="email" id="tm-email" name="email"',
|
|
||||||
' class="tm-field" placeholder="you@company.com" required />',
|
|
||||||
" </div>",
|
|
||||||
/* Location custom select */
|
|
||||||
' <div class="tm-field-group full tm-select" id="tm-location-select">',
|
|
||||||
' <label class="tm-label" for="tm-location">Server Location</label>',
|
|
||||||
' <button type="button" class="tm-field tm-select-btn is-placeholder"',
|
|
||||||
' id="tm-location-btn"',
|
|
||||||
' aria-haspopup="listbox" aria-expanded="false"',
|
|
||||||
' aria-controls="tm-location-menu">',
|
|
||||||
" Select your server location",
|
|
||||||
" </button>",
|
|
||||||
' <div class="tm-select-menu" id="tm-location-menu"',
|
|
||||||
' role="listbox" aria-labelledby="tm-location-btn">',
|
|
||||||
buildLocationOptions(),
|
|
||||||
" </div>",
|
|
||||||
' <input type="hidden" id="tm-location" name="location" value="" required />',
|
|
||||||
" </div>",
|
|
||||||
" </div>", /* /tm-grid */
|
|
||||||
/* Hidden fields */
|
|
||||||
' <input type="hidden" id="tm-product" name="product" value="' + escapeHtml(cfg.product) + '" />',
|
|
||||||
' <input type="hidden" id="tm-plan" name="plan" value="General Free Trial" />',
|
|
||||||
' <input type="hidden" name="utm_source" value="' + escapeHtml(cfg.utm_source) + '" />',
|
|
||||||
' <input type="hidden" name="utm_medium" value="' + escapeHtml(cfg.utm_medium) + '" />',
|
|
||||||
' <input type="hidden" name="utm_campaign" value="' + escapeHtml(cfg.utm_campaign) + '" />',
|
|
||||||
' <input type="hidden" name="utm_term" value="' + escapeHtml(cfg.utm_term) + '" />',
|
|
||||||
' <input type="hidden" name="utm_content" value="' + escapeHtml(cfg.utm_content) + '" />',
|
|
||||||
' <button type="submit" class="tm-submit">' + escapeHtml(cfg.submitLabel) + "</button>",
|
|
||||||
" </form>",
|
|
||||||
/* Confirmation */
|
|
||||||
' <div id="tm-confirm">',
|
|
||||||
' <p class="tm-confirm-title">' + escapeHtml(cfg.confirmTitle) + "</p>",
|
|
||||||
' <p class="tm-confirm-sub">' + escapeHtml(cfg.confirmSub) + "</p>",
|
|
||||||
' <button id="tm-confirm-close" class="tm-submit">Close</button>',
|
|
||||||
" </div>",
|
|
||||||
" </div>",
|
|
||||||
"</div>",
|
|
||||||
].join("\n");
|
|
||||||
|
|
||||||
var container = document.createElement("div");
|
|
||||||
container.innerHTML = html;
|
|
||||||
document.body.appendChild(container.firstElementChild);
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* 5. WIRE UP DOM REFERENCES */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
|
|
||||||
var overlay = document.getElementById("tm-overlay");
|
|
||||||
var box = document.getElementById("tm-box");
|
|
||||||
var closeBtn = document.getElementById("tm-close");
|
|
||||||
var form = document.getElementById("tm-form");
|
|
||||||
var confirmDiv = document.getElementById("tm-confirm");
|
|
||||||
var confirmClose = document.getElementById("tm-confirm-close");
|
|
||||||
var planField = document.getElementById("tm-plan");
|
|
||||||
var productField = document.getElementById("tm-product");
|
|
||||||
var locationField = document.getElementById("tm-location");
|
|
||||||
var locationSelect = document.getElementById("tm-location-select");
|
|
||||||
var locationBtn = document.getElementById("tm-location-btn");
|
|
||||||
var locationMenu = document.getElementById("tm-location-menu");
|
|
||||||
var defaultProduct = productField.value;
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* 6. LOCATION PICKER LOGIC */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
|
|
||||||
function syncLocationLabel() {
|
|
||||||
var value = locationField.value || "";
|
|
||||||
var selected = null;
|
|
||||||
|
|
||||||
locationMenu.querySelectorAll(".tm-select-option").forEach(function (opt) {
|
|
||||||
var isSel = opt.getAttribute("data-value") === value;
|
|
||||||
if (isSel) selected = opt;
|
|
||||||
opt.classList.toggle("is-selected", isSel);
|
|
||||||
opt.setAttribute("aria-selected", isSel ? "true" : "false");
|
|
||||||
});
|
|
||||||
|
|
||||||
locationBtn.textContent = selected
|
|
||||||
? selected.textContent.trim()
|
|
||||||
: "Select your server location";
|
|
||||||
locationBtn.classList.toggle("is-placeholder", !selected);
|
|
||||||
locationBtn.setAttribute(
|
|
||||||
"aria-expanded",
|
|
||||||
locationSelect.classList.contains("open") ? "true" : "false"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function openLocationMenu() {
|
|
||||||
locationSelect.classList.add("open");
|
|
||||||
syncLocationLabel();
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeLocationMenu() {
|
|
||||||
locationSelect.classList.remove("open");
|
|
||||||
syncLocationLabel();
|
|
||||||
}
|
|
||||||
|
|
||||||
locationBtn.addEventListener("click", function () {
|
|
||||||
locationSelect.classList.contains("open")
|
|
||||||
? closeLocationMenu()
|
|
||||||
: openLocationMenu();
|
|
||||||
});
|
|
||||||
|
|
||||||
locationMenu.querySelectorAll(".tm-select-option").forEach(function (opt) {
|
|
||||||
opt.addEventListener("click", function () {
|
|
||||||
locationField.value = opt.getAttribute("data-value") || "";
|
|
||||||
closeLocationMenu();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Close location dropdown on outside click */
|
|
||||||
document.addEventListener("click", function (e) {
|
|
||||||
if (!locationSelect.contains(e.target)) closeLocationMenu();
|
|
||||||
});
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* 7. MODAL OPEN / CLOSE */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
|
|
||||||
function openModal(plan) {
|
|
||||||
planField.value = plan || "General Free Trial";
|
|
||||||
/* Keep product pinned to configured value — never expose or change */
|
|
||||||
productField.value = defaultProduct;
|
|
||||||
/* Apply tracking fields if window._mbsTracking is present */
|
|
||||||
if (window._mbsTracking) window._mbsTracking.applyToFields(form);
|
|
||||||
closeLocationMenu();
|
|
||||||
overlay.style.display = "flex";
|
|
||||||
document.body.style.overflow = "hidden";
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeModal() {
|
|
||||||
overlay.style.display = "none";
|
|
||||||
document.body.style.overflow = "";
|
|
||||||
/* Reset after the fade (200 ms gives any CSS transition time) */
|
|
||||||
setTimeout(function () {
|
|
||||||
form.style.display = "";
|
|
||||||
confirmDiv.style.display = "none";
|
|
||||||
form.reset();
|
|
||||||
planField.value = "General Free Trial";
|
|
||||||
productField.value = defaultProduct;
|
|
||||||
locationField.value = "";
|
|
||||||
if (window._mbsTracking) window._mbsTracking.applyToFields(form);
|
|
||||||
closeLocationMenu();
|
|
||||||
var btn = form.querySelector(".tm-submit");
|
|
||||||
btn.textContent = cfg.submitLabel;
|
|
||||||
btn.disabled = false;
|
|
||||||
}, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Built-in close triggers --- */
|
|
||||||
closeBtn.addEventListener("click", closeModal);
|
|
||||||
confirmClose.addEventListener("click", closeModal);
|
|
||||||
|
|
||||||
overlay.addEventListener("click", function (e) {
|
|
||||||
if (e.target === overlay || !box.contains(e.target)) closeModal();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("keydown", function (e) {
|
|
||||||
if (e.key === "Escape") {
|
|
||||||
if (locationSelect.classList.contains("open")) {
|
|
||||||
closeLocationMenu();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (overlay.style.display === "flex") closeModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* 8. WIRE TRIGGER ELEMENTS */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
|
|
||||||
function attachTriggers() {
|
|
||||||
/* Primary #open-modal (handled separately for legacy compat) */
|
|
||||||
var primary = document.getElementById("open-modal");
|
|
||||||
if (primary) {
|
|
||||||
primary.addEventListener("click", function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
openModal();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Any element matching triggerSelector */
|
|
||||||
try {
|
|
||||||
document.querySelectorAll(cfg.triggerSelector).forEach(function (el) {
|
|
||||||
/* Avoid double-binding the primary element */
|
|
||||||
if (el.id === "open-modal") return;
|
|
||||||
el.addEventListener("click", function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
openModal(el.getAttribute("data-plan") || undefined);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.warn("[TrialModal] invalid triggerSelector:", cfg.triggerSelector, err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Run immediately if DOM is ready, otherwise wait */
|
|
||||||
if (document.readyState === "loading") {
|
|
||||||
document.addEventListener("DOMContentLoaded", attachTriggers);
|
|
||||||
} else {
|
|
||||||
attachTriggers();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* 9. FORM SUBMISSION */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
|
|
||||||
form.addEventListener("submit", function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (!cfg.webhook) {
|
|
||||||
console.warn("[TrialModal] No webhook URL configured. Set window.TrialModalConfig.webhook.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var btn = form.querySelector(".tm-submit");
|
|
||||||
var formData = new FormData(form);
|
|
||||||
|
|
||||||
/* Append tracking data if available */
|
|
||||||
if (window._mbsTracking) {
|
|
||||||
formData = window._mbsTracking.appendToFormData(formData);
|
|
||||||
window._mbsTracking.trackEvent("Trial", {
|
|
||||||
source: planField.value || "General Free Trial",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
btn.textContent = "Sending\u2026";
|
|
||||||
btn.disabled = true;
|
|
||||||
|
|
||||||
/* Show confirmation regardless of network outcome
|
|
||||||
(webhook may not return CORS-safe headers) */
|
|
||||||
fetch(cfg.webhook, { method: "POST", body: formData })
|
|
||||||
.then(function () {
|
|
||||||
form.style.display = "none";
|
|
||||||
confirmDiv.style.display = "block";
|
|
||||||
})
|
|
||||||
.catch(function () {
|
|
||||||
form.style.display = "none";
|
|
||||||
confirmDiv.style.display = "block";
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* 10. PUBLIC API */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
|
|
||||||
window.TrialModal = {
|
|
||||||
/**
|
|
||||||
* Open the trial modal.
|
|
||||||
* @param {string} [plan] - Optional plan name pre-filled in hidden field.
|
|
||||||
*/
|
|
||||||
open: openModal,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the trial modal and reset the form.
|
|
||||||
*/
|
|
||||||
close: closeModal,
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
+5
-5
@@ -1662,8 +1662,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="nav-right">
|
<div class="nav-right">
|
||||||
<a
|
<a
|
||||||
class="nav-cta-book plausible-event-name=Meeting"
|
class="nav-cta-book open-calendar plausible-event-name=Meeting"
|
||||||
href="#story"
|
href="#"
|
||||||
>Book Meeting</a
|
>Book Meeting</a
|
||||||
>
|
>
|
||||||
<div class="nav-chat" id="nav-chat">
|
<div class="nav-chat" id="nav-chat">
|
||||||
@@ -2054,8 +2054,8 @@
|
|||||||
style="margin-top: auto; justify-content: flex-end"
|
style="margin-top: auto; justify-content: flex-end"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="btn-secondary plausible-event-name=Meeting"
|
class="btn-secondary open-calendar plausible-event-name=Meeting"
|
||||||
href="#pricing"
|
href="#"
|
||||||
>Book Meeting</a
|
>Book Meeting</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@@ -3210,7 +3210,7 @@
|
|||||||
"https://002-001-5dd6e535-4d1c-46bc-9bd9-42ad4bc5f082.odoo4projects.com/webhook/c25169c6-4234-4b47-8e74-612b9539da0a",
|
"https://002-001-5dd6e535-4d1c-46bc-9bd9-42ad4bc5f082.odoo4projects.com/webhook/c25169c6-4234-4b47-8e74-612b9539da0a",
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<script src="components/trial_modal.js"></script>
|
<script src="components/mybizapp.js"></script>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@slicemypage/motionflow@latest/dist/motionflow.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/@slicemypage/motionflow@latest/dist/motionflow.min.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user