PageSourceSearch

https://secure.stgeorgessociety.org/login?forwardedFromSecureDomain=1

html stgeorgessociety.org collected 2026-09-24 19:06:11 UTC 94,464 bytes, 1,930 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en">
3<head>
4        <meta charset="UTF-8">
5        <meta http-equiv="X-UA-Compatible" content="IE=edge">
6        <meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
7
8        <!-- Add to home screen for Safari on iOS -->
9        <meta name="apple-mobile-web-app-capable" content="yes">
10        <meta name="apple-mobile-web-app-status-bar-style" content="black">
11
12        <html lang="en">
13<meta name="twitter:card" content="summary_large_image">
14<meta property="og:type" content="article"/>
15
16
17
18
19
20
21        <title>Login</title>
22
23        <link rel="shortcut icon" href="/neon/resource/stgeorgessociety/images/favicon.gif"/>
24        <!-- Common styles and scripts -->
25        <link href="https://fonts.googleapis.com/css?family=Lato:400,700|Open+Sans:400,700,900|Source+Sans+Pro:400,700,900|Montserrat:400,700,900|Roboto:100,100i,300,300i,400,400i,500,500i,700,700i,900,900i&amp;subset=cyrillic,cyrillic-ext,greek,greek-ext,latin-ext,vietnamese" rel="stylesheet">
26        
27        <link href="/nx/portal-static/component/bootstrap/css/bootstrap.min.css" rel="stylesheet">
28        <link href="/nx/portal-static/component/fontawesome-pro/css/all.min.css" rel="stylesheet">
29        <link href="/nx/portal-static/component/fontawesome-pro/css/sharp-solid.min.css" rel="stylesheet">
30        <link href="/nx/portal-static/component/cropperjs/cropper.min.css" rel="stylesheet">
31        
32        <link href="/nx/portal-static/css/portal/portal.all.css" rel="stylesheet">
33        
34
35        <!-- Custom Web Page only. Before customCss.css so org td/tr rules still win. -->
36        
37
38        <link href="/np/clients/stgeorgessociety/customCss.css" rel="stylesheet">
39
40        
41        
42
43
44
45
46
47<style>
48
49    .neon-body {
50        position: relative;
51    }
52    .neonfront {
53        position: relative;
54        z-index: 2;
55    }
56    .neon-background {
57        background: var(--page-bg-color, #E8E9F6);
58        background-image: var(--page-bg-image, url(""));
59        background-position: center; /* Center the image */
60        background-repeat: no-repeat; /* Do not repeat the image */
61        background-size: cover; /* Resize the background image to cover the entire container */
62        height: 100%;
63        width: 100%;
64        transform: var(--page-bg-mirror, );
65        background-position: var(--page-bg-align, center center);
66        position: fixed;
67        z-index: 1;
68    }
69    .neonfront_element .form-check-input:checked {
70        background-color: var(--out-line-calibration-color, #282968)!important;
71    }
72    .neonfront_content_wrap{
73        background: ;
74        backdrop-filter: blur(3px);
75    }
76    body {
77        font-family: var(--page-font);
78    }
79    .neonfront_content_title, .neonfront_content_desc {
80        font-family: var(--page-font, Arial,sans-serif) !important;
81        color: var(--page-font-color, #222222)!important;
82        word-break: break-word;
83    }
84    .neonfront_content_wrap.overlay-dark .neonfront_content_title,
85    .neonfront_content_wrap.overlay-dark .neonfront_content_desc {
86        color: #ffffff !important;
87    }
88    .neonfront_content_wrap.overlay-light .neonfront_content_title,
89    .neonfront_content_wrap.overlay-light .neonfront_content_desc {
90        color: #222222 !important;
91    }
92    .btn-outline-primary,.btn-outline-primary:hover {
93        color: var(--out-line-calibration-color, #282968)!important;
94        background-color: transparent !important;
95        border-color: #D9DFE3;
96        box-shadow: none !important;
97    }
98    .btn-outline-secondary:hover,.btn-outline-secondary:active,.btn-outline-secondary:focus{
99        background-color: #fff!important;
100        color: var(--out-line-calibration-color, #282968)!important;
101        border-color: #D9DFE3;
102        box-shadow: none !important;
103    }
104
105    .neonfront-btn-link {
106        color: var(--out-line-calibration-color, #282968)!important;
107    }
108    .neonfront_stepper.on,.neonfront_stepper_num {
109        background: var(--theme-color, #282968)!important;
110    }
111
112    .neonfront_payment_tab .active {
113        border-bottom: 4px solid var(--out-line-calibration-color, #282968) !important;
114        color: var(--out-line-calibration-color, #282968) !important;
115    }
116
117    .neonfront-btn-primary, .btn-primary {
118        background-color: var(--theme-color, #282968) !important;
119        color: var(--primary-btn-text-color, #ffffff) !important;
120        border-color: var(--theme-color, #282968) !important;
121        box-shadow: none !important;
122    }
123    .secondary-exit-btn {
124        color: var(--theme-color, #282968) !important;
125        background-color: #FFF !important;
126        border: 1px solid var(--theme-color, #282968) !important;
127        box-shadow: none !important;
128    }
129
130    .neonfront-btn-paypal {
131        background-color: #f7c457 !important;
132    }
133
134    .btn-primary, .form-check-input:checked {
135        border-color: var(--theme-color, #282968) !important;
136    }
137
138    .neonfront-btn-outline-primary {
139        color: var(--out-line-calibration-color, #282968)!important;
140    }
141
142    .btn-check:checked + .neonfront-btn-outline-primary, .donationFrequencyChecked, .btn-check:active + .neonfront-btn-outline-primary, .neonfront-btn-outline-primary.active, .neonfront-btn-outline-primary.dropdown-toggle.show {
143        background-color: var(--theme-color, #282968) !important;
144        border-color: var(--theme-color, #282968) !important;
145        color: var(--primary-btn-text-color, #ffffff) !important;
146        z-index: 0 !important;
147    }
148
149    .donation-frequency-default-select-icon-bg {
150        background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' %3e%3cpath fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");
151    }
152
153    .neonfront-check-input-level:checked + .neonfront-btn-level, .neonfront-check-input-level:active + .neonfront-btn-level, .neonfront-btn-level:active, .neonfront-btn-level.active, .neonfront-btn-level.dropdown-toggle.show {
154        border-color: var(--theme-color, #282968) !important;
155    }
156
157    .container-fluid.neonfront_header {
158        color: var(--header-text-color, #222222) !important;
159        background-color: var(--header-bg-color, )!important;
160    }
161    .container-fluid.neonfront_header .navbar-toggler {
162        color: var(--header-text-color, #222222) !important;
163        z-index: 100;
164    }
165    .container-fluid.neonfront_footer {
166        color: var(--footer-text-color, #ffffff)!important;
167        background-color: var(--footer-bg-color, #525386)!important;
168        padding-left: 0px;
169        padding-right: 0px;
170    }
171    .neonfront_footer_lv_info {
172        color: var(--footer-text-color, #ffffff)!important;
173        font-family: var(--page-font, Lato);
174        font-style: normal;
175        font-weight: 400;
176        font-size: 14px;
177        padding-top: 21px;
178        padding-bottom: 3px;
179        word-break: break-word;
180        min-height: 30px;
181    }
182    .neonfront_footer_lv_info a{
183        color: var(--footer-text-color, #ffffff);
184        text-decoration: underline!important;
185    }
186    .neonfront_header .neonfront_link {
187        color: var(--header-text-color, #222222)!important;
188    }
189    .neonfront-profile-dropdown .fa-caret-down {
190        color: #3C4144!important;
191    }
192    .neonfront-profile-dropdown .name {
193        background-color: #3C4144!important;
194        color: #ffffff!important;
195    }
196    .neonfront_header .neonfront_link.login_button {
197        border-color: var(--header-text-color, #222222)!important;
198        width: 78px;
199        height: 32px;
200        padding: 0px 0px 2px 0px !important;
201        margin-left: 15px;
202    }
203    .neonfront_footer_lv_info .neonfront_link {
204        color: var(--footer-text-color, #ffffff)!important;
205    }
206
207    .neonfront-switch input:checked{
208        background-color: var(--theme-color, #282968) !important;
209    }
210
211    .popal-opportunities-tile .popal-opportunities-tile-col .pot-neon-round-img {
212        background-color: var(--theme-color, #282968) !important;
213        color: var(--primary-btn-text-color, #ffffff) !important;
214    }
215    .portal-volunteer-info-main .opportunity-app-col1 .module-icon-round {
216        background-color: var(--theme-color, #282968) !important;
217        color: var(--primary-btn-text-color, #ffffff) !important;
218    }
219</style>
220<!-- Monthly Giving Suggestion (MGS) Styles -->
221
222
223<script type="text/javascript">
224    function setCalibrationColor(color) {
225        if (color) {
226            color = color.replace(/['"]/g, '')
227            let darkText = '#222222'
228            let lightText = '#ffffff'
229            color = this.contrastRatioColor(color, darkText) > this.contrastRatioColor(color, lightText) ? darkText : color
230            document.documentElement.style.setProperty("--out-line-calibration-color", color);
231        }
232    }
233    function contrastRatioColor(color1, color2) {
234        let color1luminanceColor = this.luminanceColor(color1)
235        let color2luminanceColor = this.luminanceColor(color2)
236        return color1luminanceColor > color2luminanceColor
237            ? ((color1luminanceColor + 0.05) / (color2luminanceColor + 0.05))
238            : ((color2luminanceColor + 0.05) / (color1luminanceColor + 0.05))
239    }
240    function luminanceColor(color) {
241        let rgbColor = /rgba?/.test(color) ? this.rgbaColorToRgbMap(color) : this.hexColorToRgb(color)
242        let rgb = [rgbColor.r, rgbColor.g, rgbColor.b].map(function (x) { x /= 255
243            return x <= 0.03928
244                ? x / 12.92
245                : Math.pow((x + 0.055) / 1.055, 2.4)
246        })
247        return rgb[0] * 0.2126 + rgb[1] * 0.7152 + rgb[2] * 0.0722
248    };
249    function rgbaColorToRgbMap(rgba) {
250        let result = rgba.match(/rgba?\((\d{1,3}), ?(\d{1,3}), ?(\d{1,3})\)?(?:, ?(\d(?:\.\d?))\))?/);
251        return result ? {r: parseInt(result[1]), g: parseInt(result[2]), b: parseInt(result[3])} : null
252    }
253    function hexColorToRgb(hex) {
254        let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
255        return result ? {
256            r: parseInt(result[1], 16),
257            g: parseInt(result[2], 16),
258            b: parseInt(result[3], 16)
259        } : null
260    }
261</script>
vendor: 1 bytes, line 261
261
262<script>
263    /* ================================================================
264     * Portal Theme JS  (included via th:replace from theme/theme.html)
265     * Provides:
266     *   SOCIAL_ICON_CONFIG / getSocialIconURL(name, style)
267     *   applyThemeCSSVars(theme)
268     *   postMessage listener for live-preview iframe
269     * ================================================================ */
270
271    // ── Social Icons ──────────────────────────────────────────────────
272    const SOCIAL_ICON_BASE = '/nx/portal-static/img/social-icon';
273
274    const SOCIAL_ICON_CONFIG = {
275        Website: {color: 'website-color.svg', white: 'website-white.svg', black: 'website-black.svg'},
276        Email: {color: 'email-color.svg', white: 'email-white.svg', black: 'email-black.svg'},
277        Facebook: {color: 'facebook-color.png', white: 'facebook-white.png', black: 'facebook-color.png'}, // no black → brand rule
278        Flickr: {color: 'flickr-color.png', white: 'flickr-white.png', black: 'flickr-black.png'},
279        Instagram: {color: 'instagram-color.png', white: 'instagram-white.png', black: 'instagram-black.png'},
280        LinkedIn: {color: 'linkedin-color.png', white: 'linkedin-white.png', black: 'linkedin-black.png'},
281        Pinterest: {color: 'pinterest-color.png', white: 'pinterest-white.png', black: 'pinterest-black.png'},
282        Snapchat: {color: 'snapchat-color.png', white: 'snapchat-white.png', black: 'snapchat-black.png'},
283        SoundCloud: {color: 'soundcloud-white.png', white: 'soundcloud-white.png', black: 'soundcloud-black.png'}, // no color → reuses white
284        Spotify: {color: 'spotify-color.png', white: 'spotify-white.png', black: 'spotify-black.png'},
285        Vimeo: {color: 'vimeo-color.png', white: 'vimeo-white.png', black: 'vimeo-black.png'},
286        X: {color: 'x-white.png', white: 'x-white.png', black: 'x-black.png'}, // no color → reuses white
287        YouTube: {color: 'youtube-color.png', white: 'youtube-white.png', black: 'youtube-black.png'}
288    };
289
290    function getSocialIconURL(name, style) {
291        // A backend bug (BrandDataManager/PersistSystemSettingAction) previously saved 'Twitter'
292        // instead of 'X' for this icon; normalize so those existing records still render.
293        const key = name === 'Twitter' ? 'X' : name;
294        const cfg = SOCIAL_ICON_CONFIG[key];
295        if (!cfg) return ''
296        const styleMap = {C: 'color', L: 'white', D: 'black'};
297        const file = cfg[styleMap[style] || 'color'] || '';
298        return file ? SOCIAL_ICON_BASE + '/' + file : ''
299    }
300
301    // ── Hosted Brand Fonts ─────────────────────────────────────────────
302
303    const SYSTEM_FONT_NAMES = ['Arial', 'Comic Sans MS', 'Courier New', 'Georgia', 'Tahoma', 'Lucida Sans Unicode', 'Times New Roman', 'Trebuchet MS', 'Verdana'];
304
305    function getBrandFontName(fontFamilyValue) {
306        if (!fontFamilyValue || typeof fontFamilyValue !== 'string') return ''
307        return fontFamilyValue.split(',')[0].trim().replace(/^['"]|['"]$/g, '')
308    }
309
310    function loadHostedBrandFontFromFamily(fontFamilyValue) {
311        const fontName = getBrandFontName(fontFamilyValue);
312        if (!fontName || SYSTEM_FONT_NAMES.indexOf(fontName) > -1) return
313
314        const linkId = 'brand-font-' + fontName.toLowerCase().replace(/[^a-z0-9]+/g, '-');
315        if (document.getElementById(linkId)) return
316
317        const link = document.createElement('link');
318        link.id = linkId;
319        link.rel = 'stylesheet';
320        link.href = 'https://fonts.googleapis.com/css2?family=' + encodeURIComponent(fontName).replace(/%20/g, '+') + '&display=swap';
321        document.head.appendChild(link);
322    }
323
324    // ── CSS Variables ─────────────────────────────────────────────────
325
326    /**
327     * Parse a color string to {r,g,b,a}.
328     * Supports: #rgb / #rrggbb / #rrggbbaa / rgb(...) / rgba(...)
329     * Returns null if the input is not a recognized format.
330     */
331    function hexToRgba(color) {
332        if (!color || typeof color !== 'string') return null
333        var c = color.trim()
334        // rgb(r, g, b) or rgba(r, g, b, a)
335        var rgbMatch = c.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+))?\s*\)$/)
336        if (rgbMatch) {
337            return {
338                r: parseInt(rgbMatch[1]),
339                g: parseInt(rgbMatch[2]),
340                b: parseInt(rgbMatch[3]),
341                a: rgbMatch[4] !== undefined ? parseFloat(rgbMatch[4]) : 1
342            }
343        }
344        // #hex
345        if (c[0] !== '#') return null
346        var h = c.substring(1)
347        if (h.length === 3) { h = h[0]+h[0]+h[1]+h[1]+h[2]+h[2] }
348        if (h.length !== 6 && h.length !== 8) return null
349        return {
350            r: parseInt(h.substring(0, 2), 16),
351            g: parseInt(h.substring(2, 4), 16),
352            b: parseInt(h.substring(4, 6), 16),
353            a: h.length === 8 ? (parseInt(h.substring(6, 8), 16) / 25
3535) : 1
354        }
355    }
356
357    /**
358     * Compute accessible font color (#222222 or #ffffff) based on WCAG contrast ratio.
359     * Reuses the existing hexToRgba() helper defined above.
360     */
361    function computeInverseColor(bgColor) {
362        if (!bgColor) return '#222222'
363        function luminance(color) {
364            const rgba = hexToRgba(color);
365            if (!rgba) return 0
366            return [rgba.r, rgba.g, rgba.b].map(function(x) {
367                x /= 255
368                return x <= 0.03928 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4)
369            }).reduce(function(sum, v, i) { return sum + v * [0.2126, 0.7152, 0.0722][i] }, 0)
370        }
371        function contrastRatio(l1, l2) { return l1 > l2 ? (l1 + 0.05) / (l2 + 0.05) : (l2 + 0.05) / (l1 + 0.05) }
372
373        const lBg = luminance(bgColor);
374        return contrastRatio(lBg, luminance('#222222')) > contrastRatio(lBg, luminance('#ffffff')) ? '#222222' : '#ffffff'
375    }
376
377    /**
378     * Write theme config as :root CSS variables so the page can update
379     * in real-time (live preview via postMessage, or on initial load).
380     * Only sets a var when the corresponding value is non-empty.
381     */
382    function applyThemeCSSVars(theme) {
383        if (!theme) return
384        const styles = theme.styles || {};
385        const root = document.documentElement;
386
387        loadHostedBrandFontFromFamily(styles.pageFont)
388
389        // Helper: set only when value is truthy; clear the var when value is empty
390        function set(name, value) {
391            if (value !== null && value !== undefined && value !== '') {
392                root.style.setProperty(name, value)
393            } else if (value === '' || value === null) {
394                root.style.removeProperty(name)
395            }
396        }
397
398        function setColorOrTransparent(name, value) {
399            root.style.setProperty(name, (value !== null && value !== undefined && value !== '') ? value : 'transparent')
400        }
401
402        function setColorOrBlack(name, value) {
403            root.style.setProperty(name, (value !== null && value !== undefined && value !== '') ? value : '#000000')
404        }
405
406        function setOrKeepPrevious(name, value) {
407            if (value !== null && value !== undefined && value !== '') {
408                root.style.setProperty(name, value)
409            }
410        }
411
412        // ── Core theme vars (used by theme.html CSS) ──
413        setOrKeepPrevious('--theme-color',      styles.themeColor)
414        setColorOrTransparent('--page-bg-color', styles.pageBgColor)
415        set('--page-font',              styles.pageFont)
416        set('--page-font-color',        computeInverseColor(styles.pageBgColor))
417        setOrKeepPrevious('--primary-btn-text-color', styles.themeColor ? computeInverseColor(styles.themeColor) : null)
418        setColorOrTransparent('--header-bg-color', styles.headerBgColor)
419        setColorOrBlack('--header-text-color', styles.headerTextColor)
420        setColorOrTransparent('--footer-bg-color', styles.footerBgColor)
421        setColorOrBlack('--footer-text-color', styles.footerTextColor)
422
423        // bg-image: wrap in url() when non-empty, clear when empty
424        if (styles.pageBgImage && styles.pageBgImage !== '') {
425            root.style.setProperty('--page-bg-image', 'url("' + styles.pageBgImage + '")')
426        } else {
427            root.style.setProperty('--page-bg-image', '')
428        }
429
430        // bg-position: default 50% when absent
431        root.style.setProperty('--page-bg-align',
432            (styles.pageBgAlign && styles.pageBgAlign !== '') ? styles.pageBgAlign : '50%')
433
434        // bg-mirror: '1' → rotateY(180deg), else empty
435        root.style.setProperty('--page-bg-mirror',
436            styles.pageBgMirror === '1' ? 'rotateY(180deg)' : '')
437
438        // ── Campaign landing page vars (portal-landing-page.css, portal-constituent.css) ──
439        set('--landing-page-font-family',    styles.pageFont)
440        set('--landing-page-font-color',     computeInverseColor(styles.pageBgColor))
441        setOrKeepPrevious('--landing-page-theme-color',    styles.themeColor)
442        setOrKeepPrevious('--primary-theme-color',         styles.themeColor)
443        setOrKeepPrevious('--landing-page-btn-text-color', styles.themeColor ? computeInverseColor(styles.themeColor) : null)
444        // defaultBackgroundColor is resolved from themeColor (same value), used when no hero image
445        setOrKeepPrevious('--landing-page-hero-bg-color',  styles.themeColor)
446
447        // ── themeColor → RGBA decomposition ──
448        // Updates --campaign-progress-r/g/b/a (campaign-progress.css)
449        // and --goal-meter-widget-r/g/b/a and computes opacity variants for landing page
450        if (styles.themeColor) {
451            const rgba = hexToRgba(styles.themeColor);
452            if (rgba) {
453                root.style.setProperty('--campaign-progress-r', rgba.r)
454                root.style.setProperty('--campaign-progress-g', rgba.g)
455                root.style.setProperty('--campaign-progress-b', rgba.b)
456                root.style.setProperty('--campaign-progress-a', rgba.a)
457                root.style.setProperty('--goal-meter-widget-r', rgba.r)
458                root.style.setProperty('--goal-meter-widget-g', rgba.g)
459                root.style.setProperty('--goal-meter-widget-b', rgba.b)
460                root.style.setProperty('--goal-meter-widget-a', rgba.a)
461                root.style.setProperty('--landing-page-theme-color-opacity',
462                    'rgba(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ',0.15)')
463                root.style.setProperty('--landing-page-theme-color-opacity-over',
464                    'rgba(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ',0.7)')
465            }
466        }
467
468        // keep calibration in sync
469        if (styles.themeColor && typeof setCalibrationColor === 'function') {
470            setCalibrationColor(styles.themeColor)
471        }
472
473        let extraConfig = theme.extraConfig ? theme.extraConfig : {}
474        if (extraConfig && extraConfig.correctDashboard) {
475            if (extraConfig.useWhiteHeader) {
476                root.style.setProperty('--dashboard-header-color', '#fff')
477                root.style.setProperty('--dashboard-header-text-color', '#3C4144')
478                root.style.setProperty('--dashboard-header-hover-link-color', '#E2F2FE')
479            } else {
480                setColorOrTransparent('--dashboard-header-color', styles.headerBgColor)
481                setColorOrBlack('--dashboard-header-text-color', styles.headerTextColor)
482                setColorOrTransparent('--dashboard-header-hover-link-color', styles.headerBgColor)
483            }
484        } else {
485            // Clear dashboard-specific vars when not on dashboard to avoid unintended overrides
486            root.style.removeProperty('--dashboard-header-color')
487            root.style.removeProperty('--dashboard-header-text-color')
488            root.style.removeProperty('--dashboard-header-hover-link-color')
489        }
490
491        // ── Fullscreen preview: hide/show dashboard-specific elements ─
492        let fullscreenStyle = document.getElementById('_neonFullscreenStyle')
493        if (!fullscreenStyle) {
494            fullscreenStyle = document.createElement('style')
495            fullscreenStyle.id = '_neonFullscreenStyle'
496            document.head.appendChild(fullscreenStyle)
497        }
498        if (extraConfig && extraConfig.fullscreen === false) {
499            fullscreenStyle.textContent = '.neonfront-portal-left-nav { visibility: hidden !important; } .neonfront-portal-nav-bar { display: none !important; } .dashboard_welcome .sidebar-title { display: none !important; } .dashboard_welcome .welcome-text { margin-left: 16px; }'
500        } else {
501            fullscreenStyle.textContent = ''
502        }
503    }
504
505    // ── Footer column text and naviLinks processing ───────────────────
506    // Replace tokens in footer col texts and naviLink URLs via a single backend call.
507    // If no relevant data, calls onDone immediately without an API request.
508    function processFooterColTexts(theme, onDone) {
509        const hasFooter = theme.footerCol1Text || theme.footerCol2Text || theme.footerCol3Text;
510        const hasNaviToken = theme.naviLinks && theme.naviLinks.some(function (l) {
511            return l.url && l.url.indexOf('Organization URL') !== -1;
512        });
513        if (!hasFooter && !hasNaviToken) {
514            onDone(theme);
515            return;
516        }
517        axios.post('/nx/portal/theme/processColTexts', {
518            footerCol1Text: theme.footerCol1Text,
519            footerCol2Text: theme.footerCol2Text,
520            footerCol3Text: theme.footerCol3Text,
521            naviLinks:      theme.naviLinks,
522        }).then(function (res) {
523            var processed = theme;
524            if (res.data && res.data.success && res.data.data) {
525                processed = Object.assign({}, theme, res.data.data);
526            }
527            onDone(processed);
528        }).catch(function (e) {
529            onDone(theme);
530        });
531    }
532
533    // ── Footer request token: UUID-based guard against stale async callbacks ──
534    // Each DOMContentLoaded or postMessage generates a new UUID token.
535    // Any async callback whose token no longer matches the current one is discarded.
536    window._neonFooterToken = null;
537    window._neonFooterDebounceTimer = null;
538    window._neonPrevTheme = null;      // cache of last applied patchData (excludes footer cols)
539    window._neonPrevTokenSrc = null;   // cache of raw source for token-processed fields (naviLinks + footer cols)
540
541    // Deep-equal comparison: JSON for objects/arrays, strict for primitives.
542    function _themeValEqual(a, b) {
543        if (a === b) return true;
544        if (a == null || b == null) return a === b;
545        if (typeof a === 'object') return JSON.stringify(a) === JSON.stringify(b);
546        return false;
547    }
548
549    // Recursively patch only changed properties on a Vue reactive target.
550    // Plain objects are patched property-by-property; arrays/primitives are assigned directly.
551    function _patchReactive(target, source, prev) {
552        Object.keys(source).forEach(function(k) {
553            const nv = source[k], pv = prev ? prev[k] : undefined;
554            if (prev && _themeValEqual(pv, nv)) return;
555            const tIsObj = target[k] != null && typeof target[k] === 'object' && !Array.isArray(target[k]);
556            const nIsObj = nv != null && typeof nv === 'object' && !Array.isArray(nv);
557            if (tIsObj && nIsObj) _patchReactive(target[k], nv, pv || {});
558            else target[k] = nv;
559        });
560    }
561
562    // ── Initial page load: wait for DOM, then process footer ──────────
563    document.addEventListener('DOMContentLoaded', function () {
564
565        // ── CLP embed: set before Vue mounts so clpEmbedEnv is reactive ───
566        if (new URLSearchParams(window.location.search).get('clpEmbedEnv') === '1') {
567            sessionStorage.setItem('clpEmbedEnv', '1');
568        }
569        if (window.top !== window.self && sessionStorage.getItem('clpEmbedEnv') === '1') {
570            const _clpStyle = document.createElement('style');
571            _clpStyle.id = '_clpEmbedStyle';
572            _clpStyle.textContent = '#header-content, #footer-content { display: none !important; }';
573            document.head.appendChild(_clpStyle);
574        }
575
576        var theme = window._neonThemeData;
577        if (!theme) return;
578        loadHostedBrandFontFromFamily(theme.styles && theme.styles.pageFont);
579        var myToken = crypto.randomUUID();
580        window._neonFooterToken = myToken;
581        // Hide footer cols while processing to avoid showing raw tokens
582        if (window.__neonApp) {
583            window.__neonApp.theme = Object.assign({}, window.__neonApp.theme, {
584                footerCol1Text: '',
585                footerCol2Text: '',
586                footerCol3Text: '',
587            });
588        }
589        processFooterColTexts(theme, function (processed) {
590            if (window._neonFooterToken !== myToken) return;
591            if (window.__neonApp) {
592                window.__neonApp.theme = Object.assign({}, window.__neonApp.theme, {
593                    footerCol1Text: processed.footerCol1Text,
594                    footerCol2Text: processed.footerCol2Text,
595                    footerCol3Text: processed.footerCol3Text,
596                    naviLinks: processed.naviLinks,
597                });
598            }
599        });
600    });
601
602    // ── CLP embed: listen for parent postMessage (handles already-logged-in scenario) ──
603    window.addEventListener('message', function (event) {
604        if (!event.data || event.data.sharingType !== 'clpEmbed') return;
605        if (window.top === window.self) {
606            return;
607        }
608        sessionStorage.setItem('clpEmbedEnv', '1');
609        if (event.data.embedIframeId) sessionStorage.setItem('clpEmbedIframeId', event.data.embedIframeId);
610        if (event.data.domainUrl) sessionStorage.setItem('clpEmbedDomainUrl', event.data.domainUrl);
611        if (!document.getElementById('_clpEmbedStyle')) {
612            const _clpStyle = document.createElement('style');
613            _clpStyle.id = '_clpEmbedStyle';
614            _clpStyle.textContent = '#header-content, #footer-content { display: none !important; }';
615            document.head.appendChild(_clpStyle);
616        }
617    });
618
619    // ── postMessage listener (live preview) ───────────────────────────
620    window.addEventListener('message', function (event) {
621        if (!event.data || event.data.type !== 'NEON_THEME_PREVIEW') return
622        let newTheme = event.data.template;
623        if (!newTheme) return
624
625        console.log('NEON_THEME_PREVIEW received', newTheme);
626
627        const myToken = crypto.randomUUID();
628        window._neonFooterToken = myToken;
629
630        const ALIGN_MAP = {LEFT: 'L', CENTER: 'C', RIGHT: 'R', L: 'L', C: 'C', R: 'R'};
631        const COLOR_MAP = {COLOR: 'C', WHITE: 'L', BLACK: 'D', C: 'C', W: 'L', B: 'D', L: 'L', D: 'D'};
632
633        function enumVal(v, map, fb) { if (!v) return fb; if (typeof v === 'object') v = v.value || fb; return map[v] || fb }
634
635        newTheme = Object.assign({}, newTheme, {
636            extraConfig:          event.data.extraConfig || {},
637            socialLinkIconAlign:  enumVal(newTheme.socialLinkIconAlign,  ALIGN_MAP, 'L'),
638            socialLinkIconStyle: enumVal(newTheme.socialLinkIconStyle, COLOR_MAP, 'L'),
639        })
640
641        // ── Step 1: immediately patch only changed non-footer properties ──
642        // Footer cols are excluded: hidden while API processes to avoid showing raw tokens.
643        // naviLinks included: raw URL changes are shown immediately (same as before).
644        const patchData = Object.assign({}, newTheme);
645        delete patchData.footerCol1Text;
646        delete patchData.footerCol2Text;
647        delete patchData.footerCol3Text;
648
649        const prev = window._neonPrevTheme;
650        const changedKeys = Object.keys(patchData).filter(function (k) {
651            return !prev || !_themeValEqual(prev[k], patchData[k]);
652        });
653        if (changedKeys.length > 0) {
654            const target = window.__neonApp ? window.__neonApp.theme : window._neonThemeData;
655            if (target) _patchReactive(target, patchData, prev);
656            if (changedKeys.indexOf('styles') !== -1 || changedKeys.indexOf('extraConfig') !== -1) {
657                applyThemeCSSVars(patchData);
658            }
659            window._neonPrevTheme = JSON.parse(JSON.stringify(patchData));
660        }
661
662        // ── Step 2: debounce token API only when naviLinks or footer cols source changed ──
663        const tokenSrc = {
664            col1: newTheme.footerCol1Text,
665            col2: newTheme.footerCol2Text,
666            col3: newTheme.footerCol3Text,
667            nav: JSON.stringify(newTheme.naviLinks),
668        };
669        const ps = window._neonPrevTokenSrc;
670        if (ps && ps.col1 === tokenSrc.col1 && ps.col2 === tokenSrc.col2 && ps.col3 === tokenSrc.col3 && ps.nav === tokenSrc.nav) return;
671
672        clearTimeout(window._neonFooterDebounceTimer)
673        window._neonFooterDebounceTimer = setTimeout(function () {
674            const capturedToken = myToken;
675            processFooterColTexts(newTheme, function (processed) {
676                if (window._neonFooterToken !== capturedToken) return;
677                if (window.__neonApp) {
678                    window.__neonApp.theme['naviLinks']      = processed.naviLinks;
679                    window.__neonApp.theme['footerCol1Text'] = processed.footerCol1Text;
680                    window.__neonApp.theme['footerCol2Text'] = processed.footerCol2Text;
681                    window.__neonApp.theme['footerCol3Text'] = processed.footerCol3Text;
682                }
683                window._neonPrevTokenSrc = tokenSrc;
684                window.parent.postMessage({ type: 'NEON_THEME_READY' }, '*')
685            })
686        }, 300)
687    })
688</script>
vendor: 1 bytes, line 688
688
689<script>
690    window._neonThemeData = {
691        id:                   21,
692        logo:                 null,
693        logoAlt:              null,
694        logoWidth:            150,
695        logoUrl:              null,
696        footerColumns:        2,
697        footerCol1Text:       null,
698        footerCol2Text:       null,
699        footerCol3Text:       null,
700        displaySocialLink:    true,
701        socialLinkIconAlign:  "C",
702        socialLinkIconStyle: "L",
703        styles:               {"headerBgColor":"","headerTextColor":"#222222","pageFont":"Arial,sans-serif","pageBgColor":"#E8E9F6","pageBgMirror":"0","pageBgImage":"https:\/\/stgeorgessociety.app.neoncrm.com\/neon\/resource\/stgeorgessociety\/images\/Copy%20of%20Event%20Invitation%20Template%20(5).png","themeJSSnippets":"","themeColor":"#282968","pageBgImageAlt":"Copy%20of%20Event%20Invitation%20Template%20(5).png","pageBgAlign":"center center","footerBgColor":"#525386","pageFontColor":"#3C4144","footerTextColor":"#ffffff","primaryBtnTextColor":"#ffffff","actBtnTextColor":"#282968"},
704        socialLinks:          [{"createBy":"Patrick Mell","createTime":"2025-01-24T08:46:13.000-06:00","lastModifyBy":"Patrick Mell","lastModifyTime":"2025-01-24T08:46:13.000-06:00","id":99,"templateId":21,"icon":"Facebook","url":"https:\/\/www.facebook.com\/SGSNY"},{"createBy":"Patrick Mell","createTime":"2025-01-24T08:46:13.000-06:00","lastModifyBy":"Patrick Mell","lastModifyTime":"2025-01-24T08:46:13.000-06:00","id":100,"templateId":21,"icon":"Twitter","url":"https:\/\/www.twitter.com\/sgs_ny"}],
705        naviLinks:            [],
706        _homePageURL:         "https:\/\/www.stgeorgessociety.org\/",
707        _previewMode:         false,
708    }
709    applyThemeCSSVars(window._neonThemeData)
710</script>
710
711
712
713    </head>
714<body>
715<div class="neon-body" id="app" v-cloak="">
716  <div class="neon-background"></div>
717  <div class="neonfront">
718    
719<!DOCTYPE html>
720<html>
721<body>
722    <nav class="navbar navbar-expand-lg pt-0" style="padding-bottom: 0!important;" id="header-content">
723        <div style="position: relative; width: 100%">
724        <div class="container-fluid neonfront_header">
725            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
726                <i class="fas fa-bars"></i>
727            </button>
728            <div class="navbar-brand">
729                <div class="neonfront_logo">
730                    <!-- Case 1: logoUrl set -> link to logoUrl, open in new tab -->
731                    <a v-if="theme.logoUrl"
732                       :href="theme.logoUrl.startsWith('http://') || theme.logoUrl.startsWith('https://') || theme.logoUrl.startsWith('//') ? theme.logoUrl : 'https://' + theme.logoUrl"
733                       :style="theme._previewMode ? {'pointer-events': 'none'} : {}"
734                       target="_blank">
735                        <img v-if="theme.logo && theme.logoWidth !== 0" :src="theme.logo" :width="theme.logoWidth || undefined" :alt="theme.logoAlt" onerror="this.style.display='none';"/>
736                        <span v-else-if="!theme.logo">{{theme.logoAlt}}</span>
737                    </a>
738                    <!-- Case 2: no logoUrl but homePageURL -> original behavior -->
739                    <a v-else-if="theme._homePageURL"
740                       :href="theme._homePageURL.startsWith('http://') || theme._homePageURL.startsWith('https://') || theme._homePageURL.startsWith('//') ? theme._homePageURL : 'https://' + theme._homePageURL"
741                       :style="theme._previewMode ? {'pointer-events': 'none'} : {}"
742                       target="_blank">
743                        <img v-if="theme.logo && theme.logoWidth !== 0" :src="theme.logo" :width="theme.logoWidth || undefined" :alt="theme.logoAlt" onerror="this.style.display='none';"/>
744                        <span v-else-if="!theme.logo">{{theme.logoAlt}}</span>
745                    </a>
746                    <!-- Case 3: no link -->
747                    <template v-else>
748                        <img v-if="theme.logo && theme.logoWidth !== 0" :src="theme.logo" :width="theme.logoWidth || undefined" :alt="theme.logoAlt" onerror="this.style.display='none';"/>
749                        <span v-else-if="!theme.logo">{{theme.logoAlt}}</span>
750                    </template>
751                </div>
752            </div>
753            <div class="collapse navbar-collapse" id="navbarSupportedContent">
754                <ul class="neonfront_links navbar-nav mb-2 mb-lg-0">
755                    <template v-for="link in theme.naviLinks" :key="link.id">
756                        <li v-if="link.url && link.name" class="nav-item">
757                            <a class="neonfront_link" :href="!link.url ? '#' : link.url.startsWith('http://') || link.url.startsWith('https://') ? (link.url === 'http://' || link.url === 'https://' ? '#' : link.url) : 'https://' + link.url"
758                               :style="theme._previewMode ? {'pointer-events': 'none'} : {}"
759                               :target="link.openInNewTab==1 ? '_blank' : '_self'">{{link.name}}</a>
760                        </li>
761                    </template>
762                    
763                    
764                </ul>
765            </div>
766        </div>
767        </div>
768    </nav>
769</body>
770</html>
771
772    
773    <div class="neonfront_cover container-fluid">
774      
775      <div class="row flex-grow-1" id="content">
776        <div class="col">
777          <div class="d-flex neonfront-portal-content">
778            <!-- Left Menu -->
779            
780            
781            <!-- Right Content -->
782            <div class="flex-grow-1" id="right-content">
783              <div class="neonfront_constituent_body container-lg">
784                  <!DOCTYPE html>
785<html lang="en">
786<body>
787<div>
788    <div>
789        <div class="login_tab_dom">
790            <div class="login_tab" :class="activeTab === 0? 'active_tab_left' : ''" @click="tabChange(0)">St. George's Society of New York Login</div>
791            <div class="login_tab" :class="activeTab === 1? 'active_tab_right' : ''" @click="tabChange(1)">Fundraiser Login</div>
792        </div>
793        <div class="neonfront_container neonfront-login p2p-neonfront-login">
794            <div class="col-12" id="neonj-page-1">
795                <div class="row g-3 neonfront-form">
796                    
797                    
798                    <div class="col-md-12 neonfront_element">
799                        <div class="login-portal-section-title">Welcome Back!</div>
800                        <label class="section-label fs-6 login-title">Log in to your account</label>
801                    </div>
802                    
803                    <div class="col-md-12 neonfront_element mt-0">
804                        <div id="globalError" class="row mt-3 neonfront-portal neonfront_form_failed_container" v-show="globalError">
805                            <div class="col-2">
806                                <span class="fa fa-exclamation-circle mt-1" style="font-size: 2.25rem;"></span>
807                            </div>
808                            <div class="col-10">
809                                <h5 class="fs-6 mb-1" style="line-height: 42px">{{globalError}}</h5>
810                            </div>
811                        </div>
812                    </div>
813                    <div class="neonfront_element col-md-12">
814                        <label for="loginName" class="form-label">Username or Email</label>
815                        <input id="loginName" v-model="loginName" name="currentPwd" type="text" @input="loginNameError = ''" class="form-control neonfront_input" :class="{'is-invalid': loginNameError}" data-testid="field-login-name">
816                        <div class="invalid-feedback">
817                            <div><i class="fa fa-exclamation-triangle"></i>
818                                Username or Email is required.
819                            </div>
820                        </div>
821                    </div>
822                    <div class="neonfront_element col-md-12 login-password-field">
823                        <label for="password" class="form-label">Password</label>
824                        <input id="password" v-model="password" name="password" type="password" @input="passwordError = ''" class="form-control neonfront_input" :class="{'is-invalid': passwordError}" data-testid="field-password">
825                        <i class="fas fa-eye-slash presentation" v-if="!showPassword" @click="showPsd(true)"></i>
826                        <i class="fas fa-eye presentation" v-if="showPassword" @click="showPsd(false)"></i>
827                        <div class="invalid-feedback">
828                            <div><i class="fa fa-exclamation-triangle"></i>Password is required.</div>
829                        </div>
830                    </div>
831                    <div class="neonfront_element col-md-12 login-additional">
832                        <div class="form-check login-remember-me">
833                            <input class="form-check-input neonfront_checkbox" type="checkbox" value="yes" id="persistCookie" v-model="persistCookie">
834                            <label class="form-check-label neonfront_checkbox_label" for="persistCookie">
835                                Remember me for 2 weeks
836                            </label>
837                        </div>
838                        <div class="trouble-login">
839                            <a href="/requestPassword" data-testid="link-forgot-password">Having trouble logging in?</a>
840                            
841                        </div>
842                    </div>
843                    <div class="col-md-12 neonfront_element">
844                        <div class="row g-2 justify-content-center">
845                            <div class="col">
846                                <button type="button" class="btn neonfront-btn-primary btn-lg" @click="login" v-show="!loading" data-testid="bt
846n-login">Login</button>
847                                <button class="btn neonfront-btn-primary btn-lg" type="button" disabled v-show="loading">
848                                    Processing&nbsp; <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
849                                </button>
850                            </div>
851                        </div>
852                    </div>
853                    <div class="col-md-12 neonfront_element login-separate">
854                        <div class="separate-line with-social-login"></div>
855                        <div class="separate-text">or log in with</div>
856                        <div class="separate-line with-social-login"></div>
857                    </div>
858                    <div class="col-md-12 neonfront_element social-login">
859                        <span class="third_part_login_item" style="margin-right: 2px;">
860                            <img width="40" src="/nx/static/img/icons/social-media/Facebook.png" @click="fn_openAuthWin('facebook')">
861                        </span>
862                        <span class="third_part_login_item" style="margin-left: 2px;">
863                            <img width="40" src="/nx/static/img/icons/social-media/Twitter.png" @click="fn_openAuthWin('twitter')">
864                        </span>
865                    </div>
866                    <div class="col-md-12 neonfront_element">
867                        <div class="create-account">
868                            Don't have an account?
869                            <a v-if="activeTab === 0 || autoMergeOn" href="/forms/createaccount">Create an account</a>
870                            <a v-else href="javascript:void(0)" @click="createFundraiser">Create an account</a>
871                        </div>
872                    </div>
873                    
874                </div>
875            </div>
876        </div>
877    </div>
878</div>
879<div class="modal fade neonfront-modal landing-page-login-modal" aria-hidden="true" id="fundraiserLoginModal" ref="createP2pAcctModal" data-bs-backdrop="static" tabindex="-1">
880    <div class="modal-dialog" style="max-width: 580px;">
881        <div class="modal-content px-sm-4 px-1">
882            <div class="modal-header">
883                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
884            </div>
885
886            <div class="modal-body" style="padding-top:0;padding-bottom:0">
887                <div class="row mt-1">
888                    <div class="col login-body-title p2p-login-body-title">
889                        Create an Account
890                    </div>
891                </div>
892                <div class="row mt-1">
893                    <div class="login-body-col">
894                        <div class="body-line" style="display: flex">
895                            <span>
896                                <label for="firstName" class="input-label">First Name *</label>
897                                <input type="text" v-model="acctData.firstName" id="firstName" name="firstName" class="form-control half-line" :class="{'invalid': acctRules.fname.invalid}" @blur="checkAcctFieldVal($event, acctRules.fname)" />
898                                <div v-if="acctRules.fname.invalid" class="field-err-msg"><i class="fa fa-exclamation-triangle"></i>{{acctRules.fname.errMsg}}</div>
899                            </span>
900                            <span style="margin-left: 30px;">
901                                <label for="lastName" class="input-label">Last Name *</label>
902                                <input type="text" v-model="acctData.lastName" class="form-control half-line" id="lastName" name="lastName" :class="{'invalid': acctRules.lname.invalid}" @blur="checkAcctFieldVal($event, acctRules.lname)" />
903                                <div v-if="acctRules.lname.invalid" class="field-err-msg"><i class="fa fa-exclamation-triangle"></i>{{acctRules.lname.errMsg}}</div>
904                            </span>
905                        </div>
906                        <div class="body-line">
907                            <label for="emailAddress" class="input-label" >Email Address *</label>
908                            <input type="text" v-model="acctData.email" class="form-control" id="emailAddress" name="emailAddress" :class="{'invalid': acctRules.email.invalid}" @blur="checkAcctFieldVal($event, acctRules.email)" />
909                            <div v-if="acctRules.email.invalid" class="field-err-msg"><i class="fa fa-exclamation-triangle"></i>{{acctRules.email.errMsg}}</div>
910                        </div>
911                        <div class="body-line">
912                            <label for="createPassword" class="input-label">Create Password *</label>
913                            <input type="password" v-model="acctData.password" class="form-control" id="createPassword" name="createPassword" :class="{'invalid': acctRules.psd1.invalid}" @blur="checkAcctFieldVal($event, acctRules.psd1)"/>
914                            <span>
915                                <i class="fa fa-info-circle"></i>
916                                <span class="info-message-text">Enter at least 8 characters, including at least one number. Spaces not allowed.</span>
917                            </span>
918                            <i class="fas fa-eye-slash presentation1" v-if="!showCreatePassword" @click="showCreatePsd(true)"></i>
919                            <i class="fas fa-eye presentation1" v-if="showCreatePassword" @click="showCreatePsd(false)"></i>
920                            <div v-if="acctRules.psd1.invalid" class="field-err-msg" style="margin-top: -1.25rem"><i class="fa fa-exclamation-triangle"></i>{{acctRules.psd1.errMsg}}</div>
921                        </div>
922                        <div class="body-line" style="margin-top: -20px;margin-bottom: 0px !important;">
923                            <label for="confirmPassword" class="input-label">Confirm Password *</label>
924                            <input type="password" class="form-control" id="confirmPassword" name="confirmPassword" :class="{'invalid': acctRules.psd2.invalid}" @blur="checkAcctFieldVal($event, acctRules.psd2)"/>
925                            <i class="fas fa-eye-slash presentation2" v-if="!showConfirmPassword" @click="showConfirmPsd(true)"></i>
926                            <i class="fas fa-eye presentation2" v-if="showConfirmPassword" @click="showConfirmPsd(false)"></i>
927                            <div v-if="acctRules.psd2.invalid" class="field-err-msg" style="margin-top:-17px;"><i class="fa fa-exclamation-triangle"></i>{{acctRules.psd2.errMsg}}</div>
928                        </div>
929
930                        <div v-if="gdprSettings.profileEnabled || gdprSettings.statementEnabled" class="body-line">
931                            <label class="input-label">Data Privacy & Consent</label>
932                            <div class="gdpr-setting-section">
933                                <div class="gdpr-setting-items">
934                                    <div v-if="gdprSettings.email" class="gdpr-setting-item neonfront_element">
935                                        <div class="checkbox-column">
936                                            <input v-model="gdprValues.email" type="checkbox" id="gdpr.email" name="gdpr.email" class="form-check-input neonfront_checkbox"/>
937                                        </div>
938                                        <label for="gdpr.email" class="setting-item-label">{{gdprSettings.email}}</label>
939                                    </div>
940                                    <div v-if="gdprSettings.phone" class="gdpr-setting-item neonfront_element">
941                                        <div class="checkbox-column">
942                                            <input v-model="gdprValues.phone" type="checkbox" id="gdpr.phone" name="gdpr.phone" class="form-check-input neonfront_checkbox"/>
943                                        </div>
944                                        <label for="gdpr.email" class="setting-item-label">{{gdprSettings.phone}}</label>
945                                    </div>
946                                    <div v-if="gdprSettings.mail" class="gdpr-setting-item neonfront_element">
947                                        <div class="checkbox-column">
948                                            <input v-model="gdprValues.mail" type="checkbox" id="gdpr.mail" name="gdpr.mail" class="form-check-input neonfront_checkbox"/>
949                                        </div>
950                                        <label for="gdpr.mail" class="setting-item-label">{{gdprSettings.mail}}</label>
951                                    </div>
952                                    <div v-if="gdprSettings.sms" class="gdpr-setting-item neonfront_element">
953                                        <div class="checkbox-column">
954                                            <input v-model="gdprValues.sms" type="checkbox" id="gdpr.sms" name="gdpr.sms" class="form-check-input neonfront_checkbox"/>
955                                        </div>
956                                        <label for="gdpr.sms" class="setting-item-label">{{gdprSettings.sms}}</label>
957                                    </div>
958                                    <div v-show="gdprValues.sms" class="smsPhoneNum-section" style="padding-left:25px">
959                                        <label for="smsPhoneNum" class="input-label">What's the best number for us to text you? *</label>
960                                        <input v-model="gdprValues.smsPhoneNum" type="text" class="form-control" id="smsPhoneNum" name="smsPhoneNum" :class="{'invalid': gdprValues.smsPhoneInvalid}" @blur="checkGdprSmsPhoneNum($event)"/>
961                                        <div v-if="gdprValues.smsPhoneInvalid" class="field-err-msg"><i class="fa fa-exclamation-triangle"></i>{{gdprValues.smsPhoneInvalidMsg}}</div>
962                                    </div>
963                                    <div v-if="gdprSettings.share" class="gdpr-setting-item neonfront_element">
964                                        <div class="checkbox-column">
965                                            <input v-model="gdprValues.share" type="checkbox" id="gdpr.share" name="gdpr.share" class="form-check-input neonfront_checkbox"/>
966                                        </div>
967                                        <label for="gdpr.share" class="setting-item-label">{{gdprSettings.share}}</label>
968                                    </div>
969                                </div>
970
971                                <div v-if="gdprSettings.statement" class="gdpr-statement">
972                                    {{gdprSettings.statement}}
973                                </div>
974                                <div v-if="gdprSettings.policyUrl" class="gdpr-policy">
975                                    <a :href="gdprSettings.policyUrl" target="_blank" style="text-decoration: none">{{gdprSettings.policyLabel}}</a>
976                                </div>
977                            </div>
978                        </div>
979                    </div>
980                </div>
981            </div>
982            <div class="login-footer p2p-login-footer">
983                <button v-if="!doCreateAcct" type="button"
984                        class="btn neonfront-btn-primary btn-lg p2p-btn"
985                        :disabled="checkCreateAcctField"
986                        @click="doP2pCreateAccount">Submit</button>
987                <button v-if="doCreateAcct" class="btn neonfront-btn-primary btn-lg p2p-btn" type="button" disabled>
988                    Processing&nbsp; <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
989                </button>
990                <div class="col-md-12 neonfront_element login-separate p2p_login_separate">
991                    <div class="separate-line"></div>
992                    <div class="separate-text">or</div>
993                    <div class="separate-line"></div>
994                </div>
995                <div class="go_login_sty">Have an account? <a href="javascript:void(0)" data-bs-dismiss="modal">Log in</a></div>
996            </div>
997        </div>
998    </div>
999</div>
1000
1001</body>
1002</html>
1003
1004              </div>
1005            </div>
1006          </div>
1007        </div>
1008      </div>
1009    </div>
1010    <!DOCTYPE html>
1011<html>
1012<body>
1013    <div class="container-fluid neonfront_footer" id="footer-content">
1014        <div class="row neonfront_footer_lv_info">
1015            <div v-if="theme.footerColumns==1" class="col-md-12" style="padding-left:30px;padding-right:30px;">
1016                <div v-html="theme.footerCol1Text"></div>
1017            </div>
1018            <div v-if="theme.footerColumns==2" class="col-md-6" style="padding-left:30px;">
1019                <div class="float-md-start float-none" v-html="theme.footerCol1Text"></div>
1020            </div>
1021            <div v-if="theme.footerColumns==2" class="col-md-6" style="padding-right:30px;">
1022                <div class="float-md-end float-none" v-html="theme.footerCol2Text"></div>
1023            </div>
1024            <div v-if="theme.footerColumns==3" class="col-md-4" style="padding-left:30px;">
1025                <div class="float-md-start float-none" v-html="theme.footerCol1Text"></div>
1026            </div>
1027            <div v-if="theme.footerColumns==3" class="col-md-4 text-md-center footer_center_column">
1028                <div class="footer_center_column_content" style="margin:0 auto;" v-html="theme.footerCol2Text"></div>
1029            </div>
1030            <div v-if="theme.footerColumns==3" class="col-md-4" style="padding-right:30px;">
1031                <div class="float-md-end float-none" v-html="theme.footerCol3Text"></div>
1032            </div>
1033        </div>
1034        <div v-if="theme.displaySocialLink"
1035             :class="['neonfront_footer_lv_media',
1036                      theme.socialLinkIconAlign==='C' ? 'text-center' : (theme.socialLinkIconAlign==='R' ? 'text-end' : 'text-start'),
1037                      theme.socialLinkIconStyle==='L' ? 'text-white' : 'text-dark']">
1038            <template v-for="item in (theme.socialLinks||[])" :key="item.icon">
1039                <a v-if="item.url"
1040                   :href="item.icon==='Email' ? 'mailto:'+item.url : (item.url.startsWith('http://') || item.url.startsWith('https://') || item.url.startsWith('//') ? item.url : 'https://'+item.url)"
1041                   :style="theme._previewMode ? {'pointer-events': 'none'} : {}"
1042                   target="_blank"
1043                   class="text-reset social-link-icon">
1044                    <img width="24" height="24"
1045                         :src="getSocialMediaIcon(item.icon, theme.socialLinkIconStyle)">
1046                </a>
1047            </template>
1048        </div>
1049        
1050        <div class="footer-root">
1051            <div class="footer-root-left"><a class="footer-root-link" href="https://neonone.com/privacypolicy/" target="_blank">Privacy Statement</a>   |  <a class="footer-root-link" href="https://neonone.com/productterms/" target="_blank">Terms of Service</a></div>
1052            <div class="footer-root-right">
1053                <a href="https://neonone.com/" target="_blank">
1054                <div class="footer-root-right-header"><img width="60" height="20" src="/nx/portal-static/img/logo-variations.svg"/></div>
1055                <div class="footer-root-right-footer"><img width="60" height="20" src="/nx/portal-static/img/merchant-copy.svg"/></div>
1056                </a>
1057            </div>
1058        </div>
1059    </div>
1060</body>
1061</html>
1062  </div>
1063  
1064  
1065    <!DOCTYPE html>
1066<html lang="en">
1067<body>
1068    <!DOCTYPE html>
1069<html lang="en">
1070<body>
1071<!-- Modal -->
1072<div class="modal fade neonfront-modal neonfront-confirm-modal" id="confirmSubMembershipEndModal" data-bs-backdrop="static" tabindex="-1">
1073    <div class="modal-dialog" style="max-width: 580px;">
1074        <div class="modal-content px-sm-4 px-1" style="z-index: 20000;">
1075            <div class="modal-header">
1076                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @click="cancelSubMembershipEndTurnOff"></button>
1077            </div>
1078            <div class="modal-body">
1079                <div class="row mt-1">
1080                    <div class="col">
1081                        <div>Are you sure you want to end the selected sub-membership? This action will set the End Date to one day in the past.</div>
1082                    </div>
1083                </div>
1084            </div>
1085            <div class="modal-footer">
1086                <button type="button" class="btn btn-link neonfront-btn-l
1086ink" @click="cancelSubMembershipEndTurnOff" style="width: auto">Cancel</button>
1087                <button type="button" class="btn neonfront-btn-primary btn-lg px-4" @click="confirmSubMembershipEndTurnOff" style="width: auto">Confirm</button>
1088            </div>
1089        </div>
1090    </div>
1091</div>
1092</body>
1093</html>
1094    <!DOCTYPE html>
1095<html lang="en">
1096<body>
1097<!-- Modal -->
1098<div class="modal fade neonfront-modal neonfront-confirm-modal" id="addressListDeleteModal" data-bs-backdrop="static" tabindex="-1">
1099  <div class="modal-dialog" style="max-width: 580px;">
1100    <div class="modal-content px-sm-4 px-1" style="z-index: 20000;">
1101      <div class="modal-header">
1102        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @click="cancelAddressListDeleteModal"></button>
1103      </div>
1104      <div class="modal-body">
1105        <div class="row mt-1">
1106          <div class="col">
1107            <p>Are you sure you want to delete this address?</p>
1108            <p class="desc">Your address will be deleted.</p>
1109          </div>
1110        </div>
1111      </div>
1112      <div class="modal-footer">
1113        <button type="button" class="btn btn-link neonfront-btn-link" @click="cancelAddressListDeleteModal" style="width: auto">Cancel</button>
1114        <button type="button" class="btn neonfront-btn-primary btn-lg px-4" @click="confirmAddressListDeleteModal" style="width: auto">Confirm</button>
1115      </div>
1116    </div>
1117  </div>
1118</div>
1119</body>
1120</html>
1121    <!DOCTYPE html>
1122<html lang="en">
1123<body>
1124<!-- Modal -->
1125<div class="modal fade neonfront-modal neonfront-confirm-modal" id="addressListCommonDeleteModal" data-bs-backdrop="static" tabindex="-1">
1126  <div class="modal-dialog" style="max-width: 580px;">
1127    <div class="modal-content px-sm-4 px-1" style="z-index: 20000;">
1128      <div class="modal-header">
1129        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @click="cancelAddressListDeleteModal"></button>
1130      </div>
1131      <div class="modal-body">
1132        <div class="row mt-1">
1133          <div class="col">
1134            <p>Address cannot be deleted because of the following linked data:</p>
1135            <p class="desc" v-for="item in deleteInfo">
1136              {{decisionDeleteInfo(item)}}
1137            </p>
1138          </div>
1139        </div>
1140      </div>
1141      <div class="modal-footer">
1142        <button type="button" class="btn neonfront-btn-primary btn-lg px-4" @click="cancelAddressListCommonDeleteModal" style="width: auto;margin-left: auto">Close</button>
1143      </div>
1144    </div>
1145  </div>
1146</div>
1147</body>
1148</html>
1149</body>
1150</html>
1151  
1152  <div class="modal fade neonfront-modal" id="goP2pConfirmModal" data-bs-backdrop="static" tabindex="-1">
1153    <div class="modal-dialog">
1154        <div class="modal-content px-sm-4 px-1 go-p2p-confirm-modal">
1155            <div class="header">
1156                Leaving this Page
1157            </div>
1158            <div class="content">
1159                You are about to be redirected to another portal to manage your Peer-to-Peer Fundraising pages. You can return to this portal at any time.
1160                <br/><br/>Do you want to continue?
1161            </div>
1162            <div class="footer">
1163                <button type="button" class="btn btn-link neonfront-btn-link" @click="cancelP2pConfirmModal()"
1164                        style="width: auto; font-family: var(--page-font, Lato)">Cancel
1165                </button>
1166                <button type="button" class="btn neonfront-btn-primary btn-lg px-4" @click="confirmP2pConfirmModal"
1167                        style="width: auto; font-family: var(--page-font, Lato)">Continue
1168                </button>
1169            </div>
1170            <i class="fa-solid fa-xmark-large close_icon" @click="cancelP2pConfirmModal"></i>
1171        </div>
1172    </div>
1173</div>
1174
1175</div>
1176
1177<script src="/nx/portal-static/component/vue/vue-3.1.2.min.js"></script>
vendor: 1 bytes, line 1177
1177
1178<script src="/nx/portal-static/component/axios/axios.min.js"></script>
vendor: 1 bytes, line 1178
1178
1179<script src="/nx/portal-static/component/qs/qs.min.js"></script>
vendor: 1 bytes, line 1179
1179
1180<script src="/nx/portal-static/component/bootstrap/js/bootstrap.bundle.min.js"></script>
vendor: 1 bytes, line 1180
1180
1181<script src="/nx/portal-static/component/laydate/laydate.js"></script>
vendor: 1 bytes, line 1181
1181
1182<script src="/nx/portal-static/component/cropperjs/cropper.min.js"></script>
vendor: 1 bytes, line 1182
1182
1183<script src="/nx/portal-static/component/moment-js/moment-2.30.1.min.js"></script>
vendor: 1 bytes, line 1183
1183
1184<script src="/nx/portal-static/component/moment-js/moment-timezone.min.js"></script>
1184
1185
1186<script src="/nx/portal-static/js/portal.all.js"></script>
1186
1187<!--
1188<th:block th:if="${churnZeroEnabled && !#strings.isEmpty(churnZeroAccountExternalId)}">
1189<script src="https://neonone.us1app.churnzero.net/churnzero.js"></script>
vendor: 1 bytes, line 1189
1189
1190<script type="application/javascript">
1191    var ChurnZero = ChurnZero || []
1192
1193    ChurnZero.push(['setAppKey', '1!MFdwDlKbVUgVpSRtljcqdO9tOYWtqDKqGG7trJX5JYwtE02']) // AppKey from ChurnZero
1194    ChurnZero.push(['setContact', '', ''])
1195</script>
1195
1196</th:block>
1197-->
1198
1199<script type="application/javascript">
1200    // Common JS Code
1201</script>
1201
1202
1203
1204
1205
1206<script>
1207    // CLP embed: notify parent on DOMContentLoaded for re-postMessage
1208    (function () {
1209        const urlParams = new URLSearchParams(window.location.search);
1210        const clpEmbedEnv = sessionStorage.getItem('clpEmbedEnv') === '1'
1211            || urlParams.get('clpEmbedEnv') === '1';
1212        if (!clpEmbedEnv) return;
1213        const embedIframeId = urlParams.get('embedIframeId') || sessionStorage.getItem('clpEmbedIframeId');
1214        const embedIframeDomainUrl = urlParams.get('embedIframeDomainUrl') || sessionStorage.getItem('clpEmbedDomainUrl');
1215        if (!embedIframeId || !embedIframeDomainUrl) return;
1216        window.addEventListener('DOMContentLoaded', function () {
1217            parent.postMessage({type: 'DOMContentLoaded', embedIframeId: embedIframeId}, embedIframeDomainUrl);
1218        });
1219    }());
1220</script>
1220
1221
1222<script src="/nx/portal-static/js/clp-embed-content.js"></script>
vendor: 66 bytes, lines 1222-1225
1222
1223
1224
1225<script async src="https://www.googletagmanager.com/gtag/js?id=
1225G-E5PVK7VDYC
vendor: 12 bytes, line 1225
1225"></script>
1226<script>
1227    
vendor: 139 bytes, lines 1227-1230
1227window.dataLayer = window.dataLayer || [];
1228    function gtag(){dataLayer.push(arguments);}
1229    gtag('js', new Date());
1230    gtag('config', '
1230G-E5PVK7VDYC
vendor: 4 bytes, line 1230
1230');
1231</script>
vendor: 72 bytes, lines 1231-1234
1231
1232
1233
1234  <script src="https://www.google.com/recaptcha/enterprise.js?render=
12346Lciqi8rAAAAAG87Wg7usbkvyHVTXylFPgeSyHkM
vendor: 24 bytes, line 1234
1234" async defer></script>
1235<script type="text/javascript">
1236    const appData = {
1237        showCreatePassword: false,
1238        showConfirmPassword: false,
1239        doLoginLoading: false,
1240        doCreateAcct: false,
1241        acctRules: {
1242            fname: {type: 'name', invalid: null, errMsg: 'First Name is required.'},
1243            lname: {type: 'name', invalid: null, errMsg: 'Last Name is required.'},
1244            email: {type: 'email', invalid: null, errMsg: 'Email Address is required.'},
1245            psd1: {type: 'psd1', invalid: null, errMsg: 'Create Password is required.'},
1246            psd2: {type: 'psd2', invalid: null, errMsg: 'Confirm Password is required.'}
1247        },
1248        acctData: {
1249            firstName: '',
1250            lastName: '',
1251            email: '',
1252            password: ''
1253        },
1254        gdprSettings: {},
1255        autoMergeOn: false,
1256        gdprValues: {
1257            email: false,
1258            phone: false,
1259            mail: false,
1260            sms: false,
1261            share: false,
1262            smsPhoneNum: '',
1263            smsPhoneInvalid: null,
1264            smsPhoneInvalidMsg: ''
1265        },
1266        loginName: '',
1267        password: '',
1268        persistCookie: '',
1269        loading: false,
1270        loginNameError: false,
1271        passwordError: false,
1272        globalError: '',
1273        showPassword: false,
1274        source: '',
1275        // OAuth Authorization Server handoff: literal "as" == OauthConstant.LOGIN_SOURCE / LoginSourceConstants.OAUTH_AS
1276        targetUrl:'',
1277        redirectUrl: '',
1278        fromNeonWebSite: false,
1279        embeddedLogin: false,
1280        activeTab: 0,
1281        googleReCaptchaEnabled: true,
1282        googleReCaptchaEnabled0: true,
1283        enableModernLogin: false,
1284        fromLogout: false,
1285        recaptchaResponse: null
1286    }
1287
1288    const appMethods = {
1289        tabChange(index){
1290            this.activeTab = index
1291        },
1292        doLogin: function () {
1293            if (this.enableModernLogin) {
1294                if (this.embeddedLogin || this.source === 'embedded') {
1295                    const loginName = btoa(this.loginName)
1296                    const loginPassword = btoa(this.password)
1297                    const url = '/nx/auth/authenticate?source=embedded'
1298                        + '&loginName=' + encodeURIComponent(loginName)
1299                        + '&loginPassword=' + encodeURIComponent(loginPassword)
1300                        + (this.persistCookie === true ? '&rememberMe=true' : '')
1301                    window.location.href = url
1302                    return
1303                }
1304                // Align with legacy signIn.do: password login does not submit/verify reCAPTCHA.
1305                this.loading = true
1306                this.globalError = ''
1307                // Modern resolves the post-login redirect server-side, so the active tab has to
1308                // travel with the request (the legacy branch below picks /p2p/overview locally).
1309                // source=as owns targetUrl — ai= is appended to it — so never overwrite it there.
1310                let target = this.targetUrl || this.redirectUrl || ''
1311                if (!target && !this.source && this.activeTab === 1) {
1312                    target = '/p2p/overview'
1313                }
1314                let url = '/nx/auth/authenticate?targetUrl=' + encodeURIComponent(target);
1315                if (this.source) {
1316                    url += '&source=' + encodeURIComponent(this.source);
1317                }
1318                axios.post(url, {
1319                    loginName: this.loginName,
1320                    password: this.password,
1321                    rememberMe: this.persistCookie === true,
1322                    requestId: 'd5343ff9-4d64-461d-b247-ca4ea3e435ba',
1323                    source: this.source || ''
1324                }).then((res) => {
1325                    const body = res.data || {};
1326                    if (body.success && body.data) {
1327                        window.location = body.data.redirect;
1328                    } else {
1329                        this.loading = false
1330                        const msgs = body.messages || [];
1331                        this.globalError = (msgs[0] && msgs[0].message) || 'Login Name/Password is invali
1331d';
1332                    }
1333                }).catch(() => {
1334                    this.loading = false
1335                    this.globalError = 'Login Name/Password is invalid';
1336                });
1337                return
1338            }
1339            let $loginData = {
1340                loginName: this.loginName,
1341                loginPassword: this.password,
1342                persistCookie: this.persistCookie === true ? 'yes' : this.persistCookie,
1343                ajaxLogin: true,
1344                source: this.fromNeonWebSite? 'duda' : this.source,
1345                targetUrl: this.targetUrl || this.redirectUrl,
1346                requestId: 'd5343ff9-4d64-461d-b247-ca4ea3e435ba'
1347            }
1348            this.loading = true
1349            this.globalError = ''
1350            axios.post('/np/security/signIn.do', Qs.stringify($loginData)).then(response => {
1351                let $data = response.data
1352                if ($data.status === 'success') {
1353                    if (this.loginName === 'admin') {
1354                        window.location = '/np/admin/content/contentList.do'
1355                    } else if ($data.succeedRedirectPath) {
1356                        window.location = $data.succeedRedirectPath
1357                    } else if(this.activeTab === 1) {
1358                        window.location = '/p2p/overview'
1359                    } else {
1360                        window.location = '/constituent/dashboard'
1361                    }
1362                } else {
1363                    this.loading = false
1364                    this.globalError = $data.errors[0]
1365                }
1366            })
1367        },
1368        login: function () {
1369            let validatePass = true
1370            if (Validator.required.check(this.loginName)) {
1371                this.loginNameError = true
1372                validatePass = false
1373            }
1374            if (Validator.required.check(this.password)) {
1375                this.passwordError = true
1376                validatePass = false
1377            }
1378            if (!validatePass) {
1379                return
1380            }
1381            this.passwordError = false
1382            this.loginNameError = false
1383            this.doLogin()
1384        },
1385
1386        showPsd: function ($show) {
1387            if ($show) {
1388                this.showPassword = true
1389                document.querySelector('#password').type = 'text'
1390            } else {
1391                this.showPassword = false
1392                document.querySelector('#password').type = 'password'
1393            }
1394        },
1395        fn_openAuthWin: function (src) {
1396            if (this.enableModernLogin) {
1397                // Same sources as password login (targetUrl / redirectUrl), plus live ?redirect=
1398                // from the address bar — Thymeleaf injection alone has been unreliable for social.
1399                const params = new URLSearchParams(window.location.search || '');
1400                let path = (params.get('redirect')
1401                    || params.get('target')
1402                    || this.targetUrl
1403                    || this.redirectUrl
1404                    || '').trim();
1405                if (path) {
1406                    try {
1407                        path = decodeURIComponent(path);
1408                    } catch (e) { /* keep raw */ }
1409                }
1410                if (!path) {
1411                    path = window.location.pathname + window.location.search;
1412                    const pathOnly = (window.location.pathname || '').replace(/\/+$/, '') || '/';
1413                    if (pathOnly === '/login' || pathOnly === '/nx/login' || /\/login$/.test(pathOnly)) {
1414                        path = '/';
1415                    }
1416                }
1417                neonSocialLogin(src, {
1418                    targetUrl: path,
1419                    orgId: this.orgId,
1420                    // Literal "as" == OauthConstant.LOGIN_SOURCE / LoginSourceConstants.OAUTH_AS
1421                    custom: this.source === 'as' ? 'as' : undefined
1422                });
1423                return;
1424            }
1425            const path = "/np/clients/" + this.orgId + "/snOpenAuthWin.do?";
1426            const param = "src=" + src + "&da=login"+ (this.fromNeonWebSite?'&custom=duda':'');
1427            const url = path + param;
1428            const size = 'width=750,height=450,top=230,left=370,toolbar=no';
1429            window.location.href = url;
1430        },
1431        enterClick: function (e) {
1432            if (e.keyCode === 13) {
1433                if (e.target.closest('.neonfront_input')) {
1434                    this.login()
1435                }
1436                if (e.target.closest('.form-check-input')) {
1437                    this.login()
1438                }
1439            }
1440        },
1441        listenEnterKey: function () {
1442            document.addEventListener('keydown', this.enterClick)
1443        },
1444        createFundraiser: function () {
1445            this.doLoginLoading = false
1446            this.doCreateAcct = false
1447            this.p2pLoginErrorMessages = []
1448            this.acctRules.fname.invalid = null
1449            this.acctRules.lname.invalid = null
1450            this.acctRules.email.invalid = null
1451            this.acctRules.psd1.invalid = null
1452            this.acctRules.psd2.invalid = null
1453            this.$nextTick(() => {
1454                const modalElement = this.$refs.createP2pAcctModal;
1455                document.body.appendChild(modalElement);
1456                const modal = new bootstrap.Modal(this.$refs.createP2pAcctModal)
1457                modal.show()
1458            })
1459        },
1460        invisibleReCaptchaInit: function () {
1461            window.ReCaptchaLoaded = this.reCaptchaCallback;
1462            let tag = document.createElement("div")
1463            tag.className = 'g-recaptcha'
1464            document.body.appendChild(tag);
1465            let timer = setInterval(() => {
1466                if(window.grecaptcha && grecaptcha.enterprise && typeof window.grecaptcha.enterprise.render === 'function'){
1467                    window.grecaptcha.enterprise.render(tag,{
1468                        sitekey: '6Lciqi8rAAAAAG87Wg7usbkvyHVTXylFPgeSyHkM',
1469                        size: 'invisible'
1470                    });
1471                    clearInterval(timer);
1472                }
1473            },500);
1474        },
1475        reCaptchaCallback: function ($token) {
1476            this.recaptchaResponse = $token
1477            if(window.grecaptcha && window.grecaptcha.enterprise) {
1478                window.grecaptcha.enterprise.reset()
1479            }
1480            this.doP2pCreateAccountSendToServer()
1481        },
1482        validateReCaptcha:function () {
1483            if(window.grecaptcha && window.grecaptcha.enterprise) {
1484                this.recaptchaResponse = null
1485                window.grecaptcha.enterprise.execute().then(($token) => {
1486                    this.reCaptchaCallback($token)
1487                })
1488            } else {
1489                this.doP2pCreateAccountSendToServer()
1490            }
1491        },
1492        doP2pCreateAccount: function () {
1493            this.doCreateAcct = true
1494            this.validateReCaptcha()
1495        },
1496        doP2pCreateAccountSendToServer: function () {
1497            let formData = new FormData();
1498            formData.append('firstName', this.acctData.firstName.trim());
1499            formData.append('lastName', this.acctData.lastName.trim());
1500            formData.append('email', this.acctData.email.trim());
1501            formData.append('password', this.acctData.password.trim());
1502            formData.append('recaptchaResponse', this.recaptchaResponse);
1503            if (this.gdprSettings.profileEnabled) {
1504                formData.append('gdpr.email', this.gdprValues.email);
1505                formData.append('gdpr.phone', this.gdprValues.phone);
1506                formData.append('gdpr.mail', this.gdprValues.mail);
1507                formData.append('gdpr.sms', this.gdprValues.sms);
1508                formData.append('gdpr.share', this.gdprValues.share);
1509                formData.append('gdpr.smsPhoneNum', this.gdprValues.smsPhoneNum);
1510            }
1511            axios.post('/nx/portal/p2p-data/create-account', formData, {}).then(response => {
1512                if (response.data.success) {
1513                    this.doP2pLogin(this.acctData.email, this.acctData.password)
1514                } else {
1515                    alert(response.data.messages[0].message);
1516                    this.doCreateAcct = false
1517                }
1518            }).catch(error => {
1519                console.log(error)
1520                this.doCreateAcct = false
1521                alert('Create account failed, please try again.')
1522            })
1523        },
1524        doP2pLogin: function (name, password) {
1525            this.doLoginLoading = true
1526            if (this.enableModernLogin) {
1527                // Align with legacy: password login does not submit/verify reCAPTCHA.
1528                let url = '/nx/auth/authenticate?targetUrl=' + encodeURIComponent('/p2p/overview')
1529                axios.post(url, {
1530                    loginName: name,
1531                    password: password,
1532                    requestId: 'd5343ff9-4d64-461d-b247-ca4ea3e435ba'
1533                }).then((res) => {
1534                    const body = res.data || {};
1535                    if (body.success && body.data) {
1536                        window.location.href = body.data.redirect;
1537                    } else {
1538                        const msgs = body.messages || [];
1539                        this.globalError = (msgs[0] && msgs[0].message) || 'Login failed';
1540                        alert(this.globalError)
1541                    }
1542                    this.doCreateAcct = false
1543                    this.doLoginLoading = false
1544                }).catch((error) => {
1545                    console.error(error.response)
1546                    alert('Login error: ' + JSON.stringify(error.response))
1547                    this.doLoginLoading = false
1548                })
1549                return
1550            }
1551            let params = {
1552                loginName: name,
1553                loginPassword: password,
1554                orgId: 'stgeorgessociety',
1555                ajaxLogin: true,
1556                requestId: 'd5343ff9-4d64-461d-b247-ca4ea3e435ba'
1557            }
1558            axios.post('/np/security/encouragedSignIn.do', Qs.stringify(params)).then(response => {
1559                let result = response.data
1560                if (result.status === 'success') {
1561                    window.location.href = 'https://' + window.location.host + '/p2p/overview'
1562                } else {
1563                    alert('Login error: ' + JSON.stringify(result.errors))
1564                }
1565                this.doCreateAcct = false
1566                this.doLoginLoading = false
1567            }).catch(error => {
1568                console.error(error.response)
1569                alert('Login error: ' + JSON.stringify(error.response))
1570                this.doLoginLoading = false
1571            })
1572        },
1573        checkAcctFieldVal: function ($event, rule) {
1574            let val = $event.target.value.trim();
1575            if (rule.type === 'name' && !val) {
1576                rule.invalid = true
1577            } else if (rule.type === 'email' && !val) {
1578                rule.errMsg = 'Email Address is required.'
1579                rule.invalid = true
1580            } else if (rule.type === 'email' && Validator['email'].check(val)) {
1581                rule.errMsg = 'Email Address format is invalid.'
1582                rule.invalid = true
1583            } else if (rule.type === 'psd1' && !val) {
1584                rule.errMsg = 'Create Password is required.'
1585                rule.invalid = true
1586            } else if (rule.type === 'psd1' && Validator.password.check(val)) {
1587                rule.errMsg = 'Your password requires at least 8 characters, including at least one number. Spaces are not allowed.'
1588                rule.invalid = true
1589            } else if (rule.type === 'psd2') {
1590                if (val) {
1591                    let val1 = document.querySelector('#createPassword').value
1592                    rule.errMsg = 'Confirm Password does not match.'
1593                    rule.invalid = (val !== val1.trim())
1594                } else {
1595                    rule.errMsg = 'Confirm Password is required.'
1596                    rule.invalid = true
1597                }
1598            } else {
1599                rule.invalid = false
1600            }
1601        },
1602        showConfirmPsd: function ($show) {
1603            if ($show) {
1604                this.showConfirmPassword = true
1605                document.querySelector('#confirmPassword').type = 'text'
1606            } else {
1607                this.showConfirmPassword = false
1608                document.querySelector('#confirmPassword').type = 'password'
1609            }
1610        },
1611        showCreatePsd: function ($show) {
1612            if ($show) {
1613                this.showCreatePassword = true
1614                document.querySelector('#createPassword').type = 'text'
1615            } else {
1616                this.showCreatePassword = false
1617                document.querySelector('#createPassword').type = 'password'
1618            }
1619        },
1620        loadGdprSetting: function () {
1621            let previewMode = false
1622            if (previewMode) {
1623                return;
1624            }
1625            axios.get('/nx/portal/p2p-data/login-settings').then(response => {
1626                if (response.data.success) {
1627                    this.gdprSettings = response.data.data.gdprSettings
1628                    this.autoMergeOn = response.data.data.autoMergeOn
1629                }
1630            }).catch(error => {
1631                alert('Load GDPR settings failed, please try again.')
1632            })
1633        },
1634        checkGdprSmsPhoneNum: function ($event) {
1635            let val = $event.target.value.trim();
1636            if (!val) {
1637                this.gdprValues.smsPhoneInvalid = true
1638                this.gdprValues.smsPhoneInvalidMsg = 'The SMS/MMS Number is required.'
1639                return
1640            }
1641            let usPhoneReg = /^(\+?1[-\s]?)?\(?[2-9]\d{2}\)?[-\s]?[2-9](?!11)\d{2}[-\s]?\d{4}$/
1642            if (!usPhoneReg.test(val.replace(/\s+/g, ''))) {
1643                this.gdprValues.smsPhoneInvalid = true
1644                if (val.replace(/[\s\-()]/g, '').match(/^[^1]\d{10}$/)) {
1645                    this.gdprValues.smsPhoneInvalidMsg = '11-digit numbers must begin with the country code 1 (US/Canada).'
1646                } else {
1647                    this.gdprValues.smsPhoneInvalidMsg = 'Please enter a valid 10 or 11 digit mobile number.'
1648                }
1649                return
1650            }
1651            this.gdprValues.smsPhoneInvalid = false
1652            this.gdprValues.smsPhoneInvalidMsg = ''
1653        }
1654    }
1655
1656    const appComputed = {
1657        checkCreateAcctField: function () {
1658            return !(this.acctRules.fname.invalid === false && this.acctRules.lname.invalid === false
1659                && this.acctRules.email.invalid === false && this.acctRules.psd1.invalid === false
1660                && this.acctRules.psd2.invalid === false && (!this.gdprValues.sms || (this.gdprValues.smsPhoneNum && this.gdprValues.smsPhoneInvalid === false)))
1661        },
1662    }
1663
1664    const appWatch = {}
1665
1666    const appConfig = {
1667        data: function () {
1668            return appData
1669        },
1670        methods: appMethods,
1671        computed: appComputed,
1672        watch: appWatch,
1673        created: function() {
1674            const params = new URLSearchParams(window.location.search);
1675            const activeTab = params.get('activeTab');
1676            if(activeTab === '1') {
1677                this.activeTab = 1
1678            }
1679        },
1680        mounted: function () {
1681            if(this.googleReCaptchaEnabled || this.googleReCaptchaEnabled0) {
1682                this.invisibleReCaptchaInit()
1683            }
1684            this.listenEnterKey()
1685            this.loadGdprSetting()
1686        }
1687    }
1688</script>
1688
1689
1690
1691
1692<script type="application/javascript">
1693  appData.loggedIn = false
1694  appData.previewMode = false
1695  appData.orgId = 'stgeorgessociety'
1696  appData.currentAccountId = 
1697  appData.accountBasicInfo = ''
1698  appData.profilePhoto = {
1699    url: '',
1700    approved: false
1701  }
1702  appData.showMenu = false
1703  appData.formCurrency= '$'
1704  appData.formCurrencyCode= 'USD'
1705  appData.theme = window._neonThemeData || {}
1706  appMethods.formatAmount = function (amount) {
1707    return Formatter.numberFormat(amount, 2, this.formCurrency)
1708  }
1709  appMethods.showP2pConfirmModal = function() {
1710      bootstrap.Modal.getInstance(document.getElementById("goP2pConfirmModal")).show()
1711  }
1712  appMethods.cancelP2pConfirmModal = function(){
1713      bootstrap.Modal.getInstance(document.getElementById("goP2pConfirmModal")).hide()
1714  }
1715  appMethods.confirmP2pConfirmModal = function(){
1716      window.location.href='/p2p/overview'
1717  }
1718  appMethods.getSocialMediaIcon = function (name, style) {
1719    if (typeof getSocialIconURL === 'function') return getSocialIconURL(name, style)
1720    // legacy fallback
1721    return style === 'D'
1722      ? '/nx/static/img/icons/social-media/darkicons/' + name + '.svg'
1723      : '/nx/static/img/icons/social-media/lighticons/' + name + '.svg'
1724  }
1725  // Add interceptor for portal page
1726  axios.interceptors.request.use(function (config) {
1727    if(appData.previewMode){
1728      config.url = config.url.replace('/portal/constituent','/admin/portal/fake').replace('/portal/p2p','/admin/portal/fake/p2p').replace('/portal/event-list','/admin/portal/fake/event-list')
1729    }
1730    return config
1731  }, function (error) {
1732    // ...
1733    return Promise.reject(error)
1734  });
1735
1736  (function (){
1737      if(window.location.href.includes('/constituent')){
1738          window.addEventListener( "pageshow", function ( $event ) {
1739              const performanceNavigationTiming = performance.getEntriesByType('navigation')[0];
1740              // performanceNavigationTiming.type === 'back_forward' for firefox
1741              const historyTraversal = $event.persisted || performanceNavigationTiming.type === 'back_forward';
1742              if ( historyTraversal ) {
1743                  window.location.reload();
1744              }
1745          });
1746      }
1747  })();
1748  const originalMounted = appConfig.mounted
1749  appConfig.mounted = function() {
1750      const result = originalMounted.apply(this);
1751      const $dom = document.getElementById("goP2pConfirmModal")
1752      if($dom) {
1753          new bootstrap.Modal($dom)
1754      }
1755      return result
1756  }
1757  appWatch.theme = {
1758    handler: function(newVal) {
1759      if (typeof applyThemeCSSVars === 'function') applyThemeCSSVars(newVal)
1760    },
1761    deep: true
1762  }
1763  const vueApp = Vue.createApp(appConfig)
1764  vueApp.config.errorHandler = function (err, vm, info) {
1765    if (handleVueJSError) {
1766      handleVueJSError(err, vm, info)
1767    }
1768    console.log(err)
1769  }
1770  vueApp.component('neon-multiselect', window.NeonMultiselect)
1771  vueApp.component('neon-search-box', window.NeonSearchBox)
1772  vueApp.component('neon-fileupload', window.NeonFileupload)
1773  vueApp.component('neon-advancedsearch', window.NeonAdvancedsearch)
1774  vueApp.component('neon-select', window.NeonSelect)
1775  vueApp.component('neon-img-cutter', window.NeonImgCutter)
1776  vueApp.component('neon-pagination', window.NeonPagination)
1777  vueApp.component('neon-pagination-plus', window.NeonPaginationPlus)
1778  vueApp.component('neon-share-modal', window.NeonShareModal)
1779  vueApp.component('neon-search-select', window.NeonSearchSelect)
1780  vueApp.component('neon-search-location', window.NeonSearchLocation)
1781  vueApp.component('neon-input', window.NeonInput)
1782  const app = vueApp.mount('#app')
1783  window.__neonApp = app
1784</script>
1784
1785
1786
1787
1788  
1788<script type="text/javascript">
1789    function setSliderBar(func , wait , immediate) {
1790        let timeout
1791        return function () {
1792            let context = this, args = arguments
1793            let later = function() {
1794                timeout = null
1795                if (!immediate) func.apply(context, args)
1796            }
1797            let callNow = immediate & !timeout
1798            clearTimeout(timeout);
1799            timeout = setTimeout(later, wait);
1800            if (callNow) func.apply(context, args)
1801        }
1802    }
1803    function fixSliderBar() {
1804        let maxWidth = document.body.clientWidth
1805        const leftBar = document.getElementById("left-nav")
1806        const content = document.getElementById("content")
1807        const rightContent = document.getElementById("right-content")
1808        const footContent = document.getElementById("footer-content")
1809        const fixedTitle = document.getElementById("fixed-title")
1810        const flexRow = rightContent.parentNode
1811        const clientHeight = window.innerHeight
1812        if (maxWidth >= 992) {
1813            let isClpEmbed = false
1814            try {
1815                isClpEmbed = sessionStorage.getItem('clpEmbedEnv') === '1'
1816                    || new URLSearchParams(window.location.search).get('clpEmbedEnv') === '1'
1817            } catch (e) {
1818            }
1819            if (isClpEmbed) {
1820                leftBar.style.position = 'static'
1821                leftBar.style.top = ''
1822                leftBar.style.bottom = ''
1823                leftBar.style.height = ''
1824                leftBar.style.minHeight = ''
1825                leftBar.style.left = ''
1826                leftBar.style.width = ''
1827                flexRow.style.paddingLeft = ''
1828                rightContent.style.marginLeft = ''
1829                rightContent.style.minHeight = '0'
1830                try {
1831                    fixedTitle.style.display = 'none'
1832                } catch (e) {
1833                }
1834                leftBar.style.maxHeight = ''
1835                leftBar.style.overflow = ''
1836                const contentHeight = rightContent.offsetHeight
1837                if (contentHeight > 0 && leftBar.scrollHeight > contentHeight) {
1838                    leftBar.style.maxHeight = contentHeight + 'px'
1839                    leftBar.style.overflow = 'hidden auto'
1840                }
1841                return
1842            }
1843            if (leftBar.getBoundingClientRect().top <= 0) {
1844                //float in top on footer
1845                const footerHidden = footContent.offsetHeight === 0
1846                let heightVary = footContent.getBoundingClientRect().bottom - clientHeight
1847                let varyHeight = footContent.offsetHeight - heightVary
1848                if (!footerHidden && varyHeight >= 0 && heightVary <= footContent.offsetHeight) {
1849                    //footer show
1850                    if(!leftBar.classList.contains('neonfront-p2p-portal-left-nav')) {
1851                        leftBar.style.bottom = varyHeight + 'px'
1852                        leftBar.style.height = (clientHeight - varyHeight) + 'px'
1853                    } else {
1854                        leftBar.style.bottom = '0px'
1855                        leftBar.style.height = clientHeight + 'px'
1856                    }
1857                    rightContent.style.minHeight = (clientHeight - varyHeight) + 'px'
1858                    leftBar.style.overflow = 'hidden auto'
1859                } else {
1860                    // footer hide
1861                    leftBar.style.height = clientHeight + 'px'
1862                    leftBar.style.bottom = ''
1863                    rightContent.style.minHeight = clientHeight + 'px'
1864                    leftBar.style.overflow = 'hidden auto'
1865                }
1866                if (leftBar.style.position !== 'fixed') {
1867                    const leftBarWidth = leftBar.offsetWidth || 260
1868                    leftBar.style.position = 'fixed'
1869                    leftBar.style.top = '0'
1870                    leftBar.style.width = leftBarWidth + 'px'
1871                    flexRow.style.paddingLeft = leftBarWidth + 'px'
1872                    rightContent.style.marginLeft = ''
1873                }
1874                leftBar.style.left = flexRow.getBoundingClientRect().left + 'px'
1875                try {
1876                    fixedTitle.style.display = ''
1877                } catch (e) {
1878                }
1879            }
1880            if (content.getBoundingClientRect().top > 0) {
1881                //roll back
1882                leftBar.style.position = 'static'
1883                leftBar.style.top = ''
1884                leftBar.style.bottom = ''
1885                leftBar.style.left = ''
1886                leftBar.style.width = ''
1887                flexRow.style.paddingLeft = ''
1888                rightContent.style.marginLeft = ''
1889                leftBar.style.minHeight = ''
1890                rightContent.style.minHeight = '0'
1891                try {
1892                    fixedTitle.style.display = 'none'
1893                } catch (e) {
1894                }
1895            }
1896        } else {
1897            //roll back
1898            leftBar.style.position = 'static'
1899            leftBar.style.top = ''
1900            leftBar.style.bottom = ''
1901            leftBar.style.left = ''
1902            leftBar.style.width = ''
1903            flexRow.style.paddingLeft = ''
1904            rightContent.style.marginLeft = ''
1905            leftBar.style.minHeight = ''
1906            rightContent.style.minHeight = '0'
1907            try {
1908                fixedTitle.style.display = 'none'
1909            } catch (e) {
1910            }
1911        }
1912    }
1913    const preFix = setSliderBar(function() {
1914        fixSliderBar();
1915    }, 4);
1916    try {
1917        const leftBar = document.getElementById("left-nav")
1918        leftBar.style.maxHeight = window.innerHeight + 'px'
1919        leftBar.style.overflow = 'hidden auto'
1920        window.addEventListener('scroll',preFix)
1921        window.addEventListener('resize',preFix)
1922    } catch (ignore) {
1923        //without sidebar not need fix
1924    }
1925</script>
1925
1926
1927
1928
1929</body>
1930</html>

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.