1function escReg(value) { 2 return String(value ?? "") 3 .replaceAll("&", "&") 4 .replaceAll("<", "<") 5 .replaceAll(">", ">") 6 .replaceAll('"', """) 7 .replaceAll("'", "'"); 8} 9 10 11const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i; 12const US_PHONE_REGEX = /^(?:\+?1[\s.-]?)?(?:\([0-9]{3}\)|[0-9]{3})[\s.-]?[0-9]{3}[\s.-]?[0-9]{4}$/; 13 14function normalizeUsPhone(value) { 15 const digits = String(value || "").replace(/\D/g, ""); 16 const ten = digits.length === 11 && digits.startsWith("1") ? digits.slice(1) : digits; 17 if (ten.length !== 10) return String(value || "").trim(); 18 return `(${ten.slice(0, 3)}) ${ten.slice(3, 6)}-${ten.slice(6)}`; 19} 20 21function isValidEmail(value) { 22 return EMAIL_REGEX.test(String(value || "").trim()); 23} 24 25function isValidUsPhone(value) { 26 const phone = String(value || "").trim(); 27 if (!phone) return true; 28 return US_PHONE_REGEX.test(phone); 29} 30 31function regInputStyle(theme = "dark") { 32 if (theme === "light") { 33 return `width:100%;box-sizing:border-box;border:1px solid #cbd5e1;background:#ffffff;color:#0f172a;border-radius:14px;padding:13px 14px;outline:none;font-size:14px;font-weight:650;font-family:inherit;box-shadow:0 1px 2px rgba(15,23,42,.04);`; 34 } 35 return `width:100%;box-sizing:border-box;border:1px solid rgba(255,255,255,.22);background:rgba(255,255,255,.12);color:white;border-radius:14px;padding:13px 14px;outline:none;font-size:14px;font-weight:650;font-family:inherit;`; 36} 37 38export const ENQUIRY_COURSE_OPTIONS = [ 39 "AI Tools for Work: ChatGPT, Copilot, Excel, Emails & Meetings â Free Demo", 40 "Business Analyst with AI, Agile & Jira â Free Demo for US Learners", 41 "Cybersecurity GRC Analyst with AI Governance & Responsible AI â Live Demo", 42 "Cybersecurity Analyst Foundation: Start with CompTIA Security+ â Live Demo", 43 "Software Testing Demo for US Learners: API Testing, Selenium, Python, JIRA", 44]; 45 46export function renderRegistrationForm({ 47 pageType = "general", 48 itemId = "", 49 itemTitle = "", 50 title = "Request Registration Info",
51 subtitle = "Share your details and our team will contact you shortly.", 52 compact = false, 53 containerId = "", 54 theme = "dark", 55 showCourseField = false, 56 courseOptions = ENQUIRY_COURSE_OPTIONS, 57 coursePlaceholder = "Course or program you are interested in", 58 badgeLabel = "Free Guidance", 59 submitLabel = "Request Registration Info", 60} = {}) { 61 const idAttr = containerId ? ` id="${escReg(containerId)}"` : ""; 62 const isLight = theme === "light"; 63 64 const cardStyle = isLight 65 ? `background:#ffffff;border-radius:${compact ? "22px" : "26px"};padding:${compact ? "24px" : "30px"};color:#0f172a;box-shadow:0 18px 45px rgba(15,23,42,.10);border:1px solid #dbeafe;position:relative;overflow:hidden;` 66 : `background:linear-gradient(135deg,#0f172a 0%,#134e4a 55%,#10b981 100%);border-radius:${compact ? "22px" : "26px"};padding:${compact ? "22px" : "30px"};color:white;box-shadow:0 20px 48px rgba(15,23,42,.18);border:1px solid rgba(255,255,255,.14);position:relative;overflow:hidden;`; 67 68 const badgeStyle = isLight 69 ? `display:inline-flex;align-items:center;gap:8px;padding:7px 12px;border-radius:999px;background:#ecfdf5;border:1px solid #bbf7d0;color:#047857;font-size:11px;font-weight:900;text-transform:uppercase;letter-spacing:.08em;margin-bottom:14px;` 70 : `display:inline-flex;align-items:center;gap:8px;padding:7px 12px;border-radius:999px;background:rgba(255,255,255,.14);border:1px solid rgba(255,255,255,.22);font-size:11px;font-weight:900;text-transform:uppercase;letter-spacing:.08em;margin-bottom:14px;`; 71 72 const subtitleColor = isLight ? "#475569" : "rgba(255,255,255,.78)"; 73 const programColor = isLight ? "#059669" : "#a7f3d0"; 74 75 return ` 76 <div${idAttr} 77 class="itl-registration-card ${isLight ? "itl-registration-card-light" : ""}" 78 data-registration-form 79 data-page-type="${escReg(pageType)}" 80 data-item-id="${escReg(itemId)}" 81 data-item-title="${escReg(itemTitle)}" 82 data-theme="${escReg(theme)}" 83 style="${cardStyle}" 84 > 85 <div style="position:absolute;width:220px;height:220px;right:-90px;top:-100px;background:${isLight ? "#dcfce7" : "rgba(255,255,255,.16)"};filter:blur(24px);border-radius:999px;pointer-events:none;opacity:${isLight ? ".65" : "1"};"></div> 86 87 <div style="position:relative;z-index:1;"> 88 <div style="${badgeStyle}">${escReg(badgeLabel)}</div> 89 90 <h3 style="margin:0;font-size:${compact ? "22px" : "clamp(24px,3vw,34px)"};line-height:1.12;font-weight:900;letter-spacing:-.03em;color:${isLight ? "#0f172a" : "white"};"> 91 ${escReg(title)} 92 </h3> 93 94 <p style="margin:12px 0 0;color:${subtitleColor};font-size:14px;line-height:1.65;font-weight:500;"> 95 ${escReg(subtitle)} 96 </p> 97 98 <form data-registration-submit style="display:grid;gap:12px;margin-top:20px;"> 99 <input name="name" required placeholder="Full name" autocomplete="name" style="${regInputStyle(theme)}" /> 100 <input name="email" type="email" required placeholder="Email address" autocomplete="email" style="${regInputStyle(theme)}" /> 101 <input name="phone" placeholder="US phone number, e.g. (703) 555-1212" autocomplete="tel" inputmode="tel" title="Enter a valid US phone number, for example (703) 555-1212 or 703-555-1212" style="${regInputStyle(theme)}" /> 102 ${showCourseField 103 ? (Array.isArray(courseOptions) && courseOptions.length 104 ? `<select name="itemTitle" style="${regInputStyle(theme)}"> 105 <option value="">${escReg(coursePlaceholder)}</option> 106 ${courseOptions.map((c) => `<option value="${escReg(c.title || c.name || c.id || c)}">${escReg(c.title || c.name || c.id || c)}</option>`).join("")} 107 </select>` 108 : `<input name="itemTitle" placeholder="${escReg(coursePlaceholder)}" style="${regInputStyle(theme)}" />`) 109 : ""} 110 <textarea name="message" rows="4" placeholder="Tell us your question" style="${regInputStyle(theme)}resize:vertical;min-height:96px;"></textarea> 111 112 <button 113 type="submit" 114 data-submit-button 115 style="width:100%;border:none;border-radius:16px;padding:15px 18px;background:${isLight ? "#0f172a" : "white"};
115color:${isLight ? "white" : "#0f172a"};font-weight:900;font-size:15px;cursor:pointer;box-shadow:${isLight ? "0 12px 26px rgba(15,23,42,.18)" : "0 14px 30px rgba(0,0,0,.18)"};font-family:inherit;" 116 > 117 ${escReg(submitLabel)} 118 </button> 119 120 <div data-registration-message style="display:none;border-radius:14px;padding:12px;font-size:13px;font-weight:800;line-height:1.45;"></div> 121 </form> 122 </div> 123 </div>`; 124} 125 126export function bindRegistrationForms(root = document) { 127 root.querySelectorAll("[data-registration-form]").forEach((card) => { 128 const form = card.querySelector("[data-registration-submit]"); 129 const btn = card.querySelector("[data-submit-button]"); 130 const msg = card.querySelector("[data-registration-message]"); 131 132 if (!form || form.dataset.bound === "true") return; 133 form.dataset.bound = "true"; 134 135 form.addEventListener("submit", async (e) => { 136 e.preventDefault(); 137 138 const fd = new FormData(form); 139 140 const submittedItemTitle = String(fd.get("itemTitle") || "").trim(); 141 142 const payload = { 143 pageType: card.dataset.pageType || "general", 144 itemId: card.dataset.itemId || "", 145 itemTitle: submittedItemTitle || card.dataset.itemTitle || "", 146 name: String(fd.get("name") || "").trim(), 147 email: String(fd.get("email") || "").trim(), 148 phone: String(fd.get("phone") || "").trim(), 149 message: String(fd.get("message") || "").trim(), 150 sourceUrl: window.location.href, 151 createdFrom: "website", 152 }; 153 154 if (!payload.name || !payload.email) { 155 showRegMessage(card, msg, "Please enter your name and email.", "error"); 156 return; 157 } 158 159 if (!isValidEmail(payload.email)) { 160 showRegMessage(card, msg, "Please enter a valid email address.", "error"); 161 return; 162 } 163 164 if (!isValidUsPhone(payload.phone)) { 165 showRegMessage(card, msg, "Please enter a valid US phone number, for example (703) 555-1212.", "error"); 166 return; 167 } 168 169 if (payload.phone) { 170 payload.phone = normalizeUsPhone(payload.phone); 171 } 172 173 const oldText = btn?.textContent || "Submit"; 174 175 try { 176 if (btn) { 177 btn.disabled = true; 178 btn.textContent = "Submitting..."; 179 btn.style.opacity = ".75"; 180 } 181 182 showRegMessage(card, msg, "Submitting your enquiry...", "loading"); 183 184 const API_BASE = window.ITL_API_BASE || "https://api.itlearn360.com"; 185 const ENQUIRY_PUBLIC_KEY = window.ITL_ENQUIRY_PUBLIC_KEY || ""; 186 187 const headers = { "Content-Type": "application/json" }; 188 if (ENQUIRY_PUBLIC_KEY) headers["X-Enquiry-Key"] = ENQUIRY_PUBLIC_KEY; 189 190 const res = await fetch(`${API_BASE}/api/enquiry`, { 191 method: "POST", 192 headers, 193 body: JSON.stringify(payload), 194 }); 195 196 const data = await res.json().catch(() => ({})); 197 198 if (!res.ok) { 199 throw new Error(data.detail || data.error || "Unable to submit enquiry"); 200 } 201 202 form.reset(); 203 showRegMessage(card, msg, "Thank you. Your enquiry has been received. Our team will c
203ontact you shortly.", "success"); 204 } catch (err) { 205 showRegMessage(card, msg, err?.message || "Unable to submit enquiry. Please try again.", "error"); 206 } finally { 207 if (btn) { 208 btn.disabled = false; 209 btn.textContent = oldText; 210 btn.style.opacity = "1"; 211 } 212 } 213 }); 214 }); 215} 216 217function showRegMessage(card, el, text, type) { 218 if (!el) return; 219 220 const isLight = 221 card?.dataset?.theme === "light" || 222 card?.classList?.contains("itl-registration-card-light"); 223 224 el.style.display = "block"; 225 el.textContent = text; 226 227 if (type === "success") { 228 el.style.background = isLight ? "#ecfdf5" : "rgba(16,185,129,.18)"; 229 el.style.border = isLight ? "1px solid #bbf7d0" : "1px solid rgba(167,243,208,.45)"; 230 el.style.color = isLight ? "#047857" : "#ecfdf5"; 231 } else if (type === "error") { 232 el.style.background = isLight ? "#fef2f2" : "rgba(239,68,68,.18)"; 233 el.style.border = isLight ? "1px solid #fecaca" : "1px solid rgba(254,202,202,.45)"; 234 el.style.color = isLight ? "#b91c1c" : "#fff1f2"; 235 } else { 236 el.style.background = isLight ? "#eff6ff" : "rgba(255,255,255,.14)"; 237 el.style.border = isLight ? "1px solid #bfdbfe" : "1px solid rgba(255,255,255,.24)"; 238 el.style.color = isLight ? "#1d4ed8" : "white"; 239 } 240}
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.