@o2vend/theme-cli 1.0.36 → 1.0.38

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 (90) hide show
  1. package/README.md +4 -0
  2. package/lib/lib/dev-server.js +344 -48
  3. package/lib/lib/liquid-engine.js +3 -1
  4. package/lib/lib/mock-data.js +473 -119
  5. package/lib/lib/widget-service.js +12 -4
  6. package/package.json +2 -2
  7. package/test-theme/assets/async-sections.js +32 -24
  8. package/test-theme/assets/cart-drawer.js +20 -22
  9. package/test-theme/assets/cart-manager.js +1 -15
  10. package/test-theme/assets/checkout-price-handler.js +12 -11
  11. package/test-theme/assets/checkout.css +1415 -0
  12. package/test-theme/assets/checkout.js +3174 -0
  13. package/test-theme/assets/components.css +178 -29
  14. package/test-theme/assets/delivery-zone.js +1 -1
  15. package/test-theme/assets/product-detail.css +1050 -0
  16. package/test-theme/assets/product-detail.js +2940 -0
  17. package/test-theme/assets/theme.css +95 -120
  18. package/test-theme/assets/theme.js +781 -186
  19. package/test-theme/layout/theme.liquid +91 -17
  20. package/test-theme/sections/content.liquid +64 -57
  21. package/test-theme/sections/footer-fallback.liquid +57 -7
  22. package/test-theme/sections/footer.liquid +63 -12
  23. package/test-theme/sections/header-fallback.liquid +41 -41
  24. package/test-theme/sections/header.liquid +41 -51
  25. package/test-theme/sections/hero-fallback.liquid +1 -1
  26. package/test-theme/sections/hero.liquid +159 -136
  27. package/test-theme/snippets/account-sidebar.liquid +121 -29
  28. package/test-theme/snippets/add-to-cart-modal.liquid +258 -206
  29. package/test-theme/snippets/breadcrumbs.liquid +98 -11
  30. package/test-theme/snippets/cart-drawer.liquid +93 -0
  31. package/test-theme/snippets/delivery-zone-city-selector.liquid +101 -15
  32. package/test-theme/snippets/delivery-zone-modal.liquid +529 -84
  33. package/test-theme/snippets/delivery-zone-search.liquid +104 -18
  34. package/test-theme/snippets/login-modal.liquid +269 -82
  35. package/test-theme/snippets/mega-menu.liquid +130 -43
  36. package/test-theme/snippets/news-thumbnail.liquid +120 -28
  37. package/test-theme/snippets/pagination.liquid +1 -1
  38. package/test-theme/snippets/price.liquid +100 -9
  39. package/test-theme/snippets/product-card-related.liquid +22 -4
  40. package/test-theme/snippets/product-card-simple.liquid +521 -25
  41. package/test-theme/snippets/product-card.liquid +145 -232
  42. package/test-theme/snippets/rating.liquid +100 -9
  43. package/test-theme/snippets/skeleton-collection-grid.liquid +94 -8
  44. package/test-theme/snippets/skeleton-product-card.liquid +102 -16
  45. package/test-theme/snippets/skeleton-product-grid.liquid +87 -1
  46. package/test-theme/snippets/social-sharing.liquid +133 -32
  47. package/test-theme/templates/account/dashboard.liquid +30 -0
  48. package/test-theme/templates/account/loyalty-redemption.liquid +29 -28
  49. package/test-theme/templates/account/loyalty.liquid +45 -43
  50. package/test-theme/templates/account/order-detail.liquid +15 -8
  51. package/test-theme/templates/account/orders.liquid +189 -35
  52. package/test-theme/templates/account/profile.liquid +509 -114
  53. package/test-theme/templates/account/register.liquid +18 -8
  54. package/test-theme/templates/account/return-orders.liquid +31 -30
  55. package/test-theme/templates/account/store-credit.liquid +27 -26
  56. package/test-theme/templates/account/subscriptions.liquid +22 -5
  57. package/test-theme/templates/account/wishlist.liquid +88 -19
  58. package/test-theme/templates/address-book.liquid +166 -69
  59. package/test-theme/templates/categories.liquid +90 -30
  60. package/test-theme/templates/checkout.liquid +137 -3834
  61. package/test-theme/templates/error.liquid +23 -21
  62. package/test-theme/templates/index.liquid +29 -0
  63. package/test-theme/templates/login.liquid +33 -6
  64. package/test-theme/templates/order-confirmation.liquid +67 -9
  65. package/test-theme/templates/page.liquid +418 -206
  66. package/test-theme/templates/product-detail.liquid +124 -3878
  67. package/test-theme/templates/products.liquid +155 -30
  68. package/test-theme/templates/search.liquid +739 -225
  69. package/test-theme/widgets/brand-carousel.liquid +102 -82
  70. package/test-theme/widgets/brand.liquid +78 -50
  71. package/test-theme/widgets/carousel.liquid +253 -121
  72. package/test-theme/widgets/category-list-carousel.liquid +32 -8
  73. package/test-theme/widgets/category-list.liquid +21 -6
  74. package/test-theme/widgets/category.liquid +104 -37
  75. package/test-theme/widgets/discount-time.liquid +326 -119
  76. package/test-theme/widgets/footer-menu.liquid +115 -23
  77. package/test-theme/widgets/footer.liquid +118 -5
  78. package/test-theme/widgets/gallery.liquid +29 -5
  79. package/test-theme/widgets/header-menu.liquid +25 -13
  80. package/test-theme/widgets/header.liquid +64 -26
  81. package/test-theme/widgets/html.liquid +29 -6
  82. package/test-theme/widgets/news.liquid +6 -0
  83. package/test-theme/widgets/product-canvas.liquid +20 -12
  84. package/test-theme/widgets/product-carousel.liquid +118 -56
  85. package/test-theme/widgets/shared/product-grid.liquid +12 -0
  86. package/test-theme/widgets/single-product.liquid +688 -250
  87. package/test-theme/widgets/spacebar-carousel.liquid +39 -10
  88. package/test-theme/widgets/spacebar.liquid +77 -6
  89. package/test-theme/widgets/splash.liquid +40 -30
  90. package/test-theme/widgets/testimonial-carousel.liquid +111 -67
@@ -69,8 +69,8 @@
69
69
  </div>
70
70
 
71
71
  <div class="address-card-actions">
72
- <button class="btn btn-outline btn-sm edit-address" data-address-id="{{ address.id }}">Edit</button>
73
- <button class="btn btn-outline-danger btn-sm delete-address" data-address-id="{{ address.id }}">
72
+ <button type="button" class="btn btn-outline btn-sm edit-address" data-address-id="{{ address.id }}">Edit</button>
73
+ <button type="button" class="btn btn-outline-danger btn-sm delete-address" data-address-id="{{ address.id }}">
74
74
  <span class="btn-text">Delete</span>
75
75
  <span class="btn-loading" style="display: none;">
76
76
  <span class="btn-spinner"></span>
@@ -97,31 +97,41 @@
97
97
  </section>
98
98
 
99
99
  <style>
100
+ /* Use theme-wide variables from assets/theme.css as the source of truth.
101
+ Map any template-specific variable names to the global theme variables
102
+ so we avoid hardcoded values here. */
100
103
  :root {
101
- --color-primary: #2563eb;
102
- --color-primary-hover: #1d4ed8;
103
- --color-primary-light: #dbeafe;
104
- --color-success: #10b981;
105
- --color-success-light: #d1fae5;
106
- --color-danger: #ef4444;
107
- --color-danger-hover: #dc2626;
108
- --color-danger-light: #fee2e2;
109
- --color-text: #1f2937;
110
- --color-text-light: #6b7280;
111
- --color-background: #f9fafb;
112
- --color-card-bg: #ffffff;
113
- --color-border: #e5e7eb;
114
- --color-border-light: #f3f4f6;
115
- --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
116
- --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
117
- --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
118
- --radius-md: 0.5rem;
119
- --radius-lg: 0.75rem;
120
- --spacing-xs: 0.5rem;
121
- --spacing-sm: 1rem;
122
- --spacing-md: 1.5rem;
123
- --spacing-lg: 2rem;
124
- --spacing-xl: 3rem;
104
+ --color-primary: {{ settings.color_primary | default: '#000000' }};
105
+ --color-primary-hover: {{ settings.color_primary_dark | default: '#333333' }};
106
+ --color-primary-light: {{ settings.color_primary_light | default: '#666666' }};
107
+
108
+ --color-success: {{ settings.color_success | default: '#22c55e' }};
109
+ --color-success-light: {{ settings.color_success | default: '#22c55e' }};
110
+
111
+ --color-danger: {{ settings.color_error | default: '#ef4444' }};
112
+ --color-danger-hover: {{ settings.color_error | default: '#ef4444' }};
113
+ --color-danger-light: {{ settings.color_error | default: '#ef4444' }};
114
+
115
+ --color-text: {{ settings.color_text | default: '#000000' }};
116
+ --color-text-light: {{ settings.color_text_muted | default: '#666666' }};
117
+
118
+ --color-background: {{ settings.color_background | default: '#ffffff' }};
119
+ --color-card-bg: {{ settings.color_surface | default: '#f5f5f5' }};
120
+ --color-border: {{ settings.color_border | default: '#cccccc' }};
121
+ --color-border-light: {{ settings.color_surface | default: '#f5f5f5' }};
122
+
123
+ --shadow-sm: var(--shadow-sm);
124
+ --shadow-md: var(--shadow-md);
125
+ --shadow-lg: var(--shadow-lg);
126
+
127
+ --radius-md: var(--radius-md);
128
+ --radius-lg: var(--radius-lg);
129
+
130
+ --spacing-xs: var(--space-3);
131
+ --spacing-sm: var(--space-4);
132
+ --spacing-md: var(--space-6);
133
+ --spacing-lg: var(--space-8);
134
+ --spacing-xl: var(--space-12);
125
135
  }
126
136
  /* Page */
127
137
  .account-page {
@@ -159,10 +169,10 @@
159
169
 
160
170
  .account-sidebar,
161
171
  .account-content {
162
- background: #fff;
172
+ background: var(--color-white);
163
173
  border-radius: 14px;
164
174
  padding: 1.75rem;
165
- border: 1px solid #e5e7eb;
175
+ border: 1px solid var(--color-border);
166
176
  }
167
177
 
168
178
  /* Section header */
@@ -227,10 +237,13 @@
227
237
  .default-badge {
228
238
  background: #dcfce7;
229
239
  color: #166534;
230
- font-size: .7rem;
240
+ font-size: 1rem;
231
241
  padding: .3rem .6rem;
232
- border-radius: 999px;
242
+ border-radius: 10px;
233
243
  font-weight: 600;
244
+ display: flex;
245
+ align-items: center;
246
+ height: fit-content;
234
247
  }
235
248
 
236
249
  /* Body */
@@ -344,7 +357,7 @@
344
357
  width: 90%;
345
358
  max-height: 90vh;
346
359
  overflow-y: auto;
347
- box-shadow: 0 20px 25px rgba(0, 0, 0, 0.15);
360
+
348
361
  }
349
362
 
350
363
  .address-modal-header {
@@ -412,12 +425,16 @@
412
425
  }
413
426
 
414
427
  .iti__flag-container {
415
- z-index: 1;
428
+ z-index: 2;
429
+ position: relative;
430
+ pointer-events: auto;
416
431
  }
417
432
 
418
433
  .iti__selected-flag {
419
434
  padding: 0 0.75rem 0 0.5rem;
420
435
  border-right: 1px solid #d1d5db;
436
+ cursor: pointer;
437
+ pointer-events: auto;
421
438
  }
422
439
 
423
440
  .iti__selected-flag:hover {
@@ -465,6 +482,7 @@
465
482
  width: 18px;
466
483
  height: 18px;
467
484
  cursor: pointer;
485
+ -webkit-appearance: auto !important;
468
486
  }
469
487
 
470
488
  .form-actions {
@@ -486,7 +504,6 @@
486
504
  align-items: center;
487
505
  gap: 0.5rem;
488
506
  }
489
-
490
507
  .btn-spinner {
491
508
  width: 14px;
492
509
  height: 14px;
@@ -526,7 +543,7 @@
526
543
  <div class="address-modal-overlay" data-address-modal-close></div>
527
544
  <div class="address-modal-content">
528
545
  <div class="address-modal-header">
529
- <h3 id="address-modal-title">Add New Address</h3>
546
+ <h5 id="address-modal-title">Add New Address</h5>
530
547
  <button class="address-modal-close" data-address-modal-close aria-label="Close">×</button>
531
548
  </div>
532
549
  <form class="address-form" id="address-form" action="/webstoreapi/addresses" method="post">
@@ -591,7 +608,7 @@
591
608
 
592
609
  <div class="form-group">
593
610
  <label for="address-phone" class="form-label">Phone</label>
594
- <input type="tel" id="address-phone" name="phone" class="form-input">
611
+ <input type="tel" id="address-phone" pattern="[\d\s()+-]{7,20}" name="phone" class="form-input">
595
612
  </div>
596
613
 
597
614
  <div class="form-group">
@@ -643,7 +660,7 @@
643
660
  }
644
661
  }
645
662
 
646
- document.addEventListener('DOMContentLoaded', function() {
663
+ document.addEventListener('DOMContentLoaded', () => {
647
664
  const modal = document.getElementById('address-modal');
648
665
  const openButtons = document.querySelectorAll('#add-address-btn, #add-first-address-btn, .edit-address');
649
666
  const closeButtons = document.querySelectorAll('[data-address-modal-close]');
@@ -651,6 +668,36 @@
651
668
  const addresses = {% if addresses %}{{ addresses | json }}{% else %}[]{% endif %};
652
669
  const COUNTRIES_DATA = {% if countries %}{{ countries | json }}{% else %}[]{% endif %};
653
670
  let addressPhoneIti = null;
671
+ let intlTelInputLoadPromise = null;
672
+
673
+ function ensureIntlTelInputLoaded() {
674
+ if (typeof window.intlTelInput !== 'undefined') {
675
+ return Promise.resolve(true);
676
+ }
677
+
678
+ if (intlTelInputLoadPromise) {
679
+ return intlTelInputLoadPromise;
680
+ }
681
+
682
+ intlTelInputLoadPromise = new Promise((resolve) => {
683
+ const existingScript = document.querySelector('script[data-intl-tel-input-loader="address-book"]');
684
+ if (existingScript) {
685
+ existingScript.addEventListener('load', () => resolve(typeof window.intlTelInput !== 'undefined'), { once: true });
686
+ existingScript.addEventListener('error', () => resolve(false), { once: true });
687
+ return;
688
+ }
689
+
690
+ const script = document.createElement('script');
691
+ script.src = 'https://cdn.jsdelivr.net/npm/intl-tel-input@23.0.0/build/js/intlTelInput.min.js';
692
+ script.defer = true;
693
+ script.setAttribute('data-intl-tel-input-loader', 'address-book');
694
+ script.onload = () => resolve(typeof window.intlTelInput !== 'undefined');
695
+ script.onerror = () => resolve(false);
696
+ document.head.appendChild(script);
697
+ });
698
+
699
+ return intlTelInputLoadPromise;
700
+ }
654
701
 
655
702
  // Helper function to get states for a country
656
703
  function getStatesForCountry(countryIdentifier) {
@@ -750,8 +797,24 @@
750
797
  stateSelect.style.display = 'block';
751
798
  stateSelect.required = true;
752
799
 
800
+ // De-duplicate states to avoid repeated options when upstream data contains duplicates.
801
+ const seenStateKeys = new Set();
802
+ const uniqueStates = [];
803
+ countryStates.forEach((state) => {
804
+ const stateCode = state && (state.code || state.abbreviation || state.isoCode || '');
805
+ const stateName = state && (state.name || state.label || '');
806
+ const normalizedCode = String(stateCode).trim().toLowerCase();
807
+ const normalizedName = String(stateName).trim().toLowerCase().replace(/\s+/g, ' ');
808
+ const key = normalizedCode ? `code:${normalizedCode}` : `name:${normalizedName}`;
809
+
810
+ if (!seenStateKeys.has(key)) {
811
+ seenStateKeys.add(key);
812
+ uniqueStates.push(state);
813
+ }
814
+ });
815
+
753
816
  // Populate the dropdown
754
- countryStates.forEach(state => {
817
+ uniqueStates.forEach(state => {
755
818
  const option = document.createElement('option');
756
819
  const stateId = state.id || state.stateOrProvinceId || state.stateId;
757
820
  const stateName = state.name || state.label || '';
@@ -790,7 +853,15 @@
790
853
  // Initialize intl-tel-input for address phone
791
854
  function initializeAddressPhoneInput() {
792
855
  const phoneInput = document.getElementById('address-phone');
793
- if (phoneInput && typeof intlTelInput !== 'undefined') {
856
+ if (!phoneInput) {
857
+ return;
858
+ }
859
+
860
+ ensureIntlTelInputLoaded().then((isLoaded) => {
861
+ if (!isLoaded || typeof window.intlTelInput === 'undefined') {
862
+ return;
863
+ }
864
+
794
865
  // Destroy existing instance if any
795
866
  if (addressPhoneIti) {
796
867
  addressPhoneIti.destroy();
@@ -799,30 +870,38 @@
799
870
 
800
871
  // Get country code from country select
801
872
  const countrySelect = document.getElementById('address-country');
802
- let initialCountry = 'auto';
873
+ let initialCountry = 'us';
803
874
 
804
875
  if (countrySelect && countrySelect.value) {
805
- const countryCode = countrySelect.options[countrySelect.selectedIndex]?.getAttribute('data-country-code2') ||
806
- countrySelect.value.toLowerCase();
807
- initialCountry = countryCode.toLowerCase();
876
+ const selectedCountryCode = (countrySelect.options[countrySelect.selectedIndex]?.getAttribute('data-country-code2') || '').trim().toLowerCase();
877
+ if (/^[a-z]{2}$/.test(selectedCountryCode)) {
878
+ initialCountry = selectedCountryCode;
879
+ }
808
880
  }
809
881
 
810
882
  // Initialize intl-tel-input
811
- addressPhoneIti = intlTelInput(phoneInput, {
812
- utilsScript: 'https://cdn.jsdelivr.net/npm/intl-tel-input@23.0.0/build/js/utils.js',
813
- initialCountry: initialCountry,
814
- preferredCountries: ['us', 'gb', 'ca', 'au', 'in'],
815
- separateDialCode: true,
816
- nationalMode: false
817
- });
883
+ try {
884
+ addressPhoneIti = window.intlTelInput(phoneInput, {
885
+ utilsScript: 'https://cdn.jsdelivr.net/npm/intl-tel-input@23.0.0/build/js/utils.js',
886
+ initialCountry: initialCountry,
887
+ preferredCountries: ['us', 'gb', 'ca', 'au', 'in'],
888
+ separateDialCode: true,
889
+ nationalMode: false,
890
+ dropdownContainer: document.body
891
+ });
892
+ } catch (error) {
893
+ console.error('[ADDRESS BOOK] Failed to initialize phone country selector', error);
894
+ return;
895
+ }
896
+ window.addressPhoneIti = addressPhoneIti;
818
897
 
819
898
  // Update country when address country changes
820
- if (countrySelect) {
821
- countrySelect.addEventListener('change', function() {
822
- const countryCode = this.options[this.selectedIndex]?.getAttribute('data-country-code2') ||
823
- this.value.toLowerCase();
824
- if (addressPhoneIti && countryCode) {
825
- addressPhoneIti.setCountry(countryCode.toLowerCase());
899
+ if (countrySelect && !countrySelect.dataset.phoneCountrySyncBound) {
900
+ countrySelect.dataset.phoneCountrySyncBound = '1';
901
+ countrySelect.addEventListener('change', () => {
902
+ const countryCode = (countrySelect.options[countrySelect.selectedIndex]?.getAttribute('data-country-code2') || '').trim().toLowerCase();
903
+ if (addressPhoneIti && /^[a-z]{2}$/.test(countryCode)) {
904
+ addressPhoneIti.setCountry(countryCode);
826
905
  }
827
906
 
828
907
  // Populate states when country changes
@@ -832,7 +911,7 @@
832
911
  }
833
912
  });
834
913
  }
835
- }
914
+ });
836
915
  }
837
916
 
838
917
  function openModal(addressId = null) {
@@ -902,11 +981,11 @@
902
981
  document.body.style.overflow = 'hidden';
903
982
 
904
983
  // Initialize phone input after modal is shown
905
- setTimeout(function() {
984
+ setTimeout(() => {
906
985
  initializeAddressPhoneInput();
907
986
  // Set phone number if editing (after initialization completes)
908
987
  if (addressId) {
909
- setTimeout(function() {
988
+ setTimeout(() => {
910
989
  const address = addresses.find(a => a.id == addressId);
911
990
  if (address && address.phone && addressPhoneIti) {
912
991
  addressPhoneIti.setNumber(address.phone);
@@ -925,11 +1004,13 @@
925
1004
  addressPhoneIti.destroy();
926
1005
  addressPhoneIti = null;
927
1006
  }
1007
+ window.addressPhoneIti = null;
928
1008
  }
929
1009
 
930
1010
  openButtons.forEach(btn => {
931
- btn.addEventListener('click', function() {
932
- const addressId = this.dataset.addressId || null;
1011
+ btn.addEventListener('click', (event) => {
1012
+ const targetButton = event.currentTarget || btn;
1013
+ const addressId = targetButton.dataset.addressId || null;
933
1014
  openModal(addressId);
934
1015
  });
935
1016
  });
@@ -939,7 +1020,7 @@
939
1020
  });
940
1021
 
941
1022
  // Handle form submission
942
- form.addEventListener('submit', async function(e) {
1023
+ form.addEventListener('submit', async (e) => {
943
1024
  e.preventDefault();
944
1025
 
945
1026
  const submitBtn = document.getElementById('save-address-btn');
@@ -1060,33 +1141,49 @@
1060
1141
 
1061
1142
  // Handle delete
1062
1143
  document.querySelectorAll('.delete-address').forEach(btn => {
1063
- btn.addEventListener('click', async function() {
1144
+ btn.addEventListener('click', async (event) => {
1145
+ event.preventDefault();
1064
1146
  if (confirm('Are you sure you want to delete this address?')) {
1065
- const addressId = this.dataset.addressId;
1066
- setButtonLoading(this, true, 'Deleting...');
1147
+ const targetButton = event.currentTarget || btn;
1148
+ const addressId = targetButton.dataset.addressId;
1149
+ if (!addressId) {
1150
+ alert('Invalid address. Please refresh and try again.');
1151
+ return;
1152
+ }
1153
+ setButtonLoading(targetButton, true, 'Deleting...');
1067
1154
 
1068
1155
  try {
1156
+
1157
+
1069
1158
  const response = await fetch(`/webstoreapi/addresses/${addressId}`, {
1159
+
1070
1160
  method: 'DELETE',
1071
1161
  headers: {
1162
+
1072
1163
  'X-Requested-With': 'XMLHttpRequest'
1073
1164
  }
1074
1165
  });
1166
+
1075
1167
 
1076
1168
  if (response.ok) {
1077
1169
  window.location.reload();
1078
1170
  } else {
1079
- const error = await response.json();
1080
- setButtonLoading(this, false);
1171
+ const error = await response.json();
1172
+
1173
+ setButtonLoading(targetButton, false, 'Delete');
1081
1174
  alert('Error deleting address: ' + (error.error || error.message || 'Unknown error'));
1082
1175
  }
1083
1176
  } catch (error) {
1084
1177
  console.error('Error deleting address:', error);
1085
- setButtonLoading(this, false);
1086
- alert('Error deleting address. Please try again.');
1178
+ setButtonLoading(targetButton, false, 'Delete');
1179
+ if (error.name === 'AbortError') {
1180
+ alert('Delete request timed out. Please try again.');
1181
+ } else {
1182
+ alert('Error deleting address. Please try again.');
1183
+ }
1087
1184
  }
1088
1185
  }
1089
1186
  });
1090
1187
  });
1091
1188
  });
1092
- </script>
1189
+ </script>
@@ -1,20 +1,37 @@
1
1
  {% layout 'layout/theme' %}
2
2
 
3
+ <!-- Hero Widgets (if any) -->
4
+ {% assign hero_widgets = widgets.hero %}
5
+ {% if hero_widgets and hero_widgets.size > 0 %}
6
+ <section class="theme-section theme-section--hero" data-section="hero">
7
+ {% for widget in hero_widgets %}
8
+ <div class="theme-widget-wrapper"
9
+ data-widget-id="{{ widget.id }}"
10
+ data-widget-type="{{ widget.type }}"
11
+ data-widget-position="{{ widget.Position | default: widget.position | default: forloop.index }}">
12
+ {% if widget and widget.template_path %}
13
+ {% render widget.template_path, widget: widget, settings: settings, shop: shop, is_hero_first: forloop.first %}
14
+ {% endif %}
15
+ </div>
16
+ {% endfor %}
17
+ </section>
18
+ {% endif %}
19
+
3
20
  <div class="collections-page">
4
21
  <div class="collections-header">
5
- <h1 class="page-title">Our Collections</h1>
6
- <p class="page-description">Discover our curated collections of amazing products</p>
22
+ <h1 class="page-title">Our Categories</h1>
23
+ <p class="page-description">Discover our curated categories of amazing products</p>
7
24
  </div>
8
25
  <div class="collections-content">
9
- {% if collections.size > 0 %}
26
+ {% if categories.size > 0 %}
10
27
  <div class="collections-grid">
11
- {% for collection in collections %}
28
+ {% for category in categories %}
12
29
  <div class="collection-card">
13
- <a href="/{{ collection.slug }}" class="collection-link">
30
+ <a href="/{{ category.slug }}" class="collection-link">
14
31
  <div class="collection-image">
15
- {% if collection.image %}
16
- <img src="{{ collection.image }}"
17
- alt="{{ collection.title | default: collection.name }}"
32
+ {% if category.image %}
33
+ <img src="{{ category.image }}"
34
+ alt="{{ category.title | default: category.name }}"
18
35
  width="100%"
19
36
  height="200"
20
37
  loading="lazy">
@@ -35,9 +52,9 @@
35
52
  {% endif %}
36
53
  </div>
37
54
  <div class="collection-info">
38
- <h3 class="collection-title">{{ collection.title | default: collection.name }}</h3>
39
- {% if collection.description %}
40
- <p class="collection-description">{{ collection.description }}</p>
55
+ <h3 class="collection-title">{{ category.title | default: category.name }}</h3>
56
+ {% if category.description %}
57
+ <p class="collection-description">{{ category.description }}</p>
41
58
  {% else %}
42
59
  <p class="collection-description">Setup your products and images in Catalog - Category</p>
43
60
  {% endif %}
@@ -47,12 +64,12 @@
47
64
  {% endfor %}
48
65
  </div>
49
66
  {% else %}
50
- <!-- No Collections -->
67
+ <!-- No Categories -->
51
68
  <div class="no-collections">
52
69
  <div class="no-collections-content">
53
70
  <div class="no-collections-icon">📂</div>
54
- <h2>No Collections Available</h2>
55
- <p>We're working on creating amazing collections for you. Check back soon!</p>
71
+ <h2>No Categories Available</h2>
72
+ <p>We're working on creating amazing categories for you. Check back soon!</p>
56
73
  <a href="/products" class="btn btn-primary">Browse All Products</a>
57
74
  </div>
58
75
  </div>
@@ -65,11 +82,55 @@
65
82
  </div>
66
83
  </div>
67
84
 
85
+ <!-- Content Widgets (if any) -->
86
+ {% assign content_widgets = widgets.content %}
87
+ {% if content_widgets and content_widgets.size > 0 %}
88
+ <section class="theme-section theme-section--content" data-section="content">
89
+ {% for widget in content_widgets %}
90
+ <div class="theme-widget-wrapper"
91
+ data-widget-id="{{ widget.id }}"
92
+ data-widget-type="{{ widget.type }}"
93
+ data-widget-position="{{ widget.Position | default: widget.position | default: forloop.index }}">
94
+ {% if widget and widget.template_path %}
95
+ {% render widget.template_path, widget: widget, settings: settings, shop: shop %}
96
+ {% endif %}
97
+ </div>
98
+ {% endfor %}
99
+ </section>
100
+ {% endif %}
101
+
68
102
  <style>
103
+ /* Template-level variable mappings to global theme variables */
104
+ :root {
105
+ --color-primary: {{ settings.color_primary | default: '#000000' }};
106
+ --color-primary-hover: {{ settings.color_primary_dark | default: '#333333' }};
107
+ --color-primary-light: {{ settings.color_primary_light | default: '#666666' }};
108
+ --color-success: {{ settings.color_success | default: '#22c55e' }};
109
+ --color-success-light: {{ settings.color_success | default: '#22c55e' }};
110
+ --color-danger: {{ settings.color_error | default: '#ef4444' }};
111
+ --color-danger-light: {{ settings.color_error | default: '#ef4444' }};
112
+ --color-text: {{ settings.color_text | default: '#000000' }};
113
+ --color-text-light: {{ settings.color_text_muted | default: '#666666' }};
114
+ --color-background: {{ settings.color_background | default: '#ffffff' }};
115
+ --color-card-bg: {{ settings.color_surface | default: '#f5f5f5' }};
116
+ --color-border: {{ settings.color_border | default: '#cccccc' }};
117
+ --color-border-light: {{ settings.color_surface | default: '#f5f5f5' }};
118
+ --shadow-sm: var(--shadow-sm);
119
+ --shadow-md: var(--shadow-md);
120
+ --shadow-lg: var(--shadow-lg);
121
+ --radius-md: var(--radius-md);
122
+ --radius-lg: var(--radius-lg);
123
+ --spacing-xs: var(--space-3);
124
+ --spacing-sm: var(--space-4);
125
+ --spacing-md: var(--space-6);
126
+ --spacing-lg: var(--space-8);
127
+ --spacing-xl: var(--space-12);
128
+ }
129
+
69
130
  /* Collection Toolbar Styles */
70
131
  .collection-toolbar {
71
- background-color: #fff;
72
- border-bottom: 1px solid rgba(0, 0, 0, 0.06);
132
+ background-color: var(--color-white);
133
+ border-bottom: 1px solid var(--color-border);
73
134
  padding: 1.5rem 0;
74
135
  margin-bottom: 2rem;
75
136
  }
@@ -84,7 +145,7 @@
84
145
  padding: 50px 20px;
85
146
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
86
147
  color: white;
87
- border-radius: 12px;
148
+ border-radius: var(--border-radius-medium);
88
149
  }
89
150
 
90
151
  .page-title {
@@ -114,10 +175,8 @@
114
175
 
115
176
  .collection-card {
116
177
  background: white;
117
- border-radius: 8px;
118
- overflow: hidden;
119
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
120
- transition: box-shadow 0.3s ease;
178
+ border-radius: var(--border-radius-medium);
179
+ overflow: hidden;
121
180
  position: relative;
122
181
  display: flex;
123
182
  flex-direction: column;
@@ -125,9 +184,9 @@
125
184
  border: 1px solid rgba(0, 0, 0, 0.05);
126
185
  }
127
186
 
128
- .collection-card:hover {
187
+ {% comment %} .collection-card:hover {
129
188
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
130
- }
189
+ } {% endcomment %}
131
190
 
132
191
  .collection-link {
133
192
  text-decoration: none;
@@ -147,6 +206,7 @@
147
206
  display: flex;
148
207
  align-items: center;
149
208
  justify-content: center;
209
+ border-radius: var(--border-radius-medium);
150
210
  }
151
211
 
152
212
  .collection-image img {
@@ -214,7 +274,7 @@
214
274
  }
215
275
 
216
276
  .collection-title {
217
- font-size: 1rem;
277
+ font-size: var(--text-xs);
218
278
  margin-bottom: 8px;
219
279
  color: #000;
220
280
  font-weight: 700;
@@ -320,7 +380,7 @@
320
380
  </style>
321
381
 
322
382
  <script>
323
- document.addEventListener('DOMContentLoaded', function() {
383
+ document.addEventListener('DOMContentLoaded', () => {
324
384
  // Parse URL parameters
325
385
  const urlParams = new URLSearchParams(window.location.search);
326
386
 
@@ -389,7 +449,7 @@ document.addEventListener('DOMContentLoaded', function() {
389
449
 
390
450
  // Toggle sort dropdown
391
451
  if (sortToggle && sortMenu) {
392
- sortToggle.addEventListener('click', function(e) {
452
+ sortToggle.addEventListener('click', (e) => {
393
453
  e.stopPropagation();
394
454
  sortMenu.classList.toggle('active');
395
455
  sortToggle.setAttribute('aria-expanded', sortMenu.classList.contains('active'));
@@ -397,7 +457,7 @@ document.addEventListener('DOMContentLoaded', function() {
397
457
  }
398
458
 
399
459
  // Close sort dropdown when clicking outside
400
- document.addEventListener('click', function(e) {
460
+ document.addEventListener('click', (e) => {
401
461
  if (sortDropdown && !sortDropdown.contains(e.target)) {
402
462
  sortMenu.classList.remove('active');
403
463
  sortToggle.setAttribute('aria-expanded', 'false');
@@ -408,7 +468,7 @@ document.addEventListener('DOMContentLoaded', function() {
408
468
  if (sortMenu) {
409
469
  const sortOptions = sortMenu.querySelectorAll('.sort-option');
410
470
  sortOptions.forEach(option => {
411
- option.addEventListener('click', function() {
471
+ option.addEventListener('click', () => {
412
472
  const sortValue = this.getAttribute('data-sort');
413
473
  const orderValue = this.getAttribute('data-order');
414
474
  const label = this.getAttribute('data-label');
@@ -439,11 +499,11 @@ document.addEventListener('DOMContentLoaded', function() {
439
499
  const collectionCards = document.querySelectorAll('.collection-card');
440
500
 
441
501
  collectionCards.forEach(card => {
442
- card.addEventListener('mouseenter', function() {
502
+ card.addEventListener('mouseenter', () => {
443
503
  this.style.transform = 'translateY(-8px)';
444
504
  });
445
505
 
446
- card.addEventListener('mouseleave', function() {
506
+ card.addEventListener('mouseleave', () => {
447
507
  this.style.transform = 'translateY(0)';
448
508
  });
449
509
  });