ultimate-jekyll-manager 1.2.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -14,6 +14,27 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
14
14
  - `Fixed` for any bug fixes.
15
15
  - `Security` in case of vulnerabilities.
16
16
 
17
+ ---
18
+ ## [1.3.0] - 2026-05-21
19
+
20
+ ### Added
21
+
22
+ - **Marketing consent capture on the signup form.** Frontend half of `backend-manager` v5.2.0's consent system. `src/defaults/dist/_layouts/themes/classy/frontend/pages/auth/signup.html` replaces the legal-copy line with two real checkboxes (`consent-legal`, `consent-marketing`) wrapped in a `#consent-group` so validation can highlight the pair as a unit. `consent-legal` is required to submit.
23
+ - **`captureSignupConsent()` + `validateConsent()` in `src/assets/js/libs/auth.js`.** Pulls checkbox state + label text from the FormManager-collected data and writes it to `webManager.storage()` under key `consent` BEFORE Firebase auth fires — survives the post-signup redirect the same way `attribution` does. `validateConsent()` blocks submit via a phantom `__consent` field name and surfaces feedback via the wrapper outline + an inline error message instead of red-X-ing the single legal checkbox.
24
+ - **`reverseAccidentalSignup()` for the Google quirk.** Landing on `/signin` with an unknown Google account auto-creates the Firebase auth user; this reverses that path — deletes the user, signs out, strips `authReturnUrl`, and surfaces an inline form error. Best-effort delete; the page-load consent guard (below, currently OFF) is the backstop if delete fails.
25
+ - **`ENFORCE_CONSENT_GUARD` in `src/assets/js/core/auth.js`.** Page-load guard that silently signs out any authenticated user whose doc has `consent.legal.status !== 'granted'`. Default **FALSE** until the legacy-user migration runs (which sets all existing docs to `granted` + `source: 'imported'`); flipping it on before then would lock every existing user out.
26
+ - **`consent` field on the `sendUserSignupMetadata` payload.** Forwards the storage-survived consent blob to BEM's `/user/signup` route so it can write the canonical `consent.{legal,marketing}` sub-tree on the new user doc.
27
+ - **Marketing-emails toggle on the account page.** `src/defaults/dist/_layouts/themes/classy/frontend/pages/account/index.html` + `src/assets/js/pages/account/sections/notifications.js` reworked to read `account.consent.marketing.status` (not the old `preferences.notifications.marketing`) and POST to `/backend-manager/marketing/email-preferences` on change. Shows the original grant date below the toggle.
28
+
29
+ ### Changed
30
+
31
+ - **`web-manager` bumped to `^4.2.0`** (was `file:../web-manager` from local dev). Locks in `DEFAULT_ACCOUNT.consent.{legal,marketing}` so `resolveAccount()` always returns a defined consent shape for legacy users.
32
+ - Minor template touchups on `oauth2.html`, `reset.html`, `signin.html`, `token.html`, `signup.html` — heading casing, `filter-adaptive` class on the brandmark logo so it inverts in dark mode.
33
+
34
+ ### Fixed
35
+
36
+ - **`_team` seed authors** — corrected LinkedIn/Twitter handles in `christina-hill.md`, `james-oconnor.md`, `marcus-johnson.md`, `priya-sharma.md`, `sarah-rodriguez.md` so the default scaffolded `/team/` page links don't 404.
37
+
17
38
  ---
18
39
  ## [1.2.3] - 2026-05-19
19
40
 
@@ -4,6 +4,12 @@ import webManager from 'web-manager';
4
4
  // Constants
5
5
  const SIGNUP_MAX_AGE = 5 * 60 * 1000;
6
6
 
7
+ // Enforce page-load consent guard. When true, any authenticated user whose doc has
8
+ // consent.legal.status !== 'granted' is silently signed out. Keep FALSE until the
9
+ // legacy user migration runs (sets all existing docs to status='granted',
10
+ // source='imported'). Otherwise every existing user gets locked out on signin.
11
+ const ENFORCE_CONSENT_GUARD = false;
12
+
7
13
  // Auth Module
8
14
  export default function () {
9
15
  // Get auth policy
@@ -59,6 +65,25 @@ export default function () {
59
65
  if (user) {
60
66
  // User is authenticated
61
67
 
68
+ // Consent guard: if the user is authenticated but their account doc shows
69
+ // no legal consent on record, they're an orphan from a reversed Google signup
70
+ // that failed to delete cleanly. Sign them out and surface a toast so the
71
+ // user knows what happened (especially if they just clicked Google and saw
72
+ // a success message before this fired).
73
+ // Gated by ENFORCE_CONSENT_GUARD (off until the legacy-user migration runs).
74
+ if (ENFORCE_CONSENT_GUARD) {
75
+ const legalStatus = state.account?.consent?.legal?.status;
76
+ if (legalStatus && legalStatus !== 'granted') {
77
+ console.warn('[Auth] Signing out user with no legal consent on record');
78
+ await webManager.auth().signOut();
79
+ webManager.utilities().showNotification(
80
+ `This account hasn't completed setup. Please sign up first.`,
81
+ { type: 'danger', timeout: 8000 }
82
+ );
83
+ return;
84
+ }
85
+ }
86
+
62
87
  // Send user signup metadata if account is new
63
88
  await sendUserSignupMetadata(user);
64
89
 
@@ -241,12 +266,14 @@ async function sendUserSignupMetadata(user) {
241
266
 
242
267
  // Get attribution data from storage
243
268
  const attribution = webManager.storage().get('attribution', {});
269
+ const consent = webManager.storage().get('consent', {});
244
270
 
245
271
  // Build the payload
246
272
  const payload = {
247
273
  // New structure
248
274
  attribution: attribution,
249
275
  context: webManager.utilities().getContext(),
276
+ consent: consent,
250
277
  };
251
278
 
252
279
  // Get server API URL
@@ -104,6 +104,13 @@ export default function () {
104
104
  return async ({ data, $submitButton }) => {
105
105
  const provider = $submitButton?.getAttribute('data-provider');
106
106
 
107
+ // Capture consent BEFORE any Firebase call. On signup pages the checkbox state
108
+ // must survive any post-auth redirect so BEM's /user/signup can write it to the doc.
109
+ // Read from FormManager-collected data on signup; ignored on signin (no checkboxes there).
110
+ if (action === 'signup') {
111
+ captureSignupConsent(data);
112
+ }
113
+
107
114
  if (provider === 'email') {
108
115
  await emailHandler(data);
109
116
  } else if (provider) {
@@ -112,6 +119,101 @@ export default function () {
112
119
  };
113
120
  }
114
121
 
122
+ // Google's signInWithPopup/Redirect auto-creates accounts. If a user lands on /signin
123
+ // with a Google account that doesn't exist yet, Firebase creates one before we can stop it.
124
+ // This reverses that: delete the auth user, sign out, surface an inline error.
125
+ async function reverseAccidentalSignup(newUser) {
126
+ console.warn('[Auth] Reversing accidental signup from /signin (new Google account created with no consent on record)');
127
+
128
+ try {
129
+ await newUser.delete();
130
+ } catch (e) {
131
+ // Best-effort. If delete fails (network/token issue), the page-load consent guard
132
+ // is the backstop — the orphan account will be signed out on every future visit.
133
+ console.error('[Auth] Failed to delete accidental account:', e);
134
+ webManager.sentry().captureException(new Error('Failed to reverse accidental signup', { cause: e }));
135
+ }
136
+
137
+ try {
138
+ const { getAuth, signOut } = await import('@firebase/auth');
139
+ await signOut(getAuth());
140
+ } catch (e) {
141
+ console.error('[Auth] Failed to sign out after accidental signup:', e);
142
+ }
143
+
144
+ // Strip authReturnUrl so the next attempt doesn't redirect them away from /signin
145
+ const url = new URL(window.location.href);
146
+ if (url.searchParams.has('authReturnUrl')) {
147
+ url.searchParams.delete('authReturnUrl');
148
+ window.history.replaceState({}, document.title, url.toString());
149
+ }
150
+
151
+ if (formManager) {
152
+ formManager.showError(`This account doesn't exist. Try signing up first or use a different account.`);
153
+ formManager.ready();
154
+ }
155
+ }
156
+
157
+ // Validate that the user has agreed to the legal terms. Instead of highlighting the
158
+ // single legal checkbox in red (which subtly frames it as "the one that matters"),
159
+ // we surround BOTH checkboxes with a red outline and surface a top-level banner.
160
+ // This frames consent as a unit the user is confirming, not a hurdle to clear.
161
+ function validateConsent({ data, setError }) {
162
+ if (data?.consentLegal === true) {
163
+ return;
164
+ }
165
+
166
+ // Phantom field name — blocks submit (FormManager checks errorCount > 0) but
167
+ // skips rendering since no DOM field matches '__consent'. The visual treatment
168
+ // is the wrapper outline + inline error message below.
169
+ setError('__consent', 'Agreement to Terms required');
170
+
171
+ const $group = document.getElementById('consent-group');
172
+ if ($group) {
173
+ // Match the same border color the email/password fields show when invalid.
174
+ // The HTML baseline is 'border: 1px solid transparent' so we only swap the color.
175
+ $group.style.borderColor = 'var(--bs-form-invalid-border-color, var(--bs-danger, #dc3545))';
176
+ }
177
+
178
+ const $err = document.getElementById('consent-error');
179
+ if ($err) {
180
+ $err.textContent = `Please select "I agree" to the Terms of Service and Privacy Policy.`;
181
+ $err.classList.remove('d-none');
182
+ }
183
+ }
184
+
185
+ // Clear the consent error styling once the user starts interacting with the boxes.
186
+ // Runs on every change to either checkbox. We only restore borderColor since the
187
+ // HTML baseline keeps 'border: 1px solid transparent' to reserve the layout space.
188
+ function clearConsentError() {
189
+ const $group = document.getElementById('consent-group');
190
+ if ($group) {
191
+ $group.style.borderColor = 'transparent';
192
+ }
193
+ const $err = document.getElementById('consent-error');
194
+ if ($err) {
195
+ $err.classList.add('d-none');
196
+ }
197
+ }
198
+
199
+ // Read the consent checkboxes and stash to storage. Survives the post-signup redirect
200
+ // the same way attribution does. BEM's /user/signup route picks it up via sendUserSignupMetadata.
201
+ function captureSignupConsent(data) {
202
+ const legalLabel = document.querySelector('label[for="consent-legal"]')?.innerText?.trim() || null;
203
+ const marketingLabel = document.querySelector('label[for="consent-marketing"]')?.innerText?.trim() || null;
204
+
205
+ webManager.storage().set('consent', {
206
+ legal: {
207
+ granted: data?.consentLegal === true || data?.consentLegal === 'on',
208
+ text: legalLabel,
209
+ },
210
+ marketing: {
211
+ granted: data?.consentMarketing === true || data?.consentMarketing === 'on',
212
+ text: marketingLabel,
213
+ },
214
+ });
215
+ }
216
+
115
217
  // Initialize signin form
116
218
  function initializeSigninForm() {
117
219
  formManager = new FormManager('#auth-form', {
@@ -139,7 +241,12 @@ export default function () {
139
241
 
140
242
  formManager.on('statechange', stateChangeHandler);
141
243
  formManager.on('validation', validateEmailProvider);
244
+ formManager.on('validation', validateConsent);
142
245
  formManager.on('submit', createAuthSubmitHandler('signup', handleEmailSignup));
246
+
247
+ // Clear consent error styling when either checkbox is toggled
248
+ document.getElementById('consent-legal')?.addEventListener('change', clearConsentError);
249
+ document.getElementById('consent-marketing')?.addEventListener('change', clearConsentError);
143
250
  }
144
251
 
145
252
  // Initialize reset form
@@ -183,6 +290,14 @@ export default function () {
183
290
  const pagePath = document.documentElement.getAttribute('data-page-path');
184
291
  const isSignupPage = pagePath === '/signup';
185
292
 
293
+ // Google quirk: if a new account was auto-created during a signin attempt
294
+ // (user came back from OAuth via the redirect path on /signin, not /signup),
295
+ // reverse it — they have no consent on record.
296
+ if (isNewUser && !isSignupPage) {
297
+ await reverseAccidentalSignup(result.user);
298
+ return true;
299
+ }
300
+
186
301
  if (isNewUser || isSignupPage) {
187
302
  trackSignup(providerId, result.user);
188
303
  formManager.showSuccess('Account created successfully!');
@@ -525,6 +640,15 @@ export default function () {
525
640
 
526
641
  // Track based on whether this is a new user
527
642
  const isNewUser = result.additionalUserInfo?.isNewUser;
643
+
644
+ // Google quirk: signInWithPopup auto-creates accounts. If a brand-new visitor
645
+ // clicks "Sign in with Google" on the SIGNIN page (not signup), reverse the
646
+ // auto-creation — they have no consent on record and never asked to create one.
647
+ if (isNewUser && action === 'signin') {
648
+ await reverseAccidentalSignup(result.user);
649
+ return;
650
+ }
651
+
528
652
  if (isNewUser || action === 'signup') {
529
653
  trackSignup(providerName, result.user);
530
654
  // Show success message
@@ -1,145 +1,95 @@
1
- // Notifications section module
1
+ /**
2
+ * Notifications section — marketing email consent toggle.
3
+ *
4
+ * Reads consent.marketing.status from the user doc for the toggle's initial state.
5
+ * On toggle, POSTs to /backend-manager/marketing/email-preferences with subscribe|unsubscribe.
6
+ * The server writes consent.marketing to the user doc + syncs SendGrid + Beehiiv.
7
+ */
8
+ import authorizedFetch from '__main_assets__/js/libs/authorized-fetch.js';
2
9
  import webManager from 'web-manager';
3
10
 
4
- // Initialize notifications section
11
+ const TOGGLE_ID = 'marketing-emails';
12
+ const GRANT_DATE_ID = 'marketing-emails-grant-date';
13
+
5
14
  export function init() {
6
- setupNotificationToggles();
15
+ const $toggle = document.getElementById(TOGGLE_ID);
16
+ if (!$toggle) {
17
+ return;
18
+ }
19
+ $toggle.addEventListener('change', handleToggleChange);
7
20
  }
8
21
 
9
- // Load notifications data
10
22
  export function loadData(account) {
11
- if (!account) return;
12
-
13
- // Load notification preferences from account
14
- const preferences = account.preferences?.notifications || {};
15
-
16
- // Set toggle states
17
- setToggleState('marketing-emails', preferences.marketing !== false);
18
- setToggleState('security-emails', preferences.security !== false);
19
- setToggleState('product-emails', preferences.product === true);
20
- }
21
-
22
- // Setup notification toggles
23
- function setupNotificationToggles() {
24
- const toggles = [
25
- 'marketing-emails',
26
- 'security-emails',
27
- 'product-emails'
28
- ];
29
-
30
- toggles.forEach(toggleId => {
31
- const $toggle = document.getElementById(toggleId);
32
- if ($toggle) {
33
- $toggle.addEventListener('change', handleToggleChange);
34
- }
35
- });
36
- }
37
-
38
- // Handle toggle change
39
- async function handleToggleChange(event) {
40
- const toggleId = event.target.id;
41
- const isEnabled = event.target.checked;
42
-
43
- try {
44
- // Map toggle ID to preference key
45
- const preferenceKey = toggleId.replace('-emails', '');
46
-
47
- // Update preferences
48
- await updateNotificationPreference(preferenceKey, isEnabled);
49
-
50
- // Show feedback
51
- const actionText = isEnabled ? 'enabled' : 'disabled';
52
- showToast(`${getToggleLabel(toggleId)} ${actionText}`, 'success');
53
-
54
- } catch (error) {
55
- console.error('Failed to update notification preference:', error);
56
-
57
- // Revert toggle state
58
- event.target.checked = !isEnabled;
59
-
60
- showToast('Failed to update notification preferences. Please try again.', 'danger');
23
+ if (!account) {
24
+ return;
61
25
  }
62
- }
63
-
64
- // Update notification preference
65
- async function updateNotificationPreference(key, value) {
66
- // This would call the appropriate API endpoint
67
- // For now, just update locally
68
- const preferences = {
69
- notifications: {
70
- [key]: value
71
- }
72
- };
73
26
 
74
- // await webManager.auth().updatePreferences(preferences);
75
- console.log('Updating notification preference:', key, value);
76
- }
77
-
78
- // Set toggle state
79
- function setToggleState(toggleId, isEnabled) {
80
- const $toggle = document.getElementById(toggleId);
81
- if ($toggle) {
82
- $toggle.checked = isEnabled;
27
+ const $toggle = document.getElementById(TOGGLE_ID);
28
+ if (!$toggle) {
29
+ return;
83
30
  }
84
- }
85
31
 
86
- // Get toggle label for feedback
87
- function getToggleLabel(toggleId) {
88
- switch(toggleId) {
89
- case 'marketing-emails':
90
- return 'Marketing emails';
91
- case 'security-emails':
92
- return 'Security alerts';
93
- case 'product-emails':
94
- return 'Product updates';
95
- default:
96
- return 'Notifications';
32
+ const isGranted = account.consent?.marketing?.status === 'granted';
33
+ $toggle.checked = isGranted;
34
+
35
+ // Show the original grant date if known — gives the user context on what they agreed to.
36
+ const grantTimestamp = account.consent?.marketing?.grantedAt?.timestamp;
37
+ if (isGranted && grantTimestamp) {
38
+ const $date = document.getElementById(GRANT_DATE_ID);
39
+ if ($date) {
40
+ const date = new Date(grantTimestamp);
41
+ $date.textContent = `Subscribed ${date.toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' })}.`;
42
+ $date.classList.remove('d-none');
43
+ }
97
44
  }
98
45
  }
99
46
 
100
- // Show toast notification
101
- function showToast(message, type = 'info') {
102
- // Create toast element
103
- const $toast = document.createElement('div');
104
- $toast.className = `toast align-items-center text-bg-${type} border-0 position-fixed bottom-0 end-0 m-3`;
105
- $toast.setAttribute('role', 'alert');
106
- $toast.setAttribute('aria-live', 'assertive');
107
- $toast.setAttribute('aria-atomic', 'true');
108
- $toast.style.zIndex = '9999';
109
-
110
- const $inner = document.createElement('div');
111
- $inner.className = 'd-flex';
47
+ async function handleToggleChange(event) {
48
+ const $toggle = event.target;
49
+ const wasChecked = !$toggle.checked; // checkbox already flipped at this point
50
+ const isEnabled = $toggle.checked;
51
+ const action = isEnabled ? 'subscribe' : 'unsubscribe';
112
52
 
113
- const $body = document.createElement('div');
114
- $body.className = 'toast-body';
115
- $body.textContent = message;
53
+ // Disable while in-flight so rapid clicks don't fire multiple requests
54
+ $toggle.disabled = true;
116
55
 
117
- const $closeBtn = document.createElement('button');
118
- $closeBtn.type = 'button';
119
- $closeBtn.className = 'btn-close btn-close-white me-2 m-auto';
120
- $closeBtn.setAttribute('data-bs-dismiss', 'toast');
121
- $closeBtn.setAttribute('aria-label', 'Close');
56
+ try {
57
+ const response = await authorizedFetch(`${webManager.getApiUrl()}/backend-manager/marketing/email-preferences`, {
58
+ method: 'POST',
59
+ timeout: 15000,
60
+ response: 'json',
61
+ tries: 2,
62
+ body: { action },
63
+ });
122
64
 
123
- $inner.appendChild($body);
124
- $inner.appendChild($closeBtn);
125
- $toast.appendChild($inner);
65
+ if (response.error || response.data?.success !== true) {
66
+ throw new Error(response.message || response.error || 'Failed to update email preferences.');
67
+ }
126
68
 
127
- // Add to page
128
- document.body.appendChild($toast);
69
+ webManager.utilities().showNotification(
70
+ isEnabled ? 'Subscribed to email updates.' : 'Unsubscribed from email updates.',
71
+ { type: 'success' }
72
+ );
73
+
74
+ // Hide the grant-date line on unsubscribe (it was the OLD grant date — informational only).
75
+ // It'll get repopulated the next time loadData runs if the user re-subscribes.
76
+ if (!isEnabled) {
77
+ const $date = document.getElementById(GRANT_DATE_ID);
78
+ if ($date) {
79
+ $date.classList.add('d-none');
80
+ }
81
+ }
82
+ } catch (error) {
83
+ console.error('Failed to update marketing consent:', error);
129
84
 
130
- // Initialize and show toast
131
- if (window.bootstrap && window.bootstrap.Toast) {
132
- const toast = new window.bootstrap.Toast($toast);
133
- toast.show();
85
+ // Revert toggle on failure so UI matches server state
86
+ $toggle.checked = wasChecked;
134
87
 
135
- // Remove after hidden
136
- $toast.addEventListener('hidden.bs.toast', () => {
137
- $toast.remove();
138
- });
139
- } else {
140
- // Fallback if Bootstrap JS not available
141
- setTimeout(() => {
142
- $toast.remove();
143
- }, 3000);
88
+ webManager.utilities().showNotification(
89
+ 'Failed to update email preferences. Please try again.',
90
+ { type: 'danger' }
91
+ );
92
+ } finally {
93
+ $toggle.disabled = false;
144
94
  }
145
95
  }
@@ -36,8 +36,8 @@ sections:
36
36
  - id: "profile"
37
37
  name: "Profile"
38
38
  icon: "user-circle"
39
- # - id: "notifications"
40
- # name: "Notifications"
39
+ - id: "notifications"
40
+ name: "Notifications"
41
41
  icon: "bell"
42
42
  - id: "security"
43
43
  name: "Security"
@@ -777,29 +777,14 @@ badges:
777
777
 
778
778
  <div class="card">
779
779
  <div class="card-body">
780
- <h5 class="card-title">Email notifications</h5>
781
-
782
- <div class="form-check form-switch mb-3">
783
- <input class="form-check-input" type="checkbox" id="marketing-emails" checked>
784
- <label class="form-check-label" for="marketing-emails">
785
- Marketing emails
786
- <small class="d-block text-muted">Receive emails about new features and updates</small>
787
- </label>
788
- </div>
789
-
790
- <div class="form-check form-switch mb-3">
791
- <input class="form-check-input" type="checkbox" id="security-emails" checked>
792
- <label class="form-check-label" for="security-emails">
793
- Security alerts
794
- <small class="d-block text-muted">Get notified about important security updates</small>
795
- </label>
796
- </div>
780
+ <h5 class="card-title">Email preferences</h5>
797
781
 
798
782
  <div class="form-check form-switch">
799
- <input class="form-check-input" type="checkbox" id="product-emails">
800
- <label class="form-check-label" for="product-emails">
801
- Product updates
802
- <small class="d-block text-muted">Receive updates about your products and services</small>
783
+ <input class="form-check-input" type="checkbox" id="marketing-emails">
784
+ <label class="form-check-label" for="marketing-emails">
785
+ Product updates, newsletters, and marketing communications
786
+ <small class="d-block text-muted">You can withdraw consent at any time.</small>
787
+ <small id="marketing-emails-grant-date" class="d-block text-muted d-none"></small>
803
788
  </label>
804
789
  </div>
805
790
  </div>
@@ -11,7 +11,7 @@ layout: themes/[ site.theme.id ]/frontend/core/cover
11
11
  <!-- Logo -->
12
12
  <div class="text-center mb-3">
13
13
  <div class="avatar avatar-xl">
14
- <img src="{{ site.brand.images.brandmark }}?cb={{ site.uj.cache_breaker }}" alt="{{ site.brand.name }} Logo"/>
14
+ <img src="{{ site.brand.images.brandmark }}?cb={{ site.uj.cache_breaker }}" class="filter-adaptive" alt="{{ site.brand.name }} Logo"/>
15
15
  </div>
16
16
  </div>
17
17
 
@@ -11,13 +11,13 @@ layout: themes/[ site.theme.id ]/frontend/core/cover
11
11
  <!-- Logo -->
12
12
  <div class="text-center mb-3">
13
13
  <div class="avatar avatar-xl">
14
- <img src="{{ site.brand.images.brandmark }}?cb={{ site.uj.cache_breaker }}" alt="{{ site.brand.name }} Logo"/>
14
+ <img src="{{ site.brand.images.brandmark }}?cb={{ site.uj.cache_breaker }}" class="filter-adaptive" alt="{{ site.brand.name }} Logo"/>
15
15
  </div>
16
16
  </div>
17
17
 
18
18
  <!-- Header -->
19
19
  <div class="text-center mb-4">
20
- <h1 class="h3 mb-2">Reset Your Password</h1>
20
+ <h1 class="h3 mb-2">Reset password</h1>
21
21
  <p class="text-muted">Enter your email address and we'll send you a link to reset your password.</p>
22
22
  </div>
23
23
 
@@ -26,13 +26,13 @@ social_signin:
26
26
  <!-- Logo -->
27
27
  <div class="text-center mb-3">
28
28
  <div class="avatar avatar-xl">
29
- <img src="{{ site.brand.images.brandmark }}?cb={{ site.uj.cache_breaker }}" alt="{{ site.brand.name }} Logo"/>
29
+ <img src="{{ site.brand.images.brandmark }}?cb={{ site.uj.cache_breaker }}" class="filter-adaptive" alt="{{ site.brand.name }} Logo"/>
30
30
  </div>
31
31
  </div>
32
32
 
33
33
  <!-- Header -->
34
34
  <div class="text-center mb-4">
35
- <h1 class="h3 mb-2">Welcome Back!</h1>
35
+ <h1 class="h3 mb-2">Welcome back!</h1>
36
36
  <p class="text-muted">Sign in to your {{ site.brand.name }} account</p>
37
37
  </div>
38
38
 
@@ -26,13 +26,13 @@ social_signup:
26
26
  <!-- Logo -->
27
27
  <div class="text-center mb-3">
28
28
  <div class="avatar avatar-xl">
29
- <img src="{{ site.brand.images.brandmark }}?cb={{ site.uj.cache_breaker }}" alt="{{ site.brand.name }} Logo"/>
29
+ <img src="{{ site.brand.images.brandmark }}?cb={{ site.uj.cache_breaker }}" class="filter-adaptive" alt="{{ site.brand.name }} Logo"/>
30
30
  </div>
31
31
  </div>
32
32
 
33
33
  <!-- Header -->
34
34
  <div class="text-center mb-4">
35
- <h1 class="h3 mb-2">Create Your Account</h1>
35
+ <h1 class="h3 mb-2">Create your account</h1>
36
36
  <p class="text-muted">Get started with {{ site.brand.name }} in seconds</p>
37
37
  </div>
38
38
 
@@ -94,10 +94,24 @@ social_signup:
94
94
  <!-- <div class="form-text">Must be at least 8 characters with letters and numbers</div> -->
95
95
  </div>
96
96
 
97
- <div class="mb-3 text-center">
98
- <p class="text-muted small">
99
- By signing up, you agree to {{ site.brand.name }}'s <a href="/terms" class="text-decoration-none" target="_blank">Terms of Service</a> and <a href="/privacy" class="text-decoration-none" target="_blank">Privacy Policy</a>.
100
- </p>
97
+ {% comment %} TODO: in regions that allow pre-checked consent (e.g. US), default both to checked. {% endcomment %}
98
+ <div id="consent-group" class="mb-3 text-start p-2 rounded" style="border: 1px solid transparent;">
99
+ <div class="form-check mb-2">
100
+ <input class="form-check-input" type="checkbox" id="consent-legal" name="consentLegal">
101
+ <label class="form-check-label small" for="consent-legal">
102
+ I agree to {{ site.brand.name }}'s
103
+ <a href="/terms" class="text-decoration-none" target="_blank">Terms of Service</a>
104
+ and
105
+ <a href="/privacy" class="text-decoration-none" target="_blank">Privacy Policy</a>.
106
+ </label>
107
+ </div>
108
+ <div class="form-check">
109
+ <input class="form-check-input" type="checkbox" id="consent-marketing" name="consentMarketing">
110
+ <label class="form-check-label small" for="consent-marketing">
111
+ I agree to receive product updates, newsletters, and marketing communications from {{ site.brand.name }}. You can unsubscribe anytime.
112
+ </label>
113
+ </div>
114
+ <div id="consent-error" class="small mt-2 d-none" style="color: var(--bs-form-invalid-color, var(--bs-danger, #dc3545));"></div>
101
115
  </div>
102
116
 
103
117
  <button type="submit" class="btn btn-success _btn-lg w-100 mb-3" data-provider="email" disabled>
@@ -9,7 +9,7 @@ layout: themes/[ site.theme.id ]/frontend/core/cover
9
9
  <!-- Logo -->
10
10
  <div class="text-center mb-3">
11
11
  <div class="avatar avatar-xl">
12
- <img src="{{ site.brand.images.brandmark }}?cb={{ site.uj.cache_breaker }}" alt="{{ site.brand.name }} Logo"/>
12
+ <img src="{{ site.brand.images.brandmark }}?cb={{ site.uj.cache_breaker }}" class="filter-adaptive" alt="{{ site.brand.name }} Logo"/>
13
13
  </div>
14
14
  </div>
15
15
 
@@ -24,10 +24,10 @@ member:
24
24
  links:
25
25
  - id: "linkedin"
26
26
  title: "LinkedIn"
27
- url: "https://www.linkedin.com/in/christina-hill"
27
+ url: "https://www.linkedin.com/in/christina-hill-23487"
28
28
  - id: "twitter"
29
29
  title: "Twitter"
30
- url: "https://twitter.com/christina-hill"
30
+ url: "https://twitter.com/christina-hill-23487"
31
31
  ---
32
32
 
33
33
  Hey there, I'm Christina!
@@ -25,7 +25,7 @@ member:
25
25
  links:
26
26
  - id: "linkedin"
27
27
  title: "LinkedIn"
28
- url: "https://www.linkedin.com/in/james-oconnor"
28
+ url: "https://www.linkedin.com/in/james-oconnor-88559"
29
29
  ---
30
30
 
31
31
  Hey! I'm James.
@@ -25,13 +25,13 @@ member:
25
25
  links:
26
26
  - id: "linkedin"
27
27
  title: "LinkedIn"
28
- url: "https://www.linkedin.com/in/marcus-johnson"
28
+ url: "https://www.linkedin.com/in/marcus-johnson-381958"
29
29
  - id: "dribbble"
30
30
  title: "Dribbble"
31
- url: "https://dribbble.com/marcus-johnson"
31
+ url: "https://dribbble.com/marcus-johnson-381958"
32
32
  - id: "instagram"
33
33
  title: "Instagram"
34
- url: "https://www.instagram.com/marcus-johnson"
34
+ url: "https://www.instagram.com/marcus-johnson-381958"
35
35
  ---
36
36
 
37
37
  Hey there, I'm Marcus!
@@ -25,10 +25,10 @@ member:
25
25
  links:
26
26
  - id: "linkedin"
27
27
  title: "LinkedIn"
28
- url: "https://www.linkedin.com/in/priya-sharma"
28
+ url: "https://www.linkedin.com/in/priya-sharma-5829947"
29
29
  - id: "twitter"
30
30
  title: "Twitter"
31
- url: "https://twitter.com/priya-sharma"
31
+ url: "https://twitter.com/priya-sharma-5829947"
32
32
  ---
33
33
 
34
34
  Hi there, I'm Priya!
@@ -25,13 +25,13 @@ member:
25
25
  links:
26
26
  - id: "linkedin"
27
27
  title: "LinkedIn"
28
- url: "https://www.linkedin.com/in/sarah-rodriguez"
28
+ url: "https://www.linkedin.com/in/sarah-rodriguez-103863"
29
29
  - id: "twitter"
30
30
  title: "Twitter"
31
- url: "https://twitter.com/sarah-rodriguez"
31
+ url: "https://twitter.com/sarah-rodriguez-103863"
32
32
  - id: "instagram"
33
33
  title: "Instagram"
34
- url: "https://www.instagram.com/sarahrodriguez"
34
+ url: "https://www.instagram.com/sarah-rodriguez-103863"
35
35
  ---
36
36
 
37
37
  Hola! I'm Sarah.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultimate-jekyll-manager",
3
- "version": "1.2.3",
3
+ "version": "1.3.0",
4
4
  "description": "Ultimate Jekyll dependency manager",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -107,7 +107,7 @@
107
107
  "prettier": "^3.8.3",
108
108
  "sass": "^1.99.0",
109
109
  "spellchecker": "^3.7.1",
110
- "web-manager": "^4.1.42",
110
+ "web-manager": "^4.2.0",
111
111
  "webpack": "^5.106.2",
112
112
  "wonderful-fetch": "^2.0.5",
113
113
  "wonderful-version": "^1.3.2",