1/* 2 * ARIE Finance â Onboarding Requirements 3 * 4 * Vanilla-JS production port of the approved design artifact. Data, strings, 5 * conditional rules and PDF layout are preserved verbatim from the frozen 6 * specification. UI is semantic HTML (buttons, native <dialog>, aria-expanded 7 * accordions), no framework. PDF generation is delegated to the locally 8 * vendored html2pdf bundle; per-page footer (corporate line + "Requirements 9 * v1.0 · Page X of Y") is added through the jsPDF handle exposed by html2pdf's 10 * .toPdf().get('pdf') chain. 11 * 12 * No analytics, no dataLayer, no third-party fetches at runtime (fonts aside). 13 */ 14(function () { 15 'use strict'; 16 17 // ---------- constants (frozen spec) ---------- 18 var VER = '1.0'; 19 var VDATE = 'October 2026'; 20 var MAY_APPLY = 'May apply â please confirm with ARIE Finance'; 21 22 var CORP_FOOTER = 'ARIE FINANCE LTD | COMPANY NUMBER 221997 | 3RD FLOOR, KPMG CENTRE, EBENE, MAURITIUS | LICENCE IK17000006 / GB25205028'; 23 24 // Category definitions. Order drives both the UI accordion and the PDF. 25 // `hidden:true` = computed dependency (never user-togglable). 26 var CATS = [ 27 { k: 'company', l: 'Applicant Company', always: true }, 28 { k: 'directors', l: 'Directors' }, 29 { k: 'signatories', l: 'Authorised Signatories' }, 30 { k: 'ubo', l: 'Ultimate Beneficial Owners' }, 31 { k: 'corpSh', l: 'Corporate Shareholder â Company' }, 32 { k: 'dirCorpSh', l: 'Director of Corporate Shareholder', hidden: true } 33 ]; 34 var TOGGLEABLE = CATS.filter(function (c) { return !c.hidden; }); 35 36 // "Required for each ..." subtitles beneath each per-person section header. 37 var PPN = { 38 directors: 'Required for each Director', 39 signatories: 'Required for each Authorised Signatory', 40 ubo: 'Required for each Ultimate Beneficial Owner', 41 corpSh: 'Required for each Corporate Shareholder', 42 dirCorpSh: 'Required for each Director of each Corporate Shareholder' 43 }; 44 45 // ---------- state ---------- 46 var state = { 47 view: 'landing', // 'landing' | 'preview' 48 modalStep: 1, // 1 | 2 49 companyName: '', 50 nameErr: false, 51 condErr: false, 52 cats: { 53 company: true, directors: true, signatories: true, ubo: true, 54 corpSh: false, dirCorpSh: false 55 }, 56 conds: { oy: null, ba: null, li: null }, 57 expL: { // landing accordion open-state 58 company: true, directors: false, signatories: false, 59 ubo: false, corpSh: false, dirCorpSh: false 60 }, 61 expP: {} // preview accordion open-state (populated on generate) 62 }; 63 64 // ---------- requirements data ---------- 65 // `cd` = governing conditional ('oy' | 'ba' | 'li'); `cn` = note shown 66 // underneath. In personalised view, items where `cn === null` are dropped 67 // (that happens when the user answered the governing question "No"). 68 function items(c) { 69 var oy = c.oy, ba = c.ba, li = c.li; 70 return { 71 company: [ 72 { n: 'Application + Onboarding Form', nt: 'To be completed and signed', dl: true }, 73 { n: 'Certified True Copy Certificate of Incorporation' }, 74 { n: 'Certified True Copy Memorandum and Articles of Association' }, 75 { n: 'Certified True Copy Register of Directors & Shareholders', 76 nt: 'Or Certificate of Incumbency showing active directors and shareholders' }, 77 { n: 'Certified True Copy Register of Beneficial Owners', 78 nt: 'Or written confirmation of beneficial owner' }, 79 { n: 'Certified True Copy Certificate of Good Standing', cd: 'oy', 80 cn: oy === 'yes' ? 'Required: company is more than 1 year old' 81 : oy === 'unsure' ? MAY_APPLY 82 : oy === 'no' ? null 83 : 'If the company is more than one year old' }, 84 { n: 'Latest Financial Statements / Management Accounts', cd: 'oy', 85 cn: oy === 'yes' ? 'Required: company is more than 1 year old' 86 : oy === 'unsure' ? MAY_APPLY 87 : oy === 'no' ? null 88 : 'If the company is more than one year old' }, 89 { n: 'Signed Structure Chart up to the Beneficial Owner' },
90 { n: 'Last 6 monthsâ Bank Statements', cd: 'ba', 91 cn: ba === 'yes' ? 'Required: existing bank account available' 92 : ba === 'unsure' ? MAY_APPLY 93 : ba === 'no' ? null 94 : 'If an existing bank account is available' }, 95 { n: 'Certified True Copy licence to operate and proof of its validity', cd: 'li', 96 cn: li === 'yes' ? 'Required: business is subject to a licence' 97 : li === 'unsure' ? MAY_APPLY 98 : li === 'no' ? null 99 : 'If the business activity is subject to a regulatory or operating licence' }, 100 { n: 'Documentary evidence of Source of Funds' } 101 ], 102 directors: [ 103 { n: 'Certified True Copy Passport' }, 104 { n: 'Certified True Copy Proof of Address', nt: 'Dated less than 3 months â Utility Bill or Bank Statement' }, 105 { n: 'CV / Detailed Profile' } 106 ], 107 signatories: [ 108 { n: 'Certified True Copy Passport' }, 109 { n: 'Certified True Copy Proof of Address', nt: 'Dated less than 3 months â Utility Bill or Bank Statement' }, 110 { n: 'Contact Details', nt: 'Email address and phone number' } 111 ], 112 ubo: [ 113 { n: 'Certified True Copy Passport' }, 114 { n: 'Certified True Copy Proof of Address', nt: 'Dated less than 2 months â Utility Bill or Bank Statement' }, 115 { n: 'CV / Detailed Profile' } 116 ], 117 // Corporate Shareholder age is INDEPENDENT of the applicant company's 118 // `oy` answer. Keep the generic conditional note; do not substitute 119 // based on `c.oy`. 120 corpSh: [ 121 { n: 'Certified True Copy Certificate of Incorporation' }, 122 { n: 'Certified True Copy Memorandum and Articles of Association' }, 123 { n: 'Certified True Copy Register of Directors & Shareholders', 124 nt: 'Or Certificate of Incumbency showing active directors and shareholders' }, 125 { n: 'Certified True Copy Register of Beneficial Owners', 126 nt: 'Or written confirmation of beneficial owner' }, 127 { n: 'Certified True Copy Certificate of Good Standing', cn: 'If the corporate shareholder is more than one year old' }, 128 { n: 'Latest Financial Statements / Management Accounts', cn: 'If the corporate shareholder is more than one year old' } 129 ], 130 dirCorpSh: [ 131 { n: 'Certified True Copy Passport' }, 132 { n: 'Certified True Copy Proof of Address', nt: 'Dated less than 3 months â Utility Bill or Bank Statement' }, 133 { n: 'CV / Detailed Profile' } 134 ] 135 }; 136 } 137 138 // Items visible in a given render: landing = all; personalised = drop 139 // conditional rows the user flipped to "No". 140 function visible(k, c, personalised) { 141 var its = items(c)[k] || []; 142 if (personalised) return its.filter(function (i) { return !i.cd || i.cn !== null; }); 143 return its; 144 } 145 146 // ---------- escaping helpers ---------- 147 function esc(s) { 148 return String(s) 149 .replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') 150 .replace(/"/g, '"').replace(/'/g, '''); 151 } 152 function safeFilenamePart(s) { 153 return String(s).replace(/[^a-zA-Z0-9 ]/g, '').replace(/\s+/g, '_').substring(0, 80); 154 } 155 156 // ---------- DOM helpers ---------- 157 function $(id) { return document.getElementById(id); } 158 function on(el, ev, fn) { el.addEventListener(ev, fn); } 159 function h(tag, attrs, children) { 160 var el = document.createElement(tag); 161 if (attrs) for (var k in attrs) { 162 if (!Object.prototype.hasOwnProperty.call(attrs, k)) continue; 163 if (k === 'class') el.className = attrs[k]; 164 else if (k === 'html') el.innerHTML = attrs[k]; 165 else if (k === 'text') el.textContent = attrs[k]; 166 else if (k.indexOf('on') === 0 && typeof attrs[k] === 'function') el.addEventListener(k.slice(2), attrs[k]); 167 else if (attrs[k] === true) el.setAttribute(k, ''); 168 else if (attrs[k] === false || attrs[k] == null) { /* skip */ } 169 else el.setAttribute(k, attrs[k]); 170 } 171 if (children) (Array.isArray(children) ? children : [children]).forEach(function (c) { 172 if (c == null || c === false) return; 173 el.appendChild(typeof c === 'string' ? document.createTextNode(c) : c); 174 }); 175 return el; 176 } 177 178 // ---------- landing accordion render ---------- 179 function renderLandingAccordions() { 180 var host = $('landing-sections'); 181 host.innerHTML = ''; 182 var catList = CATS; // landing always shows every category 183 catList.forEach(function (d, idx) { 184 var its = visible(d.k, {}, false); 185 var open = !!state.expL[d.k]; 186 var ppn = PPN[d.k]; 187 188 var head = h('button', { 189 type: 'button', class: 'acc-head', 'aria-expanded': String(open),
190 'aria-controls': 'acc-body-' + d.k, id: 'acc-head-' + d.k, 191 onclick: function () { state.expL[d.k] = !state.expL[d.k]; renderLandingAccordions(); } 192 }, [ 193 h('div', { class: 'acc-head-left' }, [ 194 h('div', { class: 'acc-num', 'aria-hidden': 'true' }, String(idx + 1).padStart(2, '0')), 195 h('div', { class: 'acc-title' }, d.l) 196 ]), 197 h('div', { class: 'acc-chev', 'aria-hidden': 'true' }, 'â') 198 ]); 199 200 var body = h('div', { 201 id: 'acc-body-' + d.k, role: 'region', 'aria-labelledby': 'acc-head-' + d.k, 202 hidden: !open 203 }); 204 if (ppn) body.appendChild(h('div', { class: 'acc-ppn' }, ppn)); 205 its.forEach(function (i) { body.appendChild(renderAccItem(i, false)); }); 206 207 host.appendChild(h('div', { class: 'acc' }, [head, body])); 208 }); 209 } 210 211 function renderAccItem(i, personalised) { 212 var ma = i.cn && i.cn.indexOf('May apply') === 0; 213 var cls = 'acc-item' + (i.cn ? (ma ? ' cond may' : ' cond') : ''); 214 var children = [h('span', { class: 'n' }, i.n)]; 215 if (i.nt) children.push(h('span', { class: 'note-s' }, i.nt)); 216 if (i.cn) children.push(h('div', null, [h('span', { class: 'tag' }, i.cn)])); 217 if (i.dl) { 218 children.push(h('div', { class: 'inline-dl' }, [ 219 h('a', { 220 href: '/documents/ARIE_Finance_Corporate_Onboarding_Pack.pdf', 221 download: 'ARIE_Finance_Corporate_Onboarding_Pack.pdf' 222 }, 'Download Corporate Onboarding Pack â'), 223 h('span', { class: 'hint' }, 'Includes the application and related onboarding declarations.') 224 ])); 225 } 226 return h('div', { class: cls }, children); 227 } 228 229 // ---------- preview render ---------- 230 function renderPreview() { 231 $('pv-company').textContent = state.companyName; 232 var selected = CATS.filter(function (d) { return state.cats[d.k]; }) 233 .map(function (d) { 234 if (d.k === 'corpSh') return 'Corporate Shareholder â Company & Directors'; 235 if (d.k === 'dirCorpSh') return null; 236 return d.l; 237 }) 238 .filter(Boolean).join(' · '); 239 $('pv-structure').textContent = selected; 240 var c = state.conds, parts = []; 241 if (c.oy) parts.push('Company more than one year old: ' + humanAns(c.oy)); 242 if (c.ba) parts.push('Existing bank account: ' + humanAns(c.ba)); 243 if (c.li) parts.push('Regulatory / operating licence: ' + humanAns(c.li)); 244 $('pv-conds').textContent = parts.join(' · '); 245 246 var host = $('preview-sections'); 247 host.innerHTML = ''; 248 var n = 0; 249 CATS.filter(function (d) { return state.cats[d.k]; }).forEach(function (d) { 250 n++; 251 var its = visible(d.k, state.conds, true); 252 var open = !!state.expP[d.k]; 253 var ppn = PPN[d.k]; 254 var head = h('button', { 255 type: 'button', class: 'acc-head', 'aria-expanded': String(open), 256 'aria-controls': 'pacc-body-' + d.k, id: 'pacc-head-' + d.k, 257 onclick: function () { state.expP[d.k] = !state.expP[d.k]; renderPreview(); } 258 }, [ 259 h('div', { class: 'acc-head-left' }, [ 260 h('div', { class: 'acc-num', 'aria-hidden': 'true' }, String(n).padStart(2, '0')), 261 h('div', { class: 'acc-title' }, d.l) 262 ]), 263 h('div', { class: 'acc-chev', 'aria-hidden': 'true' }, 'â') 264 ]); 265 var body = h('div', { 266 id: 'pacc-body-' + d.k, role: 'region', 'aria-labelledby': 'pacc-head-' + d.k, 267 hidden: !open 268 }); 269 if (ppn) body.appendChild(h('div', { class: 'acc-ppn' }, ppn)); 270 its.forEach(function (i) { body.appendChild(renderAccItem(i, true)); }); 271 host.appendChild(h('div', { class: 'acc' }, [head, body])); 272 }); 273 } 274 275 function humanAns(a) { return a === 'yes' ? 'Yes' : a === 'no' ? 'No' : 'Not sure'; } 276 277 // ---------- view switching ---------- 278 function showView(name) { 279 state.view = name; 280 $('view-landing').hidden = name !== 'landing'; 281 $('view-preview').hidden = name !== 'preview'; 282 if (name === 'landing') renderLandingAccordions(); 283 if (name === 'preview') renderPreview(); 284 window.scrollTo({ top: 0, behavior: 'instant' in window ? 'instant' : 'auto' }); 285 } 286 287 // ---------- modal (native <dialog>) ---------- 288 var modal, prevFocus; 289 function openModal(step) { 290 modal = modal || $('gen-modal'); 291 prevFocus = document.activeElement;
292 state.modalStep = step || 1; 293 state.nameErr = false; 294 state.condErr = false; 295 renderModal(); 296 if (typeof modal.showModal === 'function') modal.showModal(); 297 else { modal.setAttribute('open', ''); modal.style.display = 'block'; } 298 // Focus the first interactive field in the current step. 299 var focusTarget = state.modalStep === 1 ? $('m-company') : $('m-cond-list').querySelector('button'); 300 if (focusTarget) setTimeout(function () { focusTarget.focus(); }, 10); 301 } 302 function closeModal() { 303 if (!modal) return; 304 if (typeof modal.close === 'function') modal.close(); 305 else { modal.removeAttribute('open'); modal.style.display = 'none'; } 306 if (prevFocus && prevFocus.focus) prevFocus.focus(); 307 } 308 function renderModal() { 309 $('m-step-n').textContent = state.modalStep; 310 $('modal-title').textContent = state.modalStep === 1 ? 'Company & Structure' : 'A few quick questions'; 311 $('m-s1').hidden = state.modalStep !== 1; 312 $('m-s2').hidden = state.modalStep !== 2; 313 $('btn-modal-next').hidden = state.modalStep !== 1; 314 $('btn-modal-gen').hidden = state.modalStep !== 2; 315 $('btn-modal-back').disabled = state.modalStep === 1; 316 317 var input = $('m-company'); 318 input.value = state.companyName; 319 input.classList.toggle('err', !!state.nameErr); 320 input.setAttribute('aria-invalid', state.nameErr ? 'true' : 'false'); 321 $('m-company-err').classList.toggle('show', !!state.nameErr); 322 323 renderCatGrid(); 324 renderCondList(); 325 326 $('m-cond-err').classList.toggle('show', !!state.condErr); 327 } 328 function renderCatGrid() { 329 var host = $('m-cat-grid'); 330 host.innerHTML = ''; 331 TOGGLEABLE.forEach(function (d) { 332 var active = !!state.cats[d.k]; 333 var lbl = (d.k === 'corpSh' && active) ? 'Corporate Shareholder â Company & Directors' : d.l; 334 var btn = h('button', { 335 type: 'button', class: 'cat-chip', 336 'aria-pressed': String(active), 337 style: 'background:' + (active ? '#06113A' : '#f5f5f3') + 338 ';border:' + (active ? '1px solid #06113A' : '1px dashed #d4d4d0') + 339 ';color:' + (active ? '#fff' : '#888'), 340 disabled: !!d.always, 341 onclick: d.always ? null : function () { 342 state.cats[d.k] = !state.cats[d.k]; 343 // Corporate Shareholder couples the dependent Director block. 344 if (d.k === 'corpSh') state.cats.dirCorpSh = state.cats[d.k]; 345 renderCatGrid(); 346 } 347 }, [ 348 h('span', { 349 class: 'ck', 'aria-hidden': 'true', 350 style: 'background:' + (active ? 'rgba(255,255,255,.2)' : 'transparent') + 351 ';color:' + (active ? '#fff' : 'transparent') + 352 ';border:' + (active ? 'none' : '1.5px solid #ccc') 353 }, active ? 'â' : ''), 354 h('span', { class: 'lbl' }, lbl), 355 d.always ? h('span', { class: 'always' }, 'Always included') : null 356 ]); 357 host.appendChild(btn); 358 }); 359 } 360 var COND_QS = [ 361 { k: 'oy', q: 'Is the company more than one year old?', help: null, sep: true }, 362 { k: 'ba', q: 'Does the company maintain an existing bank account?', help: null, sep: true }, 363 { k: 'li', q: 'Is your business activity subject to a regulatory or operating licence?', 364 help: 'For example, financial services, insurance, investment, gaming, telecoms or another regulated activity.', sep: false } 365 ]; 366 function renderCondList() { 367 var host = $('m-cond-list'); 368 host.innerHTML = ''; 369 COND_QS.forEach(function (q) { 370 var row = h('div', { class: 'cond-q' + (q.sep ? ' sep' : '') }); 371 row.appendChild(h('div', { class: 'q' }, q.q)); 372 if (q.help) row.appendChild(h('div', { class: 'help' }, q.help)); 373 var cur = state.conds[q.k]; 374 var opts = [['yes', 'Yes'], ['no', 'No'], ['unsure', 'Not sure']]; 375 var btns = h('div', { class: 'cond-row', role: 'group', 'aria-label': q.q }); 376 opts.forEach(function (o) { 377 var active = cur === o[0]; 378 var isU = o[0] === 'unsure'; 379 var style = active 380 ? (isU 381 ? 'background:#fffbeb;color:#92400e;border:1px solid #fde68a' 382 : 'background:#06113A;color:#fff;border:1px solid #06113A') 383 : 'background:#f5f5f3;color:#666;border:1px solid #e8e8e5'; 384 btns.appendChild(h('button', { 385 type: 'button', 'aria-pressed': String(active), style: style, 386 onclick: function () { 387 state.conds[q.k] = state.conds[q.k] === o[0] ? null : o[0]; 388 state.condErr = false;
389 renderCondList(); 390 $('m-cond-err').classList.remove('show'); 391 } 392 }, o[1])); 393 }); 394 row.appendChild(btns); 395 host.appendChild(row); 396 }); 397 } 398 399 // ---------- generate ---------- 400 function tryContinue() { 401 var nm = $('m-company').value.trim().substring(0, 150); 402 if (!nm) { 403 state.companyName = $('m-company').value; 404 state.nameErr = true; 405 renderModal(); 406 $('m-company').focus(); 407 return; 408 } 409 state.companyName = nm; 410 state.nameErr = false; 411 state.modalStep = 2; 412 renderModal(); 413 var firstBtn = $('m-cond-list').querySelector('button'); 414 if (firstBtn) firstBtn.focus(); 415 } 416 function tryGenerate() { 417 var c = state.conds; 418 if (c.oy === null || c.ba === null || c.li === null) { 419 state.condErr = true; 420 renderModal(); 421 return; 422 } 423 // Default preview-accordion open-state: open every selected section. 424 var ep = {}; 425 CATS.forEach(function (d) { if (state.cats[d.k]) ep[d.k] = true; }); 426 state.expP = ep; 427 state.condErr = false; 428 closeModal(); 429 showView('preview'); 430 } 431 432 // ---------- PDF ---------- 433 // Builds the printable HTML for a Generic or Personalised checklist. The 434 // per-page footer (corporate line + "Page X of Y") is added separately by 435 // iterating the jsPDF handle after html2pdf has rendered, so no footer 436 // markup is embedded here. 437 function printHtml(type) { 438 var c = type === 'generic' ? {} : state.conds; 439 var cf = type === 'generic' ? null : state.cats; 440 var co = type === 'generic' ? null : state.companyName; 441 var secs = CATS.filter(function (d) { return !cf || cf[d.k]; }); 442 443 var out = ''; 444 out += '<div style="font-family:DM Sans,system-ui,sans-serif;color:#1a1a1a">'; 445 out += '<div style="height:6px;background:linear-gradient(90deg,#06113A,#201485)"></div>'; 446 out += '<div style="display:flex;align-items:flex-start;justify-content:space-between;margin:24px 0 28px">' + 447 '<img src="/onboarding-requirements/assets/arie-logo.png" style="height:22px" alt="ARIE Finance">' + 448 '<div style="font-size:8px;font-weight:600;text-transform:uppercase;letter-spacing:2px;color:#b8a070">Corporate Onboarding</div></div>'; 449 450 if (type === 'generic') { 451 out += '<div style="font-family:DM Serif Display,serif;font-size:26px;color:#06113A;line-height:1.2;margin-bottom:8px">Onboarding Requirements<br>Checklist</div>'; 452 out += '<div style="width:40px;height:2px;background:#b8a070;margin-bottom:8px"></div>'; 453 out += '<div style="font-size:10px;color:#888;margin-bottom:16px">Standard documentation required for onboarding with ARIE Finance.</div>'; 454 out += '<div style="padding:8px 0;border-top:1px solid #e8e5dd;border-bottom:1px solid #e8e5dd;margin-bottom:16px;font-size:9px;color:#888">Requirements version: <span style="color:#333;font-weight:500">' + VER + ' · ' + VDATE + '</span></div>'; 455 } else { 456 out += '<div style="font-family:DM Serif Display,serif;font-size:26px;color:#06113A;line-height:1.2;margin-bottom:8px">Onboarding Document<br>Checklist</div>'; 457 out += '<div style="width:40px;height:2px;background:#b8a070;margin-bottom:8px"></div>'; 458 out += '<div style="font-size:10px;color:#888;margin-bottom:16px">Checklist generated based on the information provided.</div>'; 459 out += '<div style="padding:12px 0;border-top:1px solid #e8e5dd;border-bottom:1px solid #e8e5dd;margin-bottom:6px">'; 460 out += '<div style="font-size:10px;margin-bottom:4px"><span style="color:#888">Company:</span> <span style="color:#06113A;font-weight:600;font-size:12px">' + esc(co || '') + '</span></div>'; 461 var dt = new Date(); 462 var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; 463 out += '<div style="display:flex;gap:16px;font-size:9px;color:#888"><div>Generated: <span style="color:#333;font-weight:500">' + 464 dt.getDate() + ' ' + months[dt.getMonth()] + ' ' + dt.getFullYear() + '</span></div>' + 465 '<div>Requirements version: <span style="color:#333;font-weight:500">' + VER + ' · ' + VDATE + '</span></div></div></div>'; 466 467 var selN = CATS.filter(function (d) { return cf[d.k]; }).map(function (d) { return d.l; }).join(' · '); 468 out += '<div style="padding:6px 0;margin-bottom:12px;font-size:9px;color:#888;line-height:1.8"><div>Structure: <span style="color:#333">' + esc(selN) + '</span></div>'; 469 var cp = []; 470 function fmtAns(k, label) { 471 if (!c[k]) return; 472 var v = c[k]; 473 var col = v === 'unsure' ? '#b8a070;font-weight:600' : '#333;font-weight:500'; 474 cp.push(label + ': <span style="color:' + col + '">' + humanAns(v) + '</span>'); 475 } 476 fmtAns('oy', 'Company more than one year old'); 477 fmtAns('ba', 'Existing bank account'); 478 fmtAns('li', 'Regulatory / operating licence'); 479 if (cp.length) out += '<div>' + cp.join(' · ') + '</div>'; 480 out += '</div>'; 481 482 out += '<div style="background:#06113A;padding:14px 18px;border-radius:4px;margin-bottom:14px">' + 483 '<div style="font-size:11px;font-weight:600;color:#fff;line-height:1.5;margin-bottom:4px">Please tick the documents included with your submission and send this completed checklist together with your supporting documents to ARIE Finance.</div>' + 484 '<div style="font-size:9px;color:rgba(255,255,255,.6);line-height:1.5">This helps ARIE Finance identify the documents submitted and review your onboarding pack more efficiently.</div></div>'; 485 } 486 487 out += '<div style="font-size:9px;color:#555;line-height:1.6;margin-bottom:18px;padding:10px 14px;background:#f5f3ee;border-radius:4px">Where a requirement applies to more than one person or entity, please tick the box once the relevant document has been included for <strong>all</strong> applicable persons or entities.</div>'; 488 489 var sN = 0; 490 secs.forEach(function (d) { 491 sN++; 492 var its = visible(d.k, c, type === 'personalised'); 493 var ppn = PPN[d.k]; 494 out += '<div style="margin-bottom:16px">'; 495 out += '<div style="display:flex;align-items:center;gap:10px;margin-bottom:' + (ppn ? '4' : '10') + 'px">' + 496 '<div style="font-size:9px;font-weight:700;color:#b8a070;letter-spacing:1px">' + (sN < 10 ? '0' : '') + sN + '</div>' + 497 '<div style="font-size:12px;font-weight:600;color:#06113A">' + esc(d.l) + '</div>' + 498 '<div style="flex:1;height:1px;background:#e8e5dd;margin-left:8px"></div></div>'; 499 if (ppn) out += '<div style="font-size:9px;color:#888;margin-bottom:8px;padding-left:22px;font-style:italic">' + esc(ppn) + '</div>'; 500 out += '<div style="padding:0 0 0 22px">';
501 its.forEach(function (i, idx) { 502 var last = idx === its.length - 1; 503 var ma = i.cn && i.cn.indexOf('May apply') === 0; 504 out += '<div style="display:flex;align-items:flex-start;gap:10px;padding:6px 0;' + (last ? '' : 'border-bottom:1px solid #f0ede6') + '">'; 505 out += '<div style="width:15px;height:15px;border:1.5px solid #888;border-radius:2px;flex-shrink:0;margin-top:1px"></div>'; 506 out += '<div style="flex:1"><div style="font-size:10px;color:#333">' + esc(i.n) + '</div>'; 507 if (i.nt) out += '<div style="font-size:9px;color:#aaa;margin-top:1px">' + esc(i.nt) + '</div>'; 508 if (i.cn) out += '<div style="font-size:9px;color:#b8a070;' + (ma ? 'font-weight:500;' : '') + 'margin-top:1px">' + esc(i.cn) + '</div>'; 509 out += '</div></div>'; 510 }); 511 out += '</div></div>'; 512 }); 513 514 var disc = type === 'generic' 515 ? 'This checklist identifies standard documentation requirements. ARIE Finance may request additional information or documentation depending on the entity, ownership structure, jurisdiction, business activities and compliance review.' 516 : 'This checklist reflects the information provided and is intended to assist with document preparation. ARIE Finance may request additional information or documentation following its review.'; 517 out += '<div style="padding-top:10px;margin-top:16px"><div style="font-size:8px;color:#aaa;line-height:1.6">' + disc + '</div></div>'; 518 519 out += '</div>'; 520 return out; 521 } 522 523 // Per-page footer injection via the jsPDF handle html2pdf exposes. 524 function addFooters(pdf) { 525 var total = pdf.internal.getNumberOfPages(); 526 var pageW = pdf.internal.pageSize.getWidth(); 527 var pageH = pdf.internal.pageSize.getHeight(); 528 for (var i = 1; i <= total; i++) { 529 pdf.setPage(i); 530 pdf.setFont('helvetica', 'normal'); 531 pdf.setFontSize(6.5); 532 pdf.setTextColor(170, 170, 170); 533 // Top-of-footer rule, 10mm above the bottom edge. 534 pdf.setDrawColor(232, 229, 221); 535 pdf.setLineWidth(0.2); 536 pdf.line(20, pageH - 14, pageW - 20, pageH - 14); 537 // Corporate line, left-aligned. 538 pdf.text(CORP_FOOTER, 20, pageH - 10); 539 // Requirements version + page X of Y, right-aligned. 540 pdf.text('Requirements v' + VER + ' · Page ' + i + ' of ' + total, pageW - 20, pageH - 5, { align: 'right' }); 541 } 542 } 543 544 // html2canvas + jsPDF are vendored locally and driven directly. The 545 // previous html2pdf.bundle.min.js wrapper placed the source inside a 546 // hidden overlay whose layout behaved inconsistently for longer content 547 // (observed: for the Generic variant, the rendered canvas placed all 548 // content in a sliver at the bottom and page 1 came out blank). 549 // Driving html2canvas on an off-screen but fully-rendered container is 550 // deterministic. 551 function printPdf(type) { 552 var hc = window.html2canvas; 553 var jsPDFCtor = (window.jspdf && window.jspdf.jsPDF) || window.jsPDF; 554 if (typeof hc !== 'function' || typeof jsPDFCtor !== 'function') { 555 alert('PDF generation is unavailable in this browser. Please contact ARIE Finance.'); 556 return; 557 } 558 var container = document.createElement('div'); 559 // Offscreen but fully rendered â html2canvas treats visibility:hidden 560 // and display:none as "skip", so hiding has to be positional. 561 container.style.cssText = 562 'position:fixed;left:-10000px;top:0;width:170mm;' + 563 'background:#FAF9F6;color:#1a1a1a;font-family:\'DM Sans\',system-ui,sans-serif;' + 564 'pointer-events:none;'; 565 container.innerHTML = printHtml(type); 566 document.body.appendChild(container); 567 568 var safe = safeFilenamePart(state.companyName); 569 var fname = type === 'generic' 570 ? 'ARIE_Onboarding_Requirements_Checklist.pdf' 571 : 'ARIE_Onboarding_Checklist_' + (safe || 'Company') + '.pdf'; 572 573 function cleanup() { try { document.body.removeChild(container); } catch (e) { /* ignore */ } } 574 575 // Give web-fonts a chance to load before rasterising, so measured heights 576 // match the intended layout. 577 var fontsReady = (document.fonts && document.fonts.ready) || Promise.resolve(); 578 fontsReady 579 .then(function () { 580 return hc(container, { scale: 2, useCORS: true, backgroundColor: '#FAF9F6', logging: false }); 581 }) 582 .then(function (canvas) { 583 var pdf = new jsPDFCtor({ unit: 'mm', format: 'a4', orientation: 'portrait' }); 584 var marginTop = 15, marginLeft = 20, marginRight = 20, marginBottom = 22; 585 var pageW = pdf.internal.pageSize.getWidth(); // 210mm 586 var pageH = pdf.internal.pageSize.getHeight(); // 297mm 587 var contentWmm = pageW - marginLeft - marginRight; // 170mm 588 var contentHmm = pageH - marginTop - marginBottom; // 260mm 589 // Full image size (mm): width is fixed to the content width, 590 // height is the canvas height scaled to that width. 591 var imgWmm = contentWmm; 592 var imgHmm = canvas.height * imgWmm / canvas.width; 593 // Place the full image on the first page, then shift it up on each 594 // subsequent page so the next slice appears at the top of the content 595 // area. Any image bleeding below the content area is clipped by 596 // addImage because we re-add the same image shifted â simplest 597 // multi-page strategy that always covers all content. 598 var y = marginTop; 599 var remaining = imgHmm; 600 var imgData = canvas.toDataURL('image/jpeg', 0.95); 601 var page = 1; 602 while (remaining > 0) { 603 if (page > 1) { pdf.addPage(); y = marginTop - (imgHmm - remaining); } 604 pdf.addImage(imgData, 'JPEG', marginLeft, y, imgWmm, imgHmm); 605 // Mask anything below the content area with a white rectangle so 606 // the image above the next page's slice doesn't show. 607 pdf.setFillColor(255, 255, 255);
608 pdf.rect(0, marginTop + contentHmm, pageW, pageH - (marginTop + contentHmm), 'F'); 609 pdf.rect(0, 0, pageW, marginTop, 'F'); 610 remaining -= contentHmm; 611 page++; 612 } 613 try { addFooters(pdf); } catch (e) { /* footer is best-effort */ } 614 pdf.save(fname); 615 cleanup(); 616 }) 617 .catch(function (err) { 618 cleanup(); 619 try { console.error('PDF generation failed:', err); } catch (e) { /* ignore */ } 620 alert('PDF generation failed. Please try again.'); 621 }); 622 } 623 624 // ---------- wiring ---------- 625 function init() { 626 // Landing CTAs 627 on($('btn-view-reqs'), 'click', function () { 628 var el = $('reqs-anchor'); 629 if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' }); 630 }); 631 on($('btn-dl-generic-hero'), 'click', function () { printPdf('generic'); }); 632 on($('btn-dl-generic-bottom'), 'click', function () { printPdf('generic'); }); 633 on($('btn-open-modal'), 'click', function () { openModal(1); }); 634 635 // Preview CTAs 636 on($('btn-back-landing'), 'click', function () { showView('landing'); }); 637 on($('btn-edit-details'), 'click', function () { openModal(1); }); 638 on($('btn-dl-personalised'), 'click', function () { printPdf('personalised'); }); 639 640 // Modal controls 641 modal = $('gen-modal'); 642 on($('btn-modal-close'), 'click', closeModal); 643 on($('btn-modal-back'), 'click', function () { 644 if (state.modalStep === 2) { state.modalStep = 1; renderModal(); $('m-company').focus(); } 645 }); 646 on($('btn-modal-next'), 'click', tryContinue); 647 on($('btn-modal-gen'), 'click', tryGenerate); 648 // Native <dialog> emits 'cancel' on Escape; 'close' when closed either way. 649 on(modal, 'cancel', function () { /* let it close; focus-return happens in closeModal via click path */ }); 650 on(modal, 'close', function () { if (prevFocus && prevFocus.focus) prevFocus.focus(); }); 651 // Backdrop-click: close only when the click lands on the dialog element 652 // itself (which fills the viewport around the shell). 653 on(modal, 'click', function (e) { if (e.target === modal) closeModal(); }); 654 on($('m-company'), 'input', function (e) { 655 state.companyName = e.target.value; 656 if (state.nameErr && e.target.value.trim()) { 657 state.nameErr = false; 658 e.target.classList.remove('err'); 659 e.target.setAttribute('aria-invalid', 'false'); 660 $('m-company-err').classList.remove('show'); 661 } 662 }); 663 on($('m-company'), 'keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); tryContinue(); } }); 664 665 showView('landing'); 666 } 667 668 if (document.readyState === 'loading') { 669 document.addEventListener('DOMContentLoaded', init); 670 } else { 671 init(); 672 } 673})();
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.