PageSourceSearch

https://www.ariefinance.com/onboarding-requirements/app.js?v=2026-10-02-r2

js ariefinance.com collected 2026-10-08 01:29:38 UTC 32,986 bytes, 673 lines download raw bytes

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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
150      .replace(/"/g, '&quot;').replace(/'/g, '&#39;');
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.