PageSourceSearch

https://optimizer365.ai/assets/site.js?v=forms-20261002

js optimizer365.ai collected 2026-10-05 12:22:51 UTC 21,386 bytes, 485 lines download raw bytes

1/* ==========================================================================
2   OPTIMIZER365 CONFIG
3
4   Everything a marketer needs to change lives in CONFIG below.
5   Nothing under it needs editing for the site to run.
6   ========================================================================== */
7
8var CONFIG = {
9
10  /* --- booking ------------------------------------------------------
11     A real link, not a form. It opens cal.com directly.               */
12  BOOKING_URL: "https://cal.com/optimizer365/30min",
13  NEWSLETTER_URL: "https://www.linkedin.com/newsletters/this-week-in-cloudops-7459508050698321920/",
14  COMMUNITY_NEWSLETTER_URL: "https://thecloudopscollective.beehiiv.com/subscribe",
15
16  /* Enquiries are addressed to [email protected].
17     FormSubmit handles delivery after the recipient activates the service.
18     Native submission preserves its CAPTCHA and delivery confirmation flow. */
19  FORM_ENDPOINT: "https://formsubmit.co/[email protected]", // Recipient activation verified on the Cloudflare site.
20
21  /* --- contact ----------------------------------------------------- */
22  EMAIL:    "[email protected]",
23  LINKEDIN: "https://www.linkedin.com/company/optimizer365",
24
25  /* --- resources feed ----------------------------------------------
26     Elfsight app id for the Optimizer365 LinkedIn feed. Nandan's personal
27     feed is deliberately not included. Blank hides the embed and leaves
28     the written fallback in place.                                     */
29  ELFSIGHT_APP_ID: "52d0aed8-8836-4b01-a737-ea9fd4432755",
30
31  /* --- analytics -----------------------------------------------------
32     Google Analytics 4. GA sets cookies, so nothing loads until a
33     visitor accepts. Blank this and analytics stops entirely, the
34     banner disappears, and the cookies paragraph in the privacy notice
35     has to change back.                                               */
36  GA_ID: "",
37
38  /* --- switches ----------------------------------------------------- */
39  SHOW_EVENT_BANNER: false,
40  EVENT_NAME:   "",
41  EVENT_DETAIL: "",
42  EVENT_URL:    "",
43
44  /* --- legal --------------------------------------------------------- */
45  LEGAL_ENTITY: "Itenic Holding Pty Ltd (ABN 30 633 888 561) trading as Optimizer365"
46};
47
48/* ==========================================================================
49   Wiring only below this line.
50   ========================================================================== */
51(function () {
52  "use strict";
53
54  function track(name) {
55    if (window.dataLayer) window.dataLayer.push({ event: "cta_click", cta: name });
56    if (window.gtag) window.gtag("event", "cta_click", { cta: name });
57  }
58
59  function mailto(subject, body) {
60    return "mailto:" + CONFIG.EMAIL +
61      "?subject=" + encodeURIComponent(subject) +
62      "&body=" + encodeURIComponent(body);
63  }
64
65  // Footer enquiries work directly in the browser, including devices without a mail app.
66  document.querySelectorAll('[data-cta="foot-email"]').forEach(function (link) {
67    link.href = "/contact/#contactForm";
68  });
69
70  /* ---------- the forms behind each call to action --------------------
71     One dialog serves them all. The fields change with the purpose, so
72     we never ask for something we do not need to answer the request. */
73  var FORMS = {
74    assessment: {
75      title: "Get a free assessment",
76      note: "Read only access. Nothing installed. Free for your first environment.",
77      submit: "Request the assessment",
78      fields: [
79        { n: "Name",    l: "Your name",  t: "text",  req: true,  ac: "name" },
80        { n: "Email",   l: "Work email", t: "email", req: true,  ac: "email" },
81        { n: "Company", l: "Company",    t: "text",  ac: "organization" },
82        { n: "Environment", l: "Which environment", t: "select",
83          o: ["Azure", "AWS", "Microsoft 365", "A mix of them"] },
84        { n: "Users",   l: "Roughly how many users", t: "text" }
85      ]
86    },
87    quote: {
88      title: "Ask for a quote",
89      note: "A quote takes one working day. We do not book you into a discovery call first.",
90      submit: "Ask for a quote",
91      fields: [
92        { n: "Name",    l: "Your name",  t: "text",  req: true, ac: "name" },
93        { n: "Email",   l: "Work email", t: "email", req: true, ac: "email" },
94        { n: "Company", l: "Company",    t: "text",  ac: "organization" },
95        { n: "Scope",   l: "What you want priced", t: "textarea",
96          p: "One environment, a client base, or ongoing coverage. Rough size is enough." }
97      ]
98    },
99    sample: {
100      title: "Send me a sample report",
101      note: "A real report with the client details removed.",
102      submit: "Send it to me",
103      fields: [
104        { n: "Name",  l: "Your name",  t: "text",  req: true, ac: "name" },
105        { n: "Email", l: "Work email", t: "email", req: true, ac: "email" },
106        { n: "Assessment", l: "Which assessment", t: "select",
107          o: ["Azure", "AWS", "Microsoft 365", "Not sure yet"] }
108      ]
109    },
110    newsletter: {
111      title: "Get the newsletter",
112      note: "Every week or two. Unsubscribe from any issue.",
113      submit: "Subscribe free",
114      fields: [
115        { n: "Email", l: "Work email", t: "email", req: true, ac: "email" },
116        { n: "Name",  l: "Your name",  t: "text",  ac: "name" }
117      ]
118    },
119    index: {
120      title: "Cloud Maturity Index",
121      note: "We will email you when the first edition is published.",
122      submit: "Tell me when it lands",
123      fields: [
124        { n: "Email",   l: "Work email", t: "email", req: true, ac: "email" },
125        { n: "Name",    l: "Your name",  t: "text",  ac: "name" },
126        { n: "Company", l: "Company",    t: "text",  ac: "organization" }
127      ]
128    }
129  };
130
131  /* ---------- dialog --------------------------------------------------- */
132  var dlg = null, lastFocus = null;
133
134  function buildField(f, i) {
135    var id = "df-" + i;
136    var input;
137    if (f.t === "textarea")
138      input = '<textarea id="' + id + '" name="' + f.n + '"' +
139        (f.p ? ' placeholder="' + f.p + '"' : "") + (f.req ? " required" : "") + "></textarea>";
140    else if (f.t === "select")
141      input = '<select id="' + id + '" name="' + f.n + '">' +
142        f.o.map(function (o) { return "<option>" + o + "</option>"; }).join("") + "</select>";
143    else
144      input = '<input id="' + id + '" name="' + f.n + '" type="' + f.t + '"' +
145        (f.ac ? ' autocomplete="' + f.ac + '"' : "") + (f.req ? " required" : "") + ">";
146    return '<div class="field"><label for="' + id + '">' + f.l + "</label>" + input + "</div>";
147  }
148
149  function openDialog(purpose, source) {
150    var spec = FORMS[purpose];
151    if (!spec) return;
152    lastFocus = document.activeElement;
153
154    if (!dlg) {
155      dlg = document.createElement("div");
156      dlg.className = "modal";
157      dlg.setAttribute("role", "dialog");
158      dlg.setAttribute("aria-modal", "true");
159      document.body.appendChild(dlg);
160    }
161
162    dlg.innerHTML =
163      '<div class="modal-back" data-close></div>' +
164      '<div class="modal-card" role="document">' +
165        '<button class="modal-x" type="button" data-close aria-label="Close">' +
166          '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" ' +
167          'stroke-linecap="round"><path d="M18 6L6 18M6 6l12 12"/></svg></button>' +
168        '<h2 id="modalTitle">' + spec.title + "</h2>" +
169        '<form class="form mt4" novalidate>' +
170          '<input type="text" name="website" tabindex="-1" autocomplete="off" ' +
171            'aria-hidden="true" class="hp">' +
172          spec.fields.map(buildField).join("") +
173          '<button class="btn btn-1 btn-lg mt2" type="submit">' + spec.submit + "</button>" +
174          '<p class="form-note">' + spec.note + "</p>" +
175          '<p class="form-msg" role="status" aria-live="polite"></p>' +
176        "</form>" +
177      "</div>";
178
179    dlg.setAttribute("aria-labelledby", "modalTitle");
180    dlg.classList.add("open");
181    document.body.classList.add("noscroll");
182    document.querySelectorAll("body > header, body > main, body > footer").forEach(function (el) { el.inert = true; });
183    var first = dlg.querySelector("input:not(.hp), select, textarea");
184    if (first) first.focus();
185
186    wireForm(dlg.querySelector("form"), purpose, source);
187  }
188
189  function closeDialog() {
190    if (!dlg) return;
191    dlg.classList.remove("open");
192    document.body.classList.remove("noscroll");
193    document.querySelectorAll("body > header, body > main, body > footer").forEach(function (el) { el.inert = false; });
194    if (lastFocus && lastFocus.focus) lastFocus.focus();
195  }
196
197  document.addEventListener("click", function (e) {
198    if (e.target.closest("[data-close]")) closeDialog();
199  });
200  document.addEventListener("keydown", function (e) {
201    if (e.key === "Escape") closeDialog();
202    if (e.key === "Tab" && dlg && dlg.classList.contains("open")) {
203      var f = dlg.querySelectorAll("button, input:not(.hp), select, textarea, a[href]");
204      if (!f.length) return;
205      var first = f[0], last = f[f.length - 1];
206      if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
207      else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
208    }
209  });
210
211  /* ---------- submitting ------------------------------------------------ */
212  function wireForm(form, purpose, source) {
213    if (!form || form.dataset.wired) return;
214    form.dataset.wired = "1";
215    if (CONFIG.FORM_ENDPOINT.indexOf("https://formsubmit.co/") === 0) {
216      form.action = CONFIG.FORM_ENDPOINT;
217      form.method = "POST";
218      var serviceNote = document.createElement("p");
219      serviceNote.className = "form-note";
220      serviceNote.textContent = "Continue to the secure submission check to send your enquiry to [email protected]. FormSubmit processes the delivery.";
221      form.appendChild(serviceNote);
222    }
223    if (!CONFIG.FORM_ENDPOINT) {
224      var deliveryNote = document.createElement("p");
225      deliveryNote.className = "form-note";
226      deliveryNote.textContent = "This opens a prepared email. Send it from your email app to complete your request.";
227      form.appendChild(deliveryNote);
228      var sendButton = form.querySelector('button[type="submit"]');
229      if (sendButton) sendButton.textContent = "Continue by email";
230    }
231
232    form.addEventListener("submit", function (e) {
233      e.preventDefault();
234      var msg = form.querySelector(".form-msg");
235      if (!msg) {
236        msg = document.createElement("p");
237        msg.className = "form-msg";
238        msg.setAttribute("role", "status");
239        msg.setAttribute("aria-live", "polite");
240        form.appendChild(msg);
241      }
242      var btn = form.querySelector('button[type="submit"]');
243      if (!form.reportValidity()) return;
244      var data = {};
245      new FormData(form).forEach(function (v, k) { data[k] = v; });
246      data.purpose = purpose;
247      data.source = source || "";
248      data.email = data.Email || data.email || "";
249      data.name = data.Name || data.name || "";
250
251      if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(data.email)) {
252        if (msg) { msg.textContent = "Please enter a work email address."; msg.className = "form-msg bad"; }
253        return;
254      }
255
256      /* no backend configured, so compose an email. it still works. */
257      if (!CONFIG.FORM_ENDPOINT) {
258        var lines = Object.keys(data)
259          .filter(function (k) { return k !== "website" && data[k]; })
260          .map(function (k) { return k + ": " + data[k]; });
261        var subject = FORMS[purpose] ? FORMS[purpose].title : "Website enquiry";
262        var draftUrl = mailto(subject, lines.join("\n") + "\n");
263        msg.textContent = "Your request has not been sent yet. Send the prepared email from your email app, or contact " + CONFIG.EMAIL + ". ";
264        var draftLink = document.createElement("a");
265        draftLink.href = draftUrl;
266        draftLink.textContent = "Open email draft";
267        msg.appendChild(draftLink);
268        window.location.href = draftUrl;
269        return;
270      }
271
272      if (CONFIG.FORM_ENDPOINT.indexOf("https://formsubmit.co/") === 0) {
273        if (data.website) return;
274        var deliveryFields = {
275          _subject: "Optimizer365: " + (FORMS[purpose] ? FORMS[purpose].title : "Website enquiry"),
276          _template: "table", _replyto: data.email,
277          purpose: purpose, source: source || "", email: data.email,
278          _url: window.location.href
279        };
280        Object.keys(deliveryFields).forEach(function (key) {
281          var field = form.querySelector('input[name="' + key + '"]');
282          if (!field) { field = document.createElement("input"); field.type = "hidden"; field.name = key; form.appendChild(field); }
283          field.value = deliveryFields[key];
284        });
285        HTMLFormElement.prototype.submit.call(form);
286        return;
287      }
288
289      if (btn) { btn.disabled = true; btn.dataset.label = btn.textContent; btn.textContent = "Sending"; }
290      if (msg) { msg.textContent = ""; msg.className = "form-msg"; }
291
292      fetch(CONFIG.FORM_ENDPOINT, {
293        method: "POST",
294        headers: { "content-type": "application/json" },
295        body: JSON.stringify(data)
296      })
297        .then(function (r) {
298          if (!r.ok) throw new Error("Submission failed");
299          return r.json();
300        })
301        .then(function (res) {
302          if (btn) { btn.disabled = false; btn.textContent = btn.dataset.label; }
303          if (res && res.ok) {
304            track(purpose + "-submitted");
305            var done = document.createElement("div");
306            done.className = "form-done";
307            var heading = document.createElement("h3");
308            heading.textContent = res.message || "Thanks. We have received your request.";
309            done.appendChild(heading);
310            form.replaceChildren(done);
311          } else if (msg) {
312            msg.textContent = (res && res.message) ||
313              "That did not go through. Please email " + CONFIG.EMAIL + ".";
314            msg.className = "form-msg bad";
315          }
316        })
317        .catch(function () {
318          if (btn) { btn.disabled = false; btn.textContent = btn.dataset.label; }
319          if (msg) {
320            msg.textContent = "That did not go through. Please email " + CONFIG.EMAIL + ".";
321            msg.className = "form-msg bad";
322          }
323        });
324    });
325  }
326
327  /* ---------- calls to action ------------------------------------------- */
328  document.querySelectorAll("[data-action]").forEach(function (el) {
329    var action = el.getAttribute("data-action");
330    if (action === "booking" || action === "newsletter" || action === "community-newsletter") {
331      if (CONFIG.BOOKING_URL) {
332        el.setAttribute("href", action === "newsletter" ? CONFIG.NEWSLETTER_URL : action === "community-newsletter" ? CONFIG.COMMUNITY_NEWSLETTER_URL : CONFIG.BOOKING_URL);
333        el.setAttribute("target", "_blank");
334        el.setAttribute("rel", "noopener");
335        el.addEventListener("click", function () { track(el.getAttribute("data-cta") || action); });
336      }
337      return;
338    }
339    if (!FORMS[action]) return;
340    // Keep the HTML fallback destination if JavaScript is interrupted.
341    el.addEventListener("click", function (e) {
342      e.preventDefault();
343      track(el.getAttribute("data-cta") || action);
344      openDialog(action, el.getAttribute("data-cta") || "");
345    });
346  });
347
348  document.addEventListener("click", function (e) {
349    var el = e.target.closest("[data-cta]");
350    if (el && !el.hasAttribute("data-action")) track(el.getAttribute("data-cta"));
351  });
352
353  /* ---------- the contact page form ------------------------------------- */
354  wireForm(document.getElementById("contactForm"), "contact", "contact-page");
355  wireForm(document.getElementById("quoteForm"), "quote", "pricing-page-inline");
356
357  /* ---------- event banner ---------------------------------------------- */
358  var banner = document.getElementById("eventBanner");
359  if (banner) {
360    if (!CONFIG.SHOW_EVENT_BANNER || !CONFIG.EVENT_NAME) {
361      banner.remove();
362    } else {
363      var n = banner.querySelector("[data-event-name]");
364      var d = banner.querySelector("[data-event-detail]");
365      var l = banner.querySelector("[data-event-link]");
366      if (n) n.textContent = CONFIG.EVENT_NAME;
367      if (d) d.textContent = CONFIG.EVENT_DETAIL;
368      if (l && CONFIG.EVENT_URL) l.setAttribute("href", CONFIG.EVENT_URL);
369    }
370  }
371
372  /* ---------- resources feed --------------------------------------------
373     The embed is third party. It can be slow, blocked by an extension, or
374     simply empty. Write something useful in immediately and only swap it
375     for the embed once the embed has actually painted.                  */
376  var feed = document.getElementById("linkedinFeed");
377  if (feed) {
378    var fallback =
379      '<div class="feed-alt">' +
380        "<h4>Recent posts</h4>" +
381        "<p>We publish on cloud cost, licensing and security posture as we go. " +
382          "The full stream lives on LinkedIn.</p>" +
383        '<a class="btn btn-2" href="' + CONFIG.LINKEDIN + '" rel="noopener" ' +
384        'target="_blank" data-cta="feed-fallback">Read it on LinkedIn</a>' +
385      "</div>";
386    feed.innerHTML = fallback;
387
388    if (CONFIG.ELFSIGHT_APP_ID) {
389      var consent = document.createElement("p");
390      consent.className = "form-note";
391      consent.textContent = "Loading the feed connects to Elfsight and may load LinkedIn content. Those services receive browser information and may use cookies.";
392      feed.appendChild(consent);
393      var load = document.createElement("button");
394      load.type = "button"; load.className = "btn btn-2";
395      load.textContent = "Load LinkedIn feed";
396      feed.appendChild(load);
397      load.addEventListener("click", function () {
398        load.disabled = true; load.textContent = "Loading feed";
399        var host = document.createElement("div");
400        host.className = "elfsight-app-" + CONFIG.ELFSIGHT_APP_ID;
401        feed.appendChild(host);
402        var script = document.createElement("script");
403        script.src = "https://elfsightcdn.com/platform.js"; script.async = true;
404        function failed() { load.textContent = "Feed unavailable. Use the LinkedIn link above."; }
405        script.onerror = failed;
406        script.onload = function () { setTimeout(function () { if (host.offsetHeight < 60) failed(); else load.remove(); }, 6000); };
407        document.body.appendChild(script);
408      }, {once: true});
409    }
410  }
411
412  /* ---------- legal line -------------------------------------------------- */
413  document.querySelectorAll("[data-legal]").forEach(function (el) {
414    el.textContent = "© " + new Date().getFullYear() + " " +
415      CONFIG.LEGAL_ENTITY + ". All rights reserved.";
416  });
417
418  /* ---------- collapsing header ---------------------------------------------
419     past the hero the bar detaches into a pill. class only; css does the rest. */
420  var scrolled = false;
421  function onScroll() {
422    var now = window.scrollY > 320;
423    if (now !== scrolled) {
424      scrolled = now;
425      document.body.classList.toggle("scrolled", now);
426    }
427  }
428  window.addEventListener("scroll", onScroll, { passive: true });
429  onScroll();
430
431  /* ---------- mobile navigation -------------------------------------------- */
432  var toggle = document.querySelector(".navtoggle");
433  var links = document.querySelector(".navlinks");
434  if (toggle && links) {
435    toggle.addEventListener("click", function () {
436      var open = links.classList.toggle("open");
437      toggle.setAttribute("aria-expanded", open ? "true" : "false");
438      toggle.setAttribute("aria-label", open ? "Close menu" : "Open menu");
439    });
440  }
441
442  /* ---------- analytics, only after consent -------------------------------
443     GA sets cookies, so it does not load until a visitor says yes. The
444     choice is remembered so the banner is asked once.                     */
445  function loadGA() {
446    if (!CONFIG.GA_ID || window.gtag) return;
447    var s = document.createElement("script");
448    s.async = true;
449    s.src = "https://www.googletagmanager.com/gtag/js?id=" + CONFIG.GA_ID;
450    document.head.appendChild(s);
451    window.dataLayer = window.dataLayer || [];
452    window.gtag = function () { window.dataLayer.push(arguments); };
453    window.gtag("js", new Date());
454    window.gtag("config", CONFIG.GA_ID, { anonymize_ip: true });
455  }
456
457  var KEY = "o365-cookies";
458  var choice = null;
459  try { choice = localStorage.getItem(KEY); } catch (e) {}
460
461  if (CONFIG.GA_ID) {
462    if (choice === "yes") {
463      loadGA();
464    } else if (choice !== "no") {
465      var bar = document.createElement("div");
466      bar.className = "cookiebar";
467      bar.innerHTML =
468        "<p>We use Google Analytics to see which pages are read. It sets cookies. " +
469        'Nothing loads until you agree. <a href="/privacy/">What we collect</a>.</p>' +
470        '<div class="cookiebtns">' +
471          '<button class="btn btn-2 btn-sm" type="button" data-cookie="no">No thanks</button>' +
472          '<button class="btn btn-1 btn-sm" type="button" data-cookie="yes">Accept</button>' +
473        "</div>";
474      document.body.appendChild(bar);
475      bar.addEventListener("click", function (e) {
476        var b = e.target.closest("[data-cookie]");
477        if (!b) return;
478        var v = b.getAttribute("data-cookie");
479        try { localStorage.setItem(KEY, v); } catch (err) {}
480        if (v === "yes") loadGA();
481        bar.remove();
482      });
483    }
484  }
485})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.