diff --git a/components/mybizapp.js b/components/mybizapp.js
new file mode 100644
index 0000000..17e9b44
--- /dev/null
+++ b/components/mybizapp.js
@@ -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 '";
+ }).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 = [
+ '
',
+ '
',
+ ' ',
+ ' ',
+ '
',
+ '
',
+ ].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, """);
+ }
+
+ /* ================================================================== */
+ /* PUBLIC API */
+ /* ================================================================== */
+
+ window.show_trial = show_trial;
+ window.show_calendar = show_calendar;
+
+})();
diff --git a/components/trial_modal.html b/components/trial_modal.html
new file mode 100644
index 0000000..bedad3c
--- /dev/null
+++ b/components/trial_modal.html
@@ -0,0 +1,64 @@
+
+
+
+
Start Your 4 Weeks Free Trial
+
Test drive your back-office in a 4-week free trial.
+
+
+
Thank you! 🎉
+
+ We've received your request and a specialist will be in touch within one business day.
+
+
+
+
+
diff --git a/components/trial_modal.js b/components/trial_modal.js
deleted file mode 100644
index db94d08..0000000
--- a/components/trial_modal.js
+++ /dev/null
@@ -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, """);
- }
-
- function buildLocationOptions() {
- return cfg.locations
- .map(function (loc) {
- return [
- '",
- ].join("");
- })
- .join("\n");
- }
-
- var html = [
- '',
- '
',
- '
',
- '
' + escapeHtml(cfg.title) + "
",
- '
' + escapeHtml(cfg.subtitle) + "
",
- '
",
- /* Confirmation */
- '
',
- '
' + escapeHtml(cfg.confirmTitle) + "
",
- '
' + escapeHtml(cfg.confirmSub) + "
",
- '
',
- "
",
- "
",
- "
",
- ].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,
- };
-})();
diff --git a/index.html b/index.html
index 5eb3636..8c6ba5b 100644
--- a/index.html
+++ b/index.html
@@ -1662,8 +1662,8 @@
Book Meeting
@@ -2054,8 +2054,8 @@
style="margin-top: auto; justify-content: flex-end"
>
Book Meeting
@@ -3210,7 +3210,7 @@
"https://002-001-5dd6e535-4d1c-46bc-9bd9-42ad4bc5f082.odoo4projects.com/webhook/c25169c6-4234-4b47-8e74-612b9539da0a",
};
-
+