ultimate-jekyll-manager 0.0.118 → 0.0.120

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.
Files changed (51) hide show
  1. package/CLAUDE.md +409 -23
  2. package/README.md +171 -2
  3. package/TODO.md +10 -2
  4. package/_backup/form-manager.backup.js +1020 -0
  5. package/dist/assets/js/core/auth.js +5 -4
  6. package/dist/assets/js/core/cookieconsent.js +24 -17
  7. package/dist/assets/js/core/exit-popup.js +15 -12
  8. package/dist/assets/js/core/social-sharing.js +8 -4
  9. package/dist/assets/js/libs/auth/pages.js +78 -149
  10. package/dist/assets/js/libs/dev.js +192 -129
  11. package/dist/assets/js/libs/form-manager.js +643 -775
  12. package/dist/assets/js/pages/account/index.js +3 -2
  13. package/dist/assets/js/pages/account/sections/api-keys.js +37 -52
  14. package/dist/assets/js/pages/account/sections/connections.js +37 -46
  15. package/dist/assets/js/pages/account/sections/delete.js +57 -78
  16. package/dist/assets/js/pages/account/sections/profile.js +37 -56
  17. package/dist/assets/js/pages/account/sections/security.js +102 -125
  18. package/dist/assets/js/pages/admin/notifications/new/index.js +73 -151
  19. package/dist/assets/js/pages/blog/index.js +33 -53
  20. package/dist/assets/js/pages/contact/index.js +112 -173
  21. package/dist/assets/js/pages/download/index.js +39 -86
  22. package/dist/assets/js/pages/oauth2/index.js +17 -17
  23. package/dist/assets/js/pages/payment/checkout/index.js +23 -36
  24. package/dist/assets/js/pages/pricing/index.js +5 -2
  25. package/dist/assets/js/pages/test/libraries/form-manager/index.js +194 -0
  26. package/dist/assets/themes/classy/css/components/_cards.scss +2 -2
  27. package/dist/defaults/_.env +6 -0
  28. package/dist/defaults/_.gitignore +7 -1
  29. package/dist/defaults/dist/_includes/core/body.html +5 -13
  30. package/dist/defaults/dist/_includes/core/foot.html +1 -0
  31. package/dist/defaults/dist/_includes/themes/classy/frontend/sections/nav.html +51 -36
  32. package/dist/defaults/dist/_layouts/blueprint/admin/notifications/new.html +13 -2
  33. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/about.html +84 -42
  34. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/account/index.html +26 -21
  35. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/auth/signin.html +2 -2
  36. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/auth/signup.html +2 -2
  37. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/blog/index.html +72 -58
  38. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/blog/post.html +46 -29
  39. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/contact.html +46 -53
  40. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/download.html +111 -73
  41. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/index.html +111 -56
  42. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/pricing.html +127 -81
  43. package/dist/defaults/dist/pages/test/libraries/form-manager.html +181 -0
  44. package/dist/defaults/dist/pages/test/libraries/lazy-loading.html +1 -1
  45. package/dist/gulp/tasks/defaults.js +210 -1
  46. package/dist/gulp/tasks/serve.js +18 -0
  47. package/dist/lib/logger.js +1 -1
  48. package/firebase-debug.log +770 -0
  49. package/package.json +6 -6
  50. package/.playwright-mcp/page-2025-10-22T19-11-27-666Z.png +0 -0
  51. package/.playwright-mcp/page-2025-10-22T19-11-57-357Z.png +0 -0
package/CLAUDE.md CHANGED
@@ -26,19 +26,179 @@ The local development server URL is stored in `.temp/_config_browsersync.yml` in
26
26
 
27
27
  ## Asset Organization
28
28
 
29
- ### CSS Architecture
30
- - `src/assets/css/ultimate-jekyll-manager.scss` - Main UJ stylesheet
31
- - `src/assets/css/main.scss` - Site-wide styles (runs on every page)
29
+ ### Ultimate Jekyll Manager Files (THIS project)
30
+
31
+ **CSS:**
32
+ - `src/assets/css/ultimate-jekyll-manager.scss` - Main UJ stylesheet (provides core styles)
32
33
  - `src/assets/css/global/` - Global UJ styles
33
- - `src/assets/css/pages/` - Page-specific styles
34
+ - `src/assets/css/pages/` - Page-specific styles provided by UJ
34
35
  - Format: `src/assets/css/pages/[page-name]/index.scss`
36
+ - Example: `src/assets/css/pages/download/index.scss`
35
37
 
36
- ### JavaScript Architecture
37
- - `src/assets/js/ultimate-jekyll-manager.js` - Main UJ JavaScript entry point
38
- - `src/assets/js/main.js` - Site-wide JavaScript (runs on every page)
38
+ **JavaScript:**
39
+ - `src/assets/js/ultimate-jekyll-manager.js` - Main UJ JavaScript entry point (provides core functionality)
39
40
  - `src/assets/js/core/` - Core UJ modules
40
- - `src/assets/js/pages/` - Page-specific JavaScript
41
+ - `src/assets/js/pages/` - Page-specific JavaScript provided by UJ
41
42
  - Format: `src/assets/js/pages/[page-name]/index.js`
43
+ - Example: `src/assets/js/pages/download/index.js`
44
+ - `src/assets/js/libs/` - UJ library modules (prerendered-icons, form-manager, authorized-fetch, etc.)
45
+
46
+ **Default Pages & Layouts:**
47
+
48
+ UJ provides default page templates and layouts in `src/defaults/dist/` that are copied to consuming projects. These are NOT meant to be edited by users.
49
+
50
+ - Format: `src/defaults/dist/_layouts/themes/[theme-id]/frontend/pages/[page-name].html`
51
+ - Examples:
52
+ - `src/defaults/dist/_layouts/themes/classy/frontend/pages/download.html`
53
+ - `src/defaults/dist/_layouts/themes/classy/frontend/pages/pricing.html`
54
+ - `src/defaults/dist/_layouts/themes/classy/frontend/pages/payment/checkout.html`
55
+ - `src/defaults/dist/_layouts/themes/classy/frontend/pages/payment/confirmation.html`
56
+ - `src/defaults/dist/_layouts/themes/classy/frontend/pages/contact.html`
57
+ - Core layouts:
58
+ - `src/defaults/dist/_layouts/core/root.html` - Root HTML wrapper
59
+ - `src/defaults/dist/_layouts/themes/[theme-id]/frontend/core/base.html` - Theme base layout
60
+
61
+ **Complete UJ Page Example:**
62
+ - **HTML:** `src/defaults/dist/_layouts/themes/classy/frontend/pages/download.html`
63
+ - **CSS:** `src/assets/css/pages/download/index.scss`
64
+ - **JS:** `src/assets/js/pages/download/index.js`
65
+
66
+ These files serve as blueprints and reference implementations. When building custom pages in consuming projects, reference these for patterns and best practices.
67
+
68
+ **IMPORTANT:** Consuming projects CAN create files with the same paths in their own `src/` directory to override UJ defaults, but this should ONLY be done when absolutely necessary. Prefer using `src/pages/` and `src/_layouts/` for custom pages instead of overriding UJ default files.
69
+
70
+ ### Section Configuration Files (JSON)
71
+
72
+ UJ provides JSON configuration files for common sections like navigation and footer. These JSON files are consumed by corresponding HTML templates during the build process.
73
+
74
+ **Configuration Files:**
75
+ - `src/defaults/src/_includes/frontend/sections/nav.json` - Navigation configuration
76
+ - `src/defaults/src/_includes/frontend/sections/footer.json` - Footer configuration
77
+
78
+ **How It Works:**
79
+ 1. JSON files contain structured data (links, labels, settings)
80
+ 2. HTML templates in `src/defaults/dist/_includes/themes/[theme-id]/frontend/sections/` read and render this data
81
+ 3. The build process converts `.json` → data loaded by `.html` templates
82
+
83
+ **Customizing Navigation/Footer:**
84
+
85
+ Consuming projects should create their own JSON files in `src/_includes/frontend/sections/`:
86
+ - `src/_includes/frontend/sections/nav.json`
87
+ - `src/_includes/frontend/sections/footer.json`
88
+
89
+ **Example: Footer Configuration**
90
+
91
+ ```json
92
+ {
93
+ logo: {
94
+ href: '/',
95
+ class: 'filter-adaptive',
96
+ text: '{{ site.brand.name }}',
97
+ description: '{{ site.meta.description }}',
98
+ },
99
+ links: [
100
+ {
101
+ label: 'Company',
102
+ href: null,
103
+ links: [
104
+ {
105
+ label: 'About Us',
106
+ href: '/about',
107
+ },
108
+ {
109
+ label: 'Pricing',
110
+ href: '/pricing',
111
+ },
112
+ ],
113
+ },
114
+ ],
115
+ socials: {
116
+ enabled: true,
117
+ },
118
+ copyright: {
119
+ enabled: true,
120
+ text: null,
121
+ },
122
+ }
123
+ ```
124
+
125
+ **Note:** These are JSON5 files (support comments, trailing commas, unquoted keys). The corresponding HTML templates automatically process these files during the build.
126
+
127
+ ### Customizing Default Pages via Frontmatter
128
+
129
+ **BEST PRACTICE:** UJ default pages are designed to be customized through frontmatter WITHOUT writing any HTML. Consuming projects can create a simple page that includes ONLY frontmatter to configure the default page's behavior.
130
+
131
+ **How It Works:**
132
+ 1. UJ default pages use `page.resolved` to access merged frontmatter (site → layout → page)
133
+ 2. **IMPORTANT:** Before customizing, READ the UJ default page in `src/defaults/dist/_layouts/` to understand available frontmatter options and how they're used
134
+ 3. Consuming projects create a page in `src/pages/` with custom frontmatter
135
+ 4. The page uses a UJ layout (e.g., `blueprint/pricing`)
136
+ 5. Frontmatter overrides default values without any HTML
137
+
138
+ **Example: Customizing the Pricing Page**
139
+
140
+ **Step 1:** Read the UJ default pricing page to see available frontmatter options:
141
+ - File: `src/defaults/dist/_layouts/themes/classy/frontend/pages/pricing.html`
142
+ - Look for frontmatter at the top and how `page.resolved.pricing` is used in the HTML
143
+
144
+ **Step 2:** In consuming project, create `src/pages/pricing.html`:
145
+
146
+ ```yaml
147
+ ---
148
+ ### ALL PAGES ###
149
+ layout: blueprint/pricing
150
+ permalink: /pricing
151
+
152
+ ### PAGE CONFIG ###
153
+ pricing:
154
+ price_per_unit:
155
+ enabled: true
156
+ feature_id: "credits"
157
+ label: "credit"
158
+ plans:
159
+ - id: "basic"
160
+ name: "Basic"
161
+ tagline: "best for getting started"
162
+ url: "/download"
163
+ pricing:
164
+ monthly: 0
165
+ annually: 0
166
+ features:
167
+ - id: "credits"
168
+ name: "Credits"
169
+ value: 1
170
+ icon: "sparkles"
171
+ ...
172
+ ---
173
+ ```
174
+
175
+ That's it! No HTML needed. The UJ pricing layout reads `page.resolved.pricing` and renders the plans accordingly.
176
+
177
+ **When to Use Frontmatter Customization:**
178
+ - ✅ Customizing UJ default pages (pricing, contact, download, etc.)
179
+ - ✅ Changing configuration without touching HTML
180
+ - ✅ Maintaining upgradability when UJ updates
181
+
182
+ **When to Create Custom Pages:**
183
+ - ❌ Building entirely new page types
184
+ - ❌ Needing custom HTML structure
185
+ - ❌ Pages with unique layouts not provided by UJ
186
+
187
+ ### Consuming Project Files
188
+
189
+ **CSS:**
190
+ - `src/assets/css/main.scss` - Site-wide custom styles (runs on every page, edits by consuming project)
191
+ - `src/assets/css/pages/` - Page-specific custom styles
192
+ - Format: `src/assets/css/pages/[page-name]/index.scss`
193
+
194
+ **JavaScript:**
195
+ - `src/assets/js/main.js` - Site-wide custom JavaScript (runs on every page, edits by consuming project)
196
+ - `src/assets/js/pages/` - Page-specific custom JavaScript
197
+ - Format: `src/assets/js/pages/[page-name]/index.js`
198
+
199
+ **Pages & Layouts:**
200
+ - `src/pages/` - Individual page HTML/Markdown files
201
+ - `src/_layouts/` - Custom layouts for the consuming project
42
202
 
43
203
  **Asset Loading:** Page-specific CSS/JS files are automatically included based on the page's canonical path. Override with `asset_path` frontmatter.
44
204
 
@@ -170,36 +330,95 @@ asset_path: blog/post
170
330
 
171
331
  Uses `/assets/css/pages/{{ asset_path }}.bundle.css` instead of deriving from `page.canonical.path`. Useful when multiple pages share assets (e.g., all blog posts).
172
332
 
173
- ## Icon Pre-rendering System
333
+ ## Icon System
174
334
 
175
- Ultimate Jekyll includes a frontmatter-based icon pre-rendering system for optimal performance.
335
+ Ultimate Jekyll uses Font Awesome icons but does NOT include the Font Awesome JavaScript or CSS library. All icons must be rendered server-side using Jekyll's `{% uj_icon %}` tag.
176
336
 
177
- ### How It Works
337
+ ### When to Use `{% uj_icon %}` vs Prerendered Icons
178
338
 
179
- 1. **Define icons in page frontmatter:**
339
+ **IMPORTANT:** Use the correct method based on WHERE the icon will be used:
340
+
341
+ #### Use `{% uj_icon %}` in HTML/Liquid Templates
342
+
343
+ When icons are part of the static HTML template, use `{% uj_icon %}` directly:
344
+
345
+ ```liquid
346
+ <!-- Alerts -->
347
+ <div class="alert alert-success">
348
+ {% uj_icon "circle-check", "fa-sm" %} Success message
349
+ </div>
350
+
351
+ <!-- Buttons -->
352
+ <button class="btn btn-primary">
353
+ {% uj_icon "paper-plane", "fa-md me-2" %}
354
+ Send
355
+ </button>
356
+
357
+ <!-- Labels -->
358
+ <label>
359
+ {% uj_icon "envelope", "fa-sm me-1 text-info" %}
360
+ Email
361
+ </label>
362
+ ```
363
+
364
+ **Use this when:**
365
+ - The icon is in a Jekyll template (.html file)
366
+ - The icon is static and known at build time
367
+ - The icon is part of the page structure
368
+
369
+ #### Use Prerendered Icons in JavaScript
370
+
371
+ When icons need to be dynamically inserted via JavaScript, pre-render them in frontmatter and access them via the library:
372
+
373
+ **1. Add icons to page frontmatter:**
180
374
  ```yaml
181
375
  ---
182
376
  prerender_icons:
183
- - name: "apple"
184
- class: "fa-3xl"
185
- - name: "android"
186
- class: "fa-2xl"
187
- - name: "chrome"
377
+ - name: "mobile"
378
+ class: "fa-sm me-1"
379
+ - name: "envelope"
380
+ class: "fa-sm me-1"
381
+ - name: "bell"
382
+ class: "fa-sm me-1"
188
383
  ---
189
384
  ```
190
385
 
191
- 2. **Icons are rendered in HTML** via `src/defaults/dist/_includes/core/body.html`
386
+ **2. Import the library in JavaScript:**
387
+ ```javascript
388
+ import { getPrerenderedIcon } from '__main_assets__/js/libs/prerendered-icons.js';
389
+ ```
390
+
391
+ **3. Use in your code:**
392
+ ```javascript
393
+ const iconHTML = getPrerenderedIcon('mobile');
394
+ $badge.innerHTML = `${iconHTML}Push Notification`;
395
+ ```
396
+
397
+ **Use this when:**
398
+ - Icons are dynamically inserted via JavaScript
399
+ - Icons are part of dynamically generated content
400
+ - Icons are added to elements created with `document.createElement()`
401
+
402
+ ### What NOT to Do
192
403
 
193
- 3. **Access icons in JavaScript:**
404
+ **NEVER use manual icon HTML in JavaScript:**
194
405
  ```javascript
195
- const iconHTML = webManager.utilities().getPrerenderedIcon('apple');
406
+ // WRONG - Bootstrap Icons (we don't use Bootstrap Icons)
407
+ $el.innerHTML = '<i class="bi bi-check-circle"></i> Text';
408
+
409
+ // ❌ WRONG - Manual Font Awesome (we don't have FA JS/CSS)
410
+ $el.innerHTML = '<i class="fa-solid fa-check"></i> Text';
411
+
412
+ // ✅ CORRECT - Use prerendered icons
413
+ const iconHTML = getPrerenderedIcon('circle-check');
414
+ $el.innerHTML = `${iconHTML} Text`;
196
415
  ```
197
416
 
198
417
  ### Benefits
199
418
  - Icons are rendered server-side with proper Font Awesome classes
200
419
  - No client-side icon generation overhead
201
420
  - Consistent icon styling across the application
202
- - Easy to access via JavaScript utilities
421
+ - No Font Awesome JavaScript/CSS library needed
203
422
 
204
423
  ## CSS Guidelines
205
424
 
@@ -378,21 +597,188 @@ const response = await authorizedFetch(url, options);
378
597
 
379
598
  #### FormManager Library
380
599
 
381
- Form handling library with built-in state management and validation.
600
+ Lightweight form state management library with built-in validation, state machine, and event system.
382
601
 
383
602
  **Import:**
384
603
  ```javascript
385
604
  import { FormManager } from '__main_assets__/js/libs/form-manager.js';
386
605
  ```
387
606
 
388
- **Usage:**
607
+ **Basic Usage:**
389
608
  ```javascript
390
609
  const formManager = new FormManager('#my-form', options);
610
+
611
+ formManager.on('submit', async ({ data, $submitButton }) => {
612
+ const response = await fetch('/api', { body: JSON.stringify(data) });
613
+ if (!response.ok) throw new Error('Failed');
614
+ formManager.showSuccess('Form submitted!');
615
+ });
616
+ ```
617
+
618
+ **State Machine:**
619
+ ```
620
+ initializing → ready ⇄ submitting → ready (or submitted)
621
+ ```
622
+
623
+ **Configuration Options:**
624
+ ```javascript
625
+ {
626
+ autoReady: true, // Auto-transition to initialState when DOM ready
627
+ initialState: 'ready', // State after autoReady fires
628
+ allowResubmit: true, // Allow resubmission after success (false = 'submitted' state)
629
+ resetOnSuccess: false, // Clear form fields after successful submission
630
+ warnOnUnsavedChanges: false, // Warn user before leaving with unsaved changes
631
+ submittingText: 'Processing...', // Text shown on submit button during submission
632
+ submittedText: 'Processed!' // Text shown on submit button after success (when allowResubmit: false)
633
+ }
634
+ ```
635
+
636
+ **Events:**
637
+
638
+ | Event | Payload | Description |
639
+ |-------|---------|-------------|
640
+ | `submit` | `{ data, $submitButton }` | Form submission (throw error to show failure) |
641
+ | `validation` | `{ data, setError }` | Custom validation before submit |
642
+ | `change` | `{ field, name, value, data }` | Field value changed |
643
+ | `statechange` | `{ state, previousState }` | State transition |
644
+
645
+ **Validation System:**
646
+
647
+ FormManager runs validation automatically before `submit`:
648
+ 1. **HTML5 validation** - Checks `required`, `minlength`, `maxlength`, `min`, `max`, `pattern`, `type="email"`, `type="url"`
649
+ 2. **Custom validation** - Use `validation` event for business logic
650
+
651
+ ```javascript
652
+ fm.on('validation', ({ data, setError }) => {
653
+ if (data.age && parseInt(data.age) < 18) {
654
+ setError('age', 'You must be 18 or older');
655
+ }
656
+ });
657
+ ```
658
+
659
+ Errors display with Bootstrap's `is-invalid` class and `.invalid-feedback` elements.
660
+
661
+ **Autofocus:**
662
+
663
+ When the form transitions to `ready` state, FormManager automatically focuses the field with the `autofocus` attribute (if present and not disabled).
664
+
665
+ **Methods:**
666
+
667
+ | Method | Description |
668
+ |--------|-------------|
669
+ | `on(event, callback)` | Register event listener (chainable) |
670
+ | `ready()` | Transition to ready state |
671
+ | `getData()` | Get form data as nested object (supports dot notation) |
672
+ | `setData(obj)` | Set form values from nested object |
673
+ | `showSuccess(msg)` | Show success notification |
674
+ | `showError(msg)` | Show error notification |
675
+ | `reset()` | Reset form and go to ready state |
676
+ | `isDirty()` | Check if form has unsaved changes |
677
+ | `setDirty(bool)` | Set dirty state |
678
+ | `clearFieldErrors()` | Clear all field validation errors |
679
+ | `throwFieldErrors({ field: msg })` | Set and display field errors, throw error |
680
+
681
+ **Nested Field Names (Dot Notation):**
682
+
683
+ Use dot notation in field names for nested data:
684
+ ```html
685
+ <input name="user.address.city" value="NYC">
686
+ ```
687
+
688
+ Results in:
689
+ ```javascript
690
+ { user: { address: { city: 'NYC' } } }
691
+ ```
692
+
693
+ **Checkbox Handling:**
694
+ - **Single checkbox:** Returns `true`/`false`
695
+ - **Checkbox group (same name):** Returns object `{ value1: true, value2: false }`
696
+
697
+ **Multiple Submit Buttons:**
698
+
699
+ Access the clicked button via `$submitButton`:
700
+ ```html
701
+ <button type="submit" data-action="save">Save</button>
702
+ <button type="submit" data-action="draft">Save Draft</button>
703
+ ```
704
+
705
+ ```javascript
706
+ fm.on('submit', async ({ data, $submitButton }) => {
707
+ const action = $submitButton?.dataset?.action; // 'save' or 'draft'
708
+ });
391
709
  ```
392
710
 
393
711
  **Reference:** `src/assets/js/libs/form-manager.js`
712
+ **Test Page:** `src/assets/js/pages/test/libraries/form-manager/index.js`
394
713
  **Example:** `src/assets/js/pages/contact/index.js`
395
714
 
715
+ ## Analytics & Tracking
716
+
717
+ Ultimate Jekyll uses three tracking platforms: Google Analytics (gtag), Facebook Pixel (fbq), and TikTok Pixel (ttq).
718
+
719
+ ### Tracking Guidelines
720
+
721
+ **IMPORTANT Rules:**
722
+ - Track important user events with `gtag()`, `fbq()`, and `ttq()` functions
723
+ - NEVER add conditional checks for tracking functions (e.g., `if (typeof gtag !== 'undefined')`)
724
+ - Always assume tracking functions exist - they're globally available or stubbed
725
+ - Reference standard events documentation before implementing custom tracking
726
+
727
+ **Standard Events Documentation:**
728
+ - **Google Analytics GA4:** https://developers.google.com/analytics/devguides/collection/ga4/reference/events
729
+ - **Facebook Pixel:** https://www.facebook.com/business/help/402791146561655?id=1205376682832142
730
+ - **TikTok Pixel:** https://ads.tiktok.com/help/article/standard-events-parameters?redirected=2
731
+
732
+ ### Platform-Specific Requirements
733
+
734
+ #### TikTok Pixel Requirements
735
+ TikTok has strict validation requirements:
736
+
737
+ **Required Parameters:**
738
+ - `content_id` - MUST be included in all events
739
+
740
+ **Valid Content Types:**
741
+ - `"product"`
742
+ - `"product_group"`
743
+ - `"destination"`
744
+ - `"hotel"`
745
+ - `"flight"`
746
+ - `"vehicle"`
747
+
748
+ Any other content type will generate a validation error.
749
+
750
+ **Example:**
751
+ ```javascript
752
+ // ✅ CORRECT
753
+ ttq.track('ViewContent', {
754
+ content_id: 'product-123',
755
+ content_type: 'product'
756
+ });
757
+
758
+ // ❌ WRONG - Missing content_id
759
+ ttq.track('ViewContent', {
760
+ content_type: 'product'
761
+ });
762
+
763
+ // ❌ WRONG - Invalid content_type
764
+ ttq.track('ViewContent', {
765
+ content_id: 'product-123',
766
+ content_type: 'custom' // Not in approved list
767
+ });
768
+ ```
769
+
770
+ ### Tracking Implementation
771
+
772
+ **IMPORTANT:** Always track events to ALL THREE platforms in this order:
773
+ 1. Google Analytics (gtag)
774
+ 2. Facebook Pixel (fbq)
775
+ 3. TikTok Pixel (ttq)
776
+
777
+ Track events directly without existence checks. All three tracking calls should be made together for every event.
778
+
779
+ **Development Mode:**
780
+ In development mode, all tracking calls are intercepted and logged to the console for debugging. See `src/assets/js/libs/dev.js` for implementation.
781
+
396
782
  ## Audit Workflow
397
783
 
398
784
  When fixing issues identified by the audit task (`src/gulp/tasks/audit.js`):
package/README.md CHANGED
@@ -381,14 +381,183 @@ const response = await authorizedFetch(serverApiURL, {
381
381
 
382
382
  #### FormManager Library
383
383
 
384
- Custom library for form handling with built-in state management and validation.
384
+ Lightweight form state management library with built-in validation, state machine, and event system.
385
385
 
386
386
  **Import:**
387
387
  ```javascript
388
388
  import { FormManager } from '__main_assets__/js/libs/form-manager.js';
389
389
  ```
390
390
 
391
- **Documentation:** See [form-manager.js](src/assets/js/libs/form-manager.js) for full API and usage examples.
391
+ **Basic Usage:**
392
+ ```javascript
393
+ const formManager = new FormManager('#my-form', {
394
+ allowResubmit: true, // Allow form to be submitted again after success
395
+ resetOnSuccess: false, // Don't clear fields after success
396
+ warnOnUnsavedChanges: false // Don't warn on page leave
397
+ });
398
+
399
+ // Handle form submission
400
+ formManager.on('submit', async ({ data, $submitButton }) => {
401
+ const response = await fetch('/api/submit', {
402
+ method: 'POST',
403
+ body: JSON.stringify(data)
404
+ });
405
+
406
+ if (!response.ok) {
407
+ throw new Error('Submission failed');
408
+ }
409
+
410
+ formManager.showSuccess('Form submitted successfully!');
411
+ });
412
+ ```
413
+
414
+ **State Machine:**
415
+ ```
416
+ initializing → ready ⇄ submitting → ready (or submitted)
417
+ ```
418
+
419
+ **Events:**
420
+
421
+ | Event | Payload | Description |
422
+ |-------|---------|-------------|
423
+ | `submit` | `{ data, $submitButton }` | Form submission. Throw an error to show failure message. |
424
+ | `validation` | `{ data, setError }` | Custom validation before submit. Use `setError(fieldName, message)` to add errors. |
425
+ | `change` | `{ field, name, value, data }` | Called when any field value changes. |
426
+ | `statechange` | `{ state, previousState }` | Called when form state changes. |
427
+
428
+ **Validation:**
429
+
430
+ FormManager runs validation automatically before the `submit` event:
431
+
432
+ 1. **HTML5 Validation** - Automatically checks `required`, `minlength`, `maxlength`, `min`, `max`, `pattern`, `type="email"`, `type="url"`
433
+ 2. **Custom Validation** - Use the `validation` event for business logic
434
+
435
+ ```javascript
436
+ formManager.on('validation', ({ data, setError }) => {
437
+ // Custom validation runs AFTER HTML5 validation
438
+ if (data.age && parseInt(data.age) < 18) {
439
+ setError('age', 'You must be 18 or older');
440
+ }
441
+
442
+ if (data.password !== data.confirmPassword) {
443
+ setError('confirmPassword', 'Passwords do not match');
444
+ }
445
+ });
446
+ ```
447
+
448
+ Validation errors are displayed using Bootstrap's `is-invalid` class and `.invalid-feedback` elements. The first field with an error is automatically focused.
449
+
450
+ **Autofocus:**
451
+
452
+ When the form transitions to `ready` state, FormManager automatically focuses any field with the `autofocus` attribute:
453
+
454
+ ```html
455
+ <input type="text" name="email" autofocus>
456
+ ```
457
+
458
+ **Methods:**
459
+
460
+ | Method | Description |
461
+ |--------|-------------|
462
+ | `on(event, callback)` | Register event listener (chainable) |
463
+ | `ready()` | Manually transition to ready state (for `autoReady: false`) |
464
+ | `getData()` | Get form data as nested object |
465
+ | `setData(obj)` | Populate form from a nested object |
466
+ | `showSuccess(msg)` | Show success notification |
467
+ | `showError(msg)` | Show error notification |
468
+ | `reset()` | Reset form and state |
469
+ | `isDirty()` | Check if form has unsaved changes |
470
+ | `clearFieldErrors()` | Clear all validation error displays |
471
+ | `throwFieldErrors({ field: msg })` | Set errors and throw (for use in submit handler) |
472
+
473
+ **Nested Field Names (Dot Notation):**
474
+
475
+ Use dot notation in field `name` attributes for nested data structures:
476
+
477
+ ```html
478
+ <input name="user.name" value="John">
479
+ <input name="user.address.city" value="NYC">
480
+ <input name="user.address.zip" value="10001">
481
+ ```
482
+
483
+ Produces:
484
+ ```javascript
485
+ {
486
+ user: {
487
+ name: 'John',
488
+ address: {
489
+ city: 'NYC',
490
+ zip: '10001'
491
+ }
492
+ }
493
+ }
494
+ ```
495
+
496
+ **Checkbox Handling:**
497
+
498
+ - **Single checkbox:** Returns `true` or `false`
499
+ - **Checkbox group (multiple with same name):** Returns object with each value as key
500
+
501
+ ```html
502
+ <!-- Single checkbox -->
503
+ <input type="checkbox" name="subscribe" checked>
504
+ <!-- Result: { subscribe: true } -->
505
+
506
+ <!-- Checkbox group -->
507
+ <input type="checkbox" name="features" value="darkmode" checked>
508
+ <input type="checkbox" name="features" value="analytics">
509
+ <input type="checkbox" name="features" value="beta" checked>
510
+ <!-- Result: { features: { darkmode: true, analytics: false, beta: true } } -->
511
+ ```
512
+
513
+ **Multiple Submit Buttons:**
514
+
515
+ Access the clicked submit button to handle different actions:
516
+
517
+ ```html
518
+ <button type="submit" data-action="save">Save</button>
519
+ <button type="submit" data-action="draft">Save as Draft</button>
520
+ ```
521
+
522
+ ```javascript
523
+ formManager.on('submit', async ({ data, $submitButton }) => {
524
+ const action = $submitButton?.dataset?.action;
525
+
526
+ if (action === 'draft') {
527
+ await saveDraft(data);
528
+ formManager.showSuccess('Draft saved!');
529
+ } else {
530
+ await saveAndPublish(data);
531
+ formManager.showSuccess('Published!');
532
+ }
533
+ });
534
+ ```
535
+
536
+ **Manual Ready Mode:**
537
+
538
+ For forms that need async initialization (e.g., loading data from API):
539
+
540
+ ```javascript
541
+ const formManager = new FormManager('#my-form', { autoReady: false });
542
+
543
+ // Load data, then mark ready
544
+ const userData = await fetchUserData();
545
+ formManager.setData(userData);
546
+ formManager.ready(); // Now form is interactive
547
+ ```
548
+
549
+ **Configuration Options:**
550
+
551
+ | Option | Default | Description |
552
+ |--------|---------|-------------|
553
+ | `autoReady` | `true` | Auto-transition to ready when DOM is ready |
554
+ | `initialState` | `'ready'` | State after autoReady fires |
555
+ | `allowResubmit` | `true` | Allow form resubmission after success |
556
+ | `resetOnSuccess` | `false` | Clear fields after successful submission |
557
+ | `warnOnUnsavedChanges` | `false` | Show browser warning when leaving with unsaved changes |
558
+ | `submittingText` | `'Processing...'` | Text shown on submit button during submission |
559
+
560
+ **Test Page:** Navigate to `/test/libraries/form-manager` to see FormManager in action with various configurations.
392
561
 
393
562
  ### Icons
394
563
  * Fontawesome