cinqcinqdev-seo 0.1.71 → 0.1.72

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/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.0.0"
6
6
  },
7
- "version": "0.1.71",
7
+ "version": "0.1.72",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "unknown"
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <section :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
2
+ <section ref="sectionEl" :class="isVisible ? 'sec-in' : 'sec-out'" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
3
3
 
4
4
  <!-- ── CENTERED ─────────────────────────────────────────────── -->
5
5
  <div v-if="layout === 'centered'" :class="[sectionPad]">
@@ -121,7 +121,7 @@ const ca = (c, h) => {
121
121
  const m = c.match(/rgba\((var\([^)]+\)),\s*[\d.]+\)/);
122
122
  return m ? `rgba(${m[1]}, ${Math.round(parseInt(h, 16) / 255 * 1e3) / 1e3})` : c + h;
123
123
  };
124
- import { computed } from "vue";
124
+ import { ref, computed, onMounted } from "vue";
125
125
  const props = defineProps({
126
126
  layout: { type: String, default: "centered" },
127
127
  titleTag: { type: String, default: "h1" },
@@ -144,7 +144,25 @@ const props = defineProps({
144
144
  paddingTop: { type: String, default: "" },
145
145
  paddingBottom: { type: String, default: "" },
146
146
  marginTop: { type: String, default: "" },
147
- marginBottom: { type: String, default: "" }
147
+ marginBottom: { type: String, default: "" },
148
+ paddingX: { type: String, default: "" }
149
+ });
150
+ const sectionEl = ref(null);
151
+ const isVisible = ref(false);
152
+ onMounted(() => {
153
+ const obs = new IntersectionObserver(
154
+ ([e]) => {
155
+ if (e.isIntersecting) {
156
+ isVisible.value = true;
157
+ obs.disconnect();
158
+ }
159
+ },
160
+ { threshold: 0.06 }
161
+ );
162
+ if (sectionEl.value) obs.observe(sectionEl.value);
163
+ setTimeout(() => {
164
+ if (!isVisible.value) isVisible.value = true;
165
+ }, 300);
148
166
  });
149
167
  const sectionPad = computed(() => ({ compact: "py-8 px-4 md:px-8", normal: "py-14 px-6 md:px-12", spacious: "py-24 px-8 md:px-16" })[props.spacing] || "py-14 px-6 md:px-12");
150
168
  const cardRound = computed(() => ({ sharp: "rounded-xl", rounded: "rounded-[24px]", pill: "rounded-[36px]" })[props.radius] || "rounded-[24px]");
@@ -161,6 +179,20 @@ const spacingStyle = computed(() => {
161
179
  if (props.paddingBottom && SM[props.paddingBottom]) s.paddingBottom = SM[props.paddingBottom];
162
180
  if (props.marginTop && SM[props.marginTop]) s.marginTop = SM[props.marginTop];
163
181
  if (props.marginBottom && SM[props.marginBottom]) s.marginBottom = SM[props.marginBottom];
182
+ if (props.paddingX && SM[props.paddingX]) {
183
+ const v = SM[props.paddingX];
184
+ if (v.startsWith("-")) {
185
+ s.marginLeft = v;
186
+ s.marginRight = v;
187
+ } else {
188
+ s.paddingLeft = v;
189
+ s.paddingRight = v;
190
+ }
191
+ }
164
192
  return s;
165
193
  });
166
194
  </script>
195
+
196
+ <style scoped>
197
+ .sec-out{opacity:0;transform:translateY(20px)}.sec-in{opacity:1;transform:none;transition:opacity .7s cubic-bezier(.16,1,.3,1),transform .7s cubic-bezier(.16,1,.3,1)}
198
+ </style>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <section :class="[sectionPad]" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
2
+ <section ref="sectionEl" :class="[sectionPad, isVisible ? 'sec-in' : 'sec-out']" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
3
3
 
4
4
  <!-- CENTERED -->
5
5
  <div v-if="layout === 'centered'" :class="[containerClass, 'text-center']">
@@ -65,7 +65,7 @@ const ca = (c, h) => {
65
65
  const m = c.match(/rgba\((var\([^)]+\)),\s*[\d.]+\)/);
66
66
  return m ? `rgba(${m[1]}, ${Math.round(parseInt(h, 16) / 255 * 1e3) / 1e3})` : c + h;
67
67
  };
68
- import { computed } from "vue";
68
+ import { ref, computed, onMounted } from "vue";
69
69
  const props = defineProps({
70
70
  layout: { type: String, default: "centered" },
71
71
  badge: { type: String, default: "" },
@@ -85,7 +85,25 @@ const props = defineProps({
85
85
  paddingTop: { type: String, default: "" },
86
86
  paddingBottom: { type: String, default: "" },
87
87
  marginTop: { type: String, default: "" },
88
- marginBottom: { type: String, default: "" }
88
+ marginBottom: { type: String, default: "" },
89
+ paddingX: { type: String, default: "" }
90
+ });
91
+ const sectionEl = ref(null);
92
+ const isVisible = ref(false);
93
+ onMounted(() => {
94
+ const obs = new IntersectionObserver(
95
+ ([e]) => {
96
+ if (e.isIntersecting) {
97
+ isVisible.value = true;
98
+ obs.disconnect();
99
+ }
100
+ },
101
+ { threshold: 0.06 }
102
+ );
103
+ if (sectionEl.value) obs.observe(sectionEl.value);
104
+ setTimeout(() => {
105
+ if (!isVisible.value) isVisible.value = true;
106
+ }, 300);
89
107
  });
90
108
  const sectionPad = computed(() => ({ compact: "py-8 px-4 md:px-8", normal: "py-14 px-6 md:px-12", spacious: "py-24 px-8 md:px-16" })[props.spacing] || "py-14 px-6 md:px-12");
91
109
  const cardRound = computed(() => ({ sharp: "rounded-xl", rounded: "rounded-2xl", pill: "rounded-full" })[props.radius] || "rounded-2xl");
@@ -99,6 +117,20 @@ const spacingStyle = computed(() => {
99
117
  if (props.paddingBottom && SM[props.paddingBottom]) s.paddingBottom = SM[props.paddingBottom];
100
118
  if (props.marginTop && SM[props.marginTop]) s.marginTop = SM[props.marginTop];
101
119
  if (props.marginBottom && SM[props.marginBottom]) s.marginBottom = SM[props.marginBottom];
120
+ if (props.paddingX && SM[props.paddingX]) {
121
+ const v = SM[props.paddingX];
122
+ if (v.startsWith("-")) {
123
+ s.marginLeft = v;
124
+ s.marginRight = v;
125
+ } else {
126
+ s.paddingLeft = v;
127
+ s.paddingRight = v;
128
+ }
129
+ }
102
130
  return s;
103
131
  });
104
132
  </script>
133
+
134
+ <style scoped>
135
+ .sec-out{opacity:0;transform:translateY(20px)}.sec-in{opacity:1;transform:none;transition:opacity .7s cubic-bezier(.16,1,.3,1),transform .7s cubic-bezier(.16,1,.3,1)}
136
+ </style>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <section :class="[sectionPad]" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
2
+ <section ref="sectionEl" :class="[sectionPad, isVisible ? 'sec-in' : 'sec-out']" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
3
3
  <div :class="[containerClass]">
4
4
 
5
5
  <!-- SPLIT layout -->
@@ -100,7 +100,7 @@ const ca = (c, h) => {
100
100
  const m = c.match(/rgba\((var\([^)]+\)),\s*[\d.]+\)/);
101
101
  return m ? `rgba(${m[1]}, ${Math.round(parseInt(h, 16) / 255 * 1e3) / 1e3})` : c + h;
102
102
  };
103
- import { ref, reactive, computed } from "vue";
103
+ import { ref, reactive, computed, onMounted } from "vue";
104
104
  const props = defineProps({
105
105
  layout: { type: String, default: "split" },
106
106
  title: { type: String, default: "Contactez-nous" },
@@ -116,6 +116,7 @@ const props = defineProps({
116
116
  paddingBottom: { type: String, default: "" },
117
117
  marginTop: { type: String, default: "" },
118
118
  marginBottom: { type: String, default: "" },
119
+ paddingX: { type: String, default: "" },
119
120
  fields: {
120
121
  type: Array,
121
122
  default: () => [
@@ -130,6 +131,23 @@ const props = defineProps({
130
131
  spacing: { type: String, default: "normal" },
131
132
  radius: { type: String, default: "rounded" }
132
133
  });
134
+ const sectionEl = ref(null);
135
+ const isVisible = ref(false);
136
+ onMounted(() => {
137
+ const obs = new IntersectionObserver(
138
+ ([e]) => {
139
+ if (e.isIntersecting) {
140
+ isVisible.value = true;
141
+ obs.disconnect();
142
+ }
143
+ },
144
+ { threshold: 0.06 }
145
+ );
146
+ if (sectionEl.value) obs.observe(sectionEl.value);
147
+ setTimeout(() => {
148
+ if (!isVisible.value) isVisible.value = true;
149
+ }, 300);
150
+ });
133
151
  const emit = defineEmits(["submit"]);
134
152
  const submitting = ref(false);
135
153
  const sent = ref(false);
@@ -156,6 +174,20 @@ const spacingStyle = computed(() => {
156
174
  if (props.paddingBottom && SM[props.paddingBottom]) s.paddingBottom = SM[props.paddingBottom];
157
175
  if (props.marginTop && SM[props.marginTop]) s.marginTop = SM[props.marginTop];
158
176
  if (props.marginBottom && SM[props.marginBottom]) s.marginBottom = SM[props.marginBottom];
177
+ if (props.paddingX && SM[props.paddingX]) {
178
+ const v = SM[props.paddingX];
179
+ if (v.startsWith("-")) {
180
+ s.marginLeft = v;
181
+ s.marginRight = v;
182
+ } else {
183
+ s.paddingLeft = v;
184
+ s.paddingRight = v;
185
+ }
186
+ }
159
187
  return s;
160
188
  });
161
189
  </script>
190
+
191
+ <style scoped>
192
+ .sec-out{opacity:0;transform:translateY(20px)}.sec-in{opacity:1;transform:none;transition:opacity .7s cubic-bezier(.16,1,.3,1),transform .7s cubic-bezier(.16,1,.3,1)}
193
+ </style>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <section :class="[sectionPad]" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
2
+ <section ref="sectionEl" :class="[sectionPad, isVisible ? 'sec-in' : 'sec-out']" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
3
3
  <div :class="[containerClass]">
4
4
  <div class="text-center mb-16">
5
5
  <component :is="titleTag" class="font-black uppercase tracking-tighter italic" :style="{ fontSize: titleFontSize }">{{ title }}</component>
@@ -31,7 +31,7 @@ const ca = (c, h) => {
31
31
  const m = c.match(/rgba\((var\([^)]+\)),\s*[\d.]+\)/);
32
32
  return m ? `rgba(${m[1]}, ${Math.round(parseInt(h, 16) / 255 * 1e3) / 1e3})` : c + h;
33
33
  };
34
- import { ref, computed } from "vue";
34
+ import { ref, computed, onMounted } from "vue";
35
35
  const props = defineProps({
36
36
  title: { type: String, default: "FAQ" },
37
37
  titleTag: { type: String, default: "h2" },
@@ -46,7 +46,25 @@ const props = defineProps({
46
46
  paddingTop: { type: String, default: "" },
47
47
  paddingBottom: { type: String, default: "" },
48
48
  marginTop: { type: String, default: "" },
49
- marginBottom: { type: String, default: "" }
49
+ marginBottom: { type: String, default: "" },
50
+ paddingX: { type: String, default: "" }
51
+ });
52
+ const sectionEl = ref(null);
53
+ const isVisible = ref(false);
54
+ onMounted(() => {
55
+ const obs = new IntersectionObserver(
56
+ ([e]) => {
57
+ if (e.isIntersecting) {
58
+ isVisible.value = true;
59
+ obs.disconnect();
60
+ }
61
+ },
62
+ { threshold: 0.06 }
63
+ );
64
+ if (sectionEl.value) obs.observe(sectionEl.value);
65
+ setTimeout(() => {
66
+ if (!isVisible.value) isVisible.value = true;
67
+ }, 300);
50
68
  });
51
69
  const sectionPad = computed(() => ({ compact: "py-8 px-4 md:px-8", normal: "py-14 px-6 md:px-12", spacious: "py-24 px-8 md:px-16" })[props.spacing] || "py-14 px-6 md:px-12");
52
70
  const cardRound = computed(() => ({ sharp: "rounded-xl", rounded: "rounded-[24px]", pill: "rounded-[36px]" })[props.radius] || "rounded-[24px]");
@@ -64,6 +82,20 @@ const spacingStyle = computed(() => {
64
82
  if (props.paddingBottom && SM[props.paddingBottom]) s.paddingBottom = SM[props.paddingBottom];
65
83
  if (props.marginTop && SM[props.marginTop]) s.marginTop = SM[props.marginTop];
66
84
  if (props.marginBottom && SM[props.marginBottom]) s.marginBottom = SM[props.marginBottom];
85
+ if (props.paddingX && SM[props.paddingX]) {
86
+ const v = SM[props.paddingX];
87
+ if (v.startsWith("-")) {
88
+ s.marginLeft = v;
89
+ s.marginRight = v;
90
+ } else {
91
+ s.paddingLeft = v;
92
+ s.paddingRight = v;
93
+ }
94
+ }
67
95
  return s;
68
96
  });
69
97
  </script>
98
+
99
+ <style scoped>
100
+ .sec-out{opacity:0;transform:translateY(20px)}.sec-in{opacity:1;transform:none;transition:opacity .7s cubic-bezier(.16,1,.3,1),transform .7s cubic-bezier(.16,1,.3,1)}
101
+ </style>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <section :class="[sectionPad, 'overflow-hidden']" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
2
+ <section ref="sectionEl" :class="[sectionPad, 'overflow-hidden', isVisible ? 'sec-in' : 'sec-out']" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
3
3
  <div :class="[containerClass]">
4
4
  <div class="mb-16 md:mb-20">
5
5
  <component :is="titleTag" class="font-black uppercase tracking-tighter leading-none italic" :style="{ fontSize: titleFontSize }">{{ title }}</component>
@@ -29,7 +29,7 @@ const ca = (c, h) => {
29
29
  const m = c.match(/rgba\((var\([^)]+\)),\s*[\d.]+\)/);
30
30
  return m ? `rgba(${m[1]}, ${Math.round(parseInt(h, 16) / 255 * 1e3) / 1e3})` : c + h;
31
31
  };
32
- import { computed } from "vue";
32
+ import { ref, computed, onMounted } from "vue";
33
33
  const props = defineProps({
34
34
  title: { type: String, default: "Why choose us?" },
35
35
  titleTag: { type: String, default: "h2" },
@@ -45,7 +45,25 @@ const props = defineProps({
45
45
  paddingTop: { type: String, default: "" },
46
46
  paddingBottom: { type: String, default: "" },
47
47
  marginTop: { type: String, default: "" },
48
- marginBottom: { type: String, default: "" }
48
+ marginBottom: { type: String, default: "" },
49
+ paddingX: { type: String, default: "" }
50
+ });
51
+ const sectionEl = ref(null);
52
+ const isVisible = ref(false);
53
+ onMounted(() => {
54
+ const obs = new IntersectionObserver(
55
+ ([e]) => {
56
+ if (e.isIntersecting) {
57
+ isVisible.value = true;
58
+ obs.disconnect();
59
+ }
60
+ },
61
+ { threshold: 0.06 }
62
+ );
63
+ if (sectionEl.value) obs.observe(sectionEl.value);
64
+ setTimeout(() => {
65
+ if (!isVisible.value) isVisible.value = true;
66
+ }, 300);
49
67
  });
50
68
  const sectionPad = computed(() => ({ compact: "py-8 px-4 md:px-8", normal: "py-14 px-6 md:px-12", spacious: "py-24 px-8 md:px-16" })[props.spacing] || "py-14 px-6 md:px-12");
51
69
  const cardRound = computed(() => ({ sharp: "rounded-xl", rounded: "rounded-[32px]", pill: "rounded-[48px]" })[props.radius] || "rounded-[32px]");
@@ -59,6 +77,20 @@ const spacingStyle = computed(() => {
59
77
  if (props.paddingBottom && SM[props.paddingBottom]) s.paddingBottom = SM[props.paddingBottom];
60
78
  if (props.marginTop && SM[props.marginTop]) s.marginTop = SM[props.marginTop];
61
79
  if (props.marginBottom && SM[props.marginBottom]) s.marginBottom = SM[props.marginBottom];
80
+ if (props.paddingX && SM[props.paddingX]) {
81
+ const v = SM[props.paddingX];
82
+ if (v.startsWith("-")) {
83
+ s.marginLeft = v;
84
+ s.marginRight = v;
85
+ } else {
86
+ s.paddingLeft = v;
87
+ s.paddingRight = v;
88
+ }
89
+ }
62
90
  return s;
63
91
  });
64
92
  </script>
93
+
94
+ <style scoped>
95
+ .sec-out{opacity:0;transform:translateY(20px)}.sec-in{opacity:1;transform:none;transition:opacity .7s cubic-bezier(.16,1,.3,1),transform .7s cubic-bezier(.16,1,.3,1)}
96
+ </style>
@@ -193,6 +193,7 @@ const props = defineProps({
193
193
  paddingBottom: { type: String, default: "" },
194
194
  marginTop: { type: String, default: "" },
195
195
  marginBottom: { type: String, default: "" },
196
+ paddingX: { type: String, default: "" },
196
197
  overlay: { type: String, default: "auto" },
197
198
  overlayDirection: { type: String, default: "to top" },
198
199
  overlayStrength: { type: String, default: "medium" },
@@ -250,6 +251,16 @@ const spacingStyle = computed(() => {
250
251
  if (props.paddingBottom && SM[props.paddingBottom]) s.paddingBottom = SM[props.paddingBottom];
251
252
  if (props.marginTop && SM[props.marginTop]) s.marginTop = SM[props.marginTop];
252
253
  if (props.marginBottom && SM[props.marginBottom]) s.marginBottom = SM[props.marginBottom];
254
+ if (props.paddingX && SM[props.paddingX]) {
255
+ const v = SM[props.paddingX];
256
+ if (v.startsWith("-")) {
257
+ s.marginLeft = v;
258
+ s.marginRight = v;
259
+ } else {
260
+ s.paddingLeft = v;
261
+ s.paddingRight = v;
262
+ }
263
+ }
253
264
  return s;
254
265
  });
255
266
  </script>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <section :class="[sectionPad]" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
2
+ <section ref="sectionEl" :class="[sectionPad, isVisible ? 'sec-in' : 'sec-out']" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
3
3
 
4
4
  <!-- CENTERED -->
5
5
  <div v-if="layout === 'centered'" :class="[containerClass, 'text-center']">
@@ -51,7 +51,7 @@ const ca = (c, h) => {
51
51
  const m = c.match(/rgba\((var\([^)]+\)),\s*[\d.]+\)/);
52
52
  return m ? `rgba(${m[1]}, ${Math.round(parseInt(h, 16) / 255 * 1e3) / 1e3})` : c + h;
53
53
  };
54
- import { computed } from "vue";
54
+ import { ref, computed, onMounted } from "vue";
55
55
  const props = defineProps({
56
56
  layout: { type: String, default: "centered" },
57
57
  badge: { type: String, default: "" },
@@ -70,7 +70,25 @@ const props = defineProps({
70
70
  paddingTop: { type: String, default: "" },
71
71
  paddingBottom: { type: String, default: "" },
72
72
  marginTop: { type: String, default: "" },
73
- marginBottom: { type: String, default: "" }
73
+ marginBottom: { type: String, default: "" },
74
+ paddingX: { type: String, default: "" }
75
+ });
76
+ const sectionEl = ref(null);
77
+ const isVisible = ref(false);
78
+ onMounted(() => {
79
+ const obs = new IntersectionObserver(
80
+ ([e]) => {
81
+ if (e.isIntersecting) {
82
+ isVisible.value = true;
83
+ obs.disconnect();
84
+ }
85
+ },
86
+ { threshold: 0.06 }
87
+ );
88
+ if (sectionEl.value) obs.observe(sectionEl.value);
89
+ setTimeout(() => {
90
+ if (!isVisible.value) isVisible.value = true;
91
+ }, 300);
74
92
  });
75
93
  const sectionPad = computed(() => ({ compact: "py-8 px-4 md:px-8", normal: "py-14 px-6 md:px-12", spacious: "py-24 px-8 md:px-16" })[props.spacing] || "py-14 px-6 md:px-12");
76
94
  const cardRound = computed(() => ({ sharp: "rounded-lg", rounded: "rounded-xl", pill: "rounded-full" })[props.radius] || "rounded-xl");
@@ -84,6 +102,20 @@ const spacingStyle = computed(() => {
84
102
  if (props.paddingBottom && SM[props.paddingBottom]) s.paddingBottom = SM[props.paddingBottom];
85
103
  if (props.marginTop && SM[props.marginTop]) s.marginTop = SM[props.marginTop];
86
104
  if (props.marginBottom && SM[props.marginBottom]) s.marginBottom = SM[props.marginBottom];
105
+ if (props.paddingX && SM[props.paddingX]) {
106
+ const v = SM[props.paddingX];
107
+ if (v.startsWith("-")) {
108
+ s.marginLeft = v;
109
+ s.marginRight = v;
110
+ } else {
111
+ s.paddingLeft = v;
112
+ s.paddingRight = v;
113
+ }
114
+ }
87
115
  return s;
88
116
  });
89
117
  </script>
118
+
119
+ <style scoped>
120
+ .sec-out{opacity:0;transform:translateY(20px)}.sec-in{opacity:1;transform:none;transition:opacity .7s cubic-bezier(.16,1,.3,1),transform .7s cubic-bezier(.16,1,.3,1)}
121
+ </style>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <section :class="[sectionPad]" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
2
+ <section ref="sectionEl" :class="[sectionPad, isVisible ? 'sec-in' : 'sec-out']" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
3
3
  <div :class="[containerClass]">
4
4
 
5
5
  <div v-if="title || description" class="mb-12 text-center max-w-2xl mx-auto">
@@ -88,7 +88,7 @@ const ca = (c, h) => {
88
88
  const m = c.match(/rgba\((var\([^)]+\)),\s*[\d.]+\)/);
89
89
  return m ? `rgba(${m[1]}, ${Math.round(parseInt(h, 16) / 255 * 1e3) / 1e3})` : c + h;
90
90
  };
91
- import { computed } from "vue";
91
+ import { ref, computed, onMounted } from "vue";
92
92
  const props = defineProps({
93
93
  layout: { type: String, default: "cards" },
94
94
  title: { type: String, default: "Our Pricing" },
@@ -106,7 +106,25 @@ const props = defineProps({
106
106
  paddingTop: { type: String, default: "" },
107
107
  paddingBottom: { type: String, default: "" },
108
108
  marginTop: { type: String, default: "" },
109
- marginBottom: { type: String, default: "" }
109
+ marginBottom: { type: String, default: "" },
110
+ paddingX: { type: String, default: "" }
111
+ });
112
+ const sectionEl = ref(null);
113
+ const isVisible = ref(false);
114
+ onMounted(() => {
115
+ const obs = new IntersectionObserver(
116
+ ([e]) => {
117
+ if (e.isIntersecting) {
118
+ isVisible.value = true;
119
+ obs.disconnect();
120
+ }
121
+ },
122
+ { threshold: 0.06 }
123
+ );
124
+ if (sectionEl.value) obs.observe(sectionEl.value);
125
+ setTimeout(() => {
126
+ if (!isVisible.value) isVisible.value = true;
127
+ }, 300);
110
128
  });
111
129
  const sectionPad = computed(() => ({ compact: "py-8 px-4 md:px-8", normal: "py-14 px-6 md:px-12", spacious: "py-24 px-8 md:px-16" })[props.spacing] || "py-14 px-6 md:px-12");
112
130
  const cardRound = computed(() => ({ sharp: "rounded-xl", rounded: "rounded-3xl", pill: "rounded-[40px]" })[props.radius] || "rounded-3xl");
@@ -126,6 +144,20 @@ const spacingStyle = computed(() => {
126
144
  if (props.paddingBottom && SM[props.paddingBottom]) s.paddingBottom = SM[props.paddingBottom];
127
145
  if (props.marginTop && SM[props.marginTop]) s.marginTop = SM[props.marginTop];
128
146
  if (props.marginBottom && SM[props.marginBottom]) s.marginBottom = SM[props.marginBottom];
147
+ if (props.paddingX && SM[props.paddingX]) {
148
+ const v = SM[props.paddingX];
149
+ if (v.startsWith("-")) {
150
+ s.marginLeft = v;
151
+ s.marginRight = v;
152
+ } else {
153
+ s.paddingLeft = v;
154
+ s.paddingRight = v;
155
+ }
156
+ }
129
157
  return s;
130
158
  });
131
159
  </script>
160
+
161
+ <style scoped>
162
+ .sec-out{opacity:0;transform:translateY(20px)}.sec-in{opacity:1;transform:none;transition:opacity .7s cubic-bezier(.16,1,.3,1),transform .7s cubic-bezier(.16,1,.3,1)}
163
+ </style>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <section :class="[sectionPad]" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
2
+ <section ref="sectionEl" :class="[sectionPad, isVisible ? 'sec-in' : 'sec-out']" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
3
3
  <div :class="[containerClass]">
4
4
 
5
5
  <div v-if="title || description" class="mb-12" :class="['horizontal','cards'].includes(layout) ? 'text-center max-w-2xl mx-auto' : 'max-w-xl'">
@@ -80,7 +80,7 @@ const ca = (c, h) => {
80
80
  const m = c.match(/rgba\((var\([^)]+\)),\s*[\d.]+\)/);
81
81
  return m ? `rgba(${m[1]}, ${Math.round(parseInt(h, 16) / 255 * 1e3) / 1e3})` : c + h;
82
82
  };
83
- import { computed } from "vue";
83
+ import { ref, computed, onMounted } from "vue";
84
84
  const props = defineProps({
85
85
  layout: { type: String, default: "steps" },
86
86
  title: { type: String, default: "How it works" },
@@ -99,7 +99,25 @@ const props = defineProps({
99
99
  paddingTop: { type: String, default: "" },
100
100
  paddingBottom: { type: String, default: "" },
101
101
  marginTop: { type: String, default: "" },
102
- marginBottom: { type: String, default: "" }
102
+ marginBottom: { type: String, default: "" },
103
+ paddingX: { type: String, default: "" }
104
+ });
105
+ const sectionEl = ref(null);
106
+ const isVisible = ref(false);
107
+ onMounted(() => {
108
+ const obs = new IntersectionObserver(
109
+ ([e]) => {
110
+ if (e.isIntersecting) {
111
+ isVisible.value = true;
112
+ obs.disconnect();
113
+ }
114
+ },
115
+ { threshold: 0.06 }
116
+ );
117
+ if (sectionEl.value) obs.observe(sectionEl.value);
118
+ setTimeout(() => {
119
+ if (!isVisible.value) isVisible.value = true;
120
+ }, 300);
103
121
  });
104
122
  const sectionPad = computed(() => ({ compact: "py-8 px-4 md:px-8", normal: "py-14 px-6 md:px-12", spacious: "py-24 px-8 md:px-16" })[props.spacing] || "py-14 px-6 md:px-12");
105
123
  const cardRound = computed(() => ({ sharp: "rounded-xl", rounded: "rounded-3xl", pill: "rounded-[40px]" })[props.radius] || "rounded-3xl");
@@ -113,6 +131,20 @@ const spacingStyle = computed(() => {
113
131
  if (props.paddingBottom && SM[props.paddingBottom]) s.paddingBottom = SM[props.paddingBottom];
114
132
  if (props.marginTop && SM[props.marginTop]) s.marginTop = SM[props.marginTop];
115
133
  if (props.marginBottom && SM[props.marginBottom]) s.marginBottom = SM[props.marginBottom];
134
+ if (props.paddingX && SM[props.paddingX]) {
135
+ const v = SM[props.paddingX];
136
+ if (v.startsWith("-")) {
137
+ s.marginLeft = v;
138
+ s.marginRight = v;
139
+ } else {
140
+ s.paddingLeft = v;
141
+ s.paddingRight = v;
142
+ }
143
+ }
116
144
  return s;
117
145
  });
118
146
  </script>
147
+
148
+ <style scoped>
149
+ .sec-out{opacity:0;transform:translateY(20px)}.sec-in{opacity:1;transform:none;transition:opacity .7s cubic-bezier(.16,1,.3,1),transform .7s cubic-bezier(.16,1,.3,1)}
150
+ </style>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <section :class="[sectionPad]" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
2
+ <section ref="sectionEl" :class="[sectionPad, isVisible ? 'sec-in' : 'sec-out']" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
3
3
 
4
4
  <!-- CENTERED -->
5
5
  <div v-if="layout === 'centered'" :class="[containerClass, 'text-center']">
@@ -62,7 +62,7 @@ const ca = (c, h) => {
62
62
  const m = c.match(/rgba\((var\([^)]+\)),\s*[\d.]+\)/);
63
63
  return m ? `rgba(${m[1]}, ${Math.round(parseInt(h, 16) / 255 * 1e3) / 1e3})` : c + h;
64
64
  };
65
- import { computed } from "vue";
65
+ import { ref, computed, onMounted } from "vue";
66
66
  const props = defineProps({
67
67
  layout: { type: String, default: "centered" },
68
68
  quote: { type: String, default: "" },
@@ -79,7 +79,25 @@ const props = defineProps({
79
79
  paddingTop: { type: String, default: "" },
80
80
  paddingBottom: { type: String, default: "" },
81
81
  marginTop: { type: String, default: "" },
82
- marginBottom: { type: String, default: "" }
82
+ marginBottom: { type: String, default: "" },
83
+ paddingX: { type: String, default: "" }
84
+ });
85
+ const sectionEl = ref(null);
86
+ const isVisible = ref(false);
87
+ onMounted(() => {
88
+ const obs = new IntersectionObserver(
89
+ ([e]) => {
90
+ if (e.isIntersecting) {
91
+ isVisible.value = true;
92
+ obs.disconnect();
93
+ }
94
+ },
95
+ { threshold: 0.06 }
96
+ );
97
+ if (sectionEl.value) obs.observe(sectionEl.value);
98
+ setTimeout(() => {
99
+ if (!isVisible.value) isVisible.value = true;
100
+ }, 300);
83
101
  });
84
102
  const sectionPad = computed(() => ({ compact: "py-8 px-4 md:px-8", normal: "py-14 px-6 md:px-12", spacious: "py-24 px-8 md:px-16" })[props.spacing] || "py-14 px-6 md:px-12");
85
103
  const containerClass = computed(() => ({ narrow: "max-w-3xl mx-auto", normal: "max-w-5xl mx-auto", wide: "max-w-7xl mx-auto", full: "max-w-full" })[props.contentWidth] || "max-w-3xl mx-auto");
@@ -91,6 +109,20 @@ const spacingStyle = computed(() => {
91
109
  if (props.paddingBottom && SM[props.paddingBottom]) s.paddingBottom = SM[props.paddingBottom];
92
110
  if (props.marginTop && SM[props.marginTop]) s.marginTop = SM[props.marginTop];
93
111
  if (props.marginBottom && SM[props.marginBottom]) s.marginBottom = SM[props.marginBottom];
112
+ if (props.paddingX && SM[props.paddingX]) {
113
+ const v = SM[props.paddingX];
114
+ if (v.startsWith("-")) {
115
+ s.marginLeft = v;
116
+ s.marginRight = v;
117
+ } else {
118
+ s.paddingLeft = v;
119
+ s.paddingRight = v;
120
+ }
121
+ }
94
122
  return s;
95
123
  });
96
124
  </script>
125
+
126
+ <style scoped>
127
+ .sec-out{opacity:0;transform:translateY(20px)}.sec-in{opacity:1;transform:none;transition:opacity .7s cubic-bezier(.16,1,.3,1),transform .7s cubic-bezier(.16,1,.3,1)}
128
+ </style>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <section :class="[sectionPad]" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
2
+ <section ref="sectionEl" :class="[sectionPad, isVisible ? 'sec-in' : 'sec-out']" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
3
3
  <div :class="maxW">
4
4
 
5
5
  <!-- Optional title -->
@@ -32,7 +32,7 @@ const ca = (c, h) => {
32
32
  const m = c.match(/rgba\((var\([^)]+\)),\s*[\d.]+\)/);
33
33
  return m ? `rgba(${m[1]}, ${Math.round(parseInt(h, 16) / 255 * 1e3) / 1e3})` : c + h;
34
34
  };
35
- import { computed } from "vue";
35
+ import { ref, computed, onMounted } from "vue";
36
36
  const props = defineProps({
37
37
  layout: { type: String, default: "article" },
38
38
  titleTag: { type: String, default: "h2" },
@@ -50,7 +50,25 @@ const props = defineProps({
50
50
  paddingTop: { type: String, default: "" },
51
51
  paddingBottom: { type: String, default: "" },
52
52
  marginTop: { type: String, default: "" },
53
- marginBottom: { type: String, default: "" }
53
+ marginBottom: { type: String, default: "" },
54
+ paddingX: { type: String, default: "" }
55
+ });
56
+ const sectionEl = ref(null);
57
+ const isVisible = ref(false);
58
+ onMounted(() => {
59
+ const obs = new IntersectionObserver(
60
+ ([e]) => {
61
+ if (e.isIntersecting) {
62
+ isVisible.value = true;
63
+ obs.disconnect();
64
+ }
65
+ },
66
+ { threshold: 0.06 }
67
+ );
68
+ if (sectionEl.value) obs.observe(sectionEl.value);
69
+ setTimeout(() => {
70
+ if (!isVisible.value) isVisible.value = true;
71
+ }, 300);
54
72
  });
55
73
  const sectionPad = computed(() => ({ compact: "py-8 px-4 md:px-8", normal: "py-14 px-6 md:px-12", spacious: "py-24 px-8 md:px-16" })[props.spacing] || "py-14 px-6 md:px-12");
56
74
  const cardRound = computed(() => ({ sharp: "rounded-lg", rounded: "rounded-xl", pill: "rounded-full" })[props.radius] || "rounded-xl");
@@ -81,10 +99,20 @@ const spacingStyle = computed(() => {
81
99
  if (props.paddingBottom && SM[props.paddingBottom]) s.paddingBottom = SM[props.paddingBottom];
82
100
  if (props.marginTop && SM[props.marginTop]) s.marginTop = SM[props.marginTop];
83
101
  if (props.marginBottom && SM[props.marginBottom]) s.marginBottom = SM[props.marginBottom];
102
+ if (props.paddingX && SM[props.paddingX]) {
103
+ const v = SM[props.paddingX];
104
+ if (v.startsWith("-")) {
105
+ s.marginLeft = v;
106
+ s.marginRight = v;
107
+ } else {
108
+ s.paddingLeft = v;
109
+ s.paddingRight = v;
110
+ }
111
+ }
84
112
  return s;
85
113
  });
86
114
  </script>
87
115
 
88
116
  <style scoped>
89
- .prose-content :deep(h1){font-size:1.9em;font-weight:900;letter-spacing:-.03em;margin:1.6em 0 .5em}.prose-content :deep(h2){font-size:1.4em;font-weight:900;letter-spacing:-.02em;margin:1.5em 0 .5em}.prose-content :deep(h3){font-size:1.15em;font-weight:800;margin:1.2em 0 .4em}.prose-content :deep(h4){font-size:1em;font-weight:700;margin:1em 0 .3em}.prose-content :deep(p){margin-bottom:1em}.prose-content :deep(ul){list-style:disc;margin-bottom:1em;padding-left:1.6em}.prose-content :deep(ol){list-style:decimal;margin-bottom:1em;padding-left:1.6em}.prose-content :deep(li){margin-bottom:.35em}.prose-content :deep(strong){font-weight:700}.prose-content :deep(em){font-style:italic}.prose-content :deep(u){text-decoration:underline}.prose-content :deep(a){opacity:.75;text-decoration:underline}.prose-content :deep(blockquote){border-left:3px solid;font-style:italic;margin:1.2em 0;opacity:.65;padding-left:1em}
117
+ .sec-out{opacity:0;transform:translateY(20px)}.sec-in{opacity:1;transform:none;transition:opacity .7s cubic-bezier(.16,1,.3,1),transform .7s cubic-bezier(.16,1,.3,1)}.prose-content :deep(h1){font-size:1.9em;font-weight:900;letter-spacing:-.03em;margin:1.6em 0 .5em}.prose-content :deep(h2){font-size:1.4em;font-weight:900;letter-spacing:-.02em;margin:1.5em 0 .5em}.prose-content :deep(h3){font-size:1.15em;font-weight:800;margin:1.2em 0 .4em}.prose-content :deep(h4){font-size:1em;font-weight:700;margin:1em 0 .3em}.prose-content :deep(p){margin-bottom:1em}.prose-content :deep(ul){list-style:disc;margin-bottom:1em;padding-left:1.6em}.prose-content :deep(ol){list-style:decimal;margin-bottom:1em;padding-left:1.6em}.prose-content :deep(li){margin-bottom:.35em}.prose-content :deep(strong){font-weight:700}.prose-content :deep(em){font-style:italic}.prose-content :deep(u){text-decoration:underline}.prose-content :deep(a){opacity:.75;text-decoration:underline}.prose-content :deep(blockquote){border-left:3px solid;font-style:italic;margin:1.2em 0;opacity:.65;padding-left:1em}
90
118
  </style>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <section :class="[sectionPad]" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
2
+ <section ref="sectionEl" :class="[sectionPad, isVisible ? 'sec-in' : 'sec-out']" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
3
3
  <div :class="[containerClass]">
4
4
 
5
5
  <div v-if="title || description" class="mb-12" :class="headerAlign === 'center' ? 'text-center max-w-2xl mx-auto' : 'max-w-xl'">
@@ -80,7 +80,7 @@ const ca = (c, h) => {
80
80
  const m = c.match(/rgba\((var\([^)]+\)),\s*[\d.]+\)/);
81
81
  return m ? `rgba(${m[1]}, ${Math.round(parseInt(h, 16) / 255 * 1e3) / 1e3})` : c + h;
82
82
  };
83
- import { computed } from "vue";
83
+ import { ref, computed, onMounted } from "vue";
84
84
  const props = defineProps({
85
85
  layout: { type: String, default: "grid" },
86
86
  title: { type: String, default: "Our Services" },
@@ -101,7 +101,25 @@ const props = defineProps({
101
101
  paddingTop: { type: String, default: "" },
102
102
  paddingBottom: { type: String, default: "" },
103
103
  marginTop: { type: String, default: "" },
104
- marginBottom: { type: String, default: "" }
104
+ marginBottom: { type: String, default: "" },
105
+ paddingX: { type: String, default: "" }
106
+ });
107
+ const sectionEl = ref(null);
108
+ const isVisible = ref(false);
109
+ onMounted(() => {
110
+ const obs = new IntersectionObserver(
111
+ ([e]) => {
112
+ if (e.isIntersecting) {
113
+ isVisible.value = true;
114
+ obs.disconnect();
115
+ }
116
+ },
117
+ { threshold: 0.06 }
118
+ );
119
+ if (sectionEl.value) obs.observe(sectionEl.value);
120
+ setTimeout(() => {
121
+ if (!isVisible.value) isVisible.value = true;
122
+ }, 300);
105
123
  });
106
124
  const sectionPad = computed(() => ({ compact: "py-8 px-4 md:px-8", normal: "py-14 px-6 md:px-12", spacious: "py-24 px-8 md:px-16" })[props.spacing] || "py-14 px-6 md:px-12");
107
125
  const cardRound = computed(() => ({ sharp: "rounded-xl", rounded: "rounded-3xl", pill: "rounded-[40px]" })[props.radius] || "rounded-3xl");
@@ -115,6 +133,20 @@ const spacingStyle = computed(() => {
115
133
  if (props.paddingBottom && SM[props.paddingBottom]) s.paddingBottom = SM[props.paddingBottom];
116
134
  if (props.marginTop && SM[props.marginTop]) s.marginTop = SM[props.marginTop];
117
135
  if (props.marginBottom && SM[props.marginBottom]) s.marginBottom = SM[props.marginBottom];
136
+ if (props.paddingX && SM[props.paddingX]) {
137
+ const v = SM[props.paddingX];
138
+ if (v.startsWith("-")) {
139
+ s.marginLeft = v;
140
+ s.marginRight = v;
141
+ } else {
142
+ s.paddingLeft = v;
143
+ s.paddingRight = v;
144
+ }
145
+ }
118
146
  return s;
119
147
  });
120
148
  </script>
149
+
150
+ <style scoped>
151
+ .sec-out{opacity:0;transform:translateY(20px)}.sec-in{opacity:1;transform:none;transition:opacity .7s cubic-bezier(.16,1,.3,1),transform .7s cubic-bezier(.16,1,.3,1)}
152
+ </style>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <section :class="[sectionPad]" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
2
+ <section ref="sectionEl" :class="[sectionPad, isVisible ? 'sec-in' : 'sec-out']" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
3
3
  <div :class="[containerClass]">
4
4
 
5
5
  <div v-if="title || description" class="mb-12 max-w-xl">
@@ -81,7 +81,7 @@ const ca = (c, h) => {
81
81
  const m = c.match(/rgba\((var\([^)]+\)),\s*[\d.]+\)/);
82
82
  return m ? `rgba(${m[1]}, ${Math.round(parseInt(h, 16) / 255 * 1e3) / 1e3})` : c + h;
83
83
  };
84
- import { computed } from "vue";
84
+ import { ref, computed, onMounted } from "vue";
85
85
  const props = defineProps({
86
86
  layout: { type: String, default: "grid" },
87
87
  title: { type: String, default: "What our clients say" },
@@ -99,7 +99,25 @@ const props = defineProps({
99
99
  paddingTop: { type: String, default: "" },
100
100
  paddingBottom: { type: String, default: "" },
101
101
  marginTop: { type: String, default: "" },
102
- marginBottom: { type: String, default: "" }
102
+ marginBottom: { type: String, default: "" },
103
+ paddingX: { type: String, default: "" }
104
+ });
105
+ const sectionEl = ref(null);
106
+ const isVisible = ref(false);
107
+ onMounted(() => {
108
+ const obs = new IntersectionObserver(
109
+ ([e]) => {
110
+ if (e.isIntersecting) {
111
+ isVisible.value = true;
112
+ obs.disconnect();
113
+ }
114
+ },
115
+ { threshold: 0.06 }
116
+ );
117
+ if (sectionEl.value) obs.observe(sectionEl.value);
118
+ setTimeout(() => {
119
+ if (!isVisible.value) isVisible.value = true;
120
+ }, 300);
103
121
  });
104
122
  const sectionPad = computed(() => ({ compact: "py-8 px-4 md:px-8", normal: "py-14 px-6 md:px-12", spacious: "py-24 px-8 md:px-16" })[props.spacing] || "py-14 px-6 md:px-12");
105
123
  const cardRound = computed(() => ({ sharp: "rounded-xl", rounded: "rounded-3xl", pill: "rounded-[40px]" })[props.radius] || "rounded-3xl");
@@ -113,6 +131,20 @@ const spacingStyle = computed(() => {
113
131
  if (props.paddingBottom && SM[props.paddingBottom]) s.paddingBottom = SM[props.paddingBottom];
114
132
  if (props.marginTop && SM[props.marginTop]) s.marginTop = SM[props.marginTop];
115
133
  if (props.marginBottom && SM[props.marginBottom]) s.marginBottom = SM[props.marginBottom];
134
+ if (props.paddingX && SM[props.paddingX]) {
135
+ const v = SM[props.paddingX];
136
+ if (v.startsWith("-")) {
137
+ s.marginLeft = v;
138
+ s.marginRight = v;
139
+ } else {
140
+ s.paddingLeft = v;
141
+ s.paddingRight = v;
142
+ }
143
+ }
116
144
  return s;
117
145
  });
118
146
  </script>
147
+
148
+ <style scoped>
149
+ .sec-out{opacity:0;transform:translateY(20px)}.sec-in{opacity:1;transform:none;transition:opacity .7s cubic-bezier(.16,1,.3,1),transform .7s cubic-bezier(.16,1,.3,1)}
150
+ </style>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <section :class="[sectionPad]" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
2
+ <section ref="sectionEl" :class="[sectionPad, isVisible ? 'sec-in' : 'sec-out']" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
3
3
 
4
4
  <!-- SPLIT -->
5
5
  <div v-if="layout === 'split'" :class="[containerClass, 'flex flex-col md:flex-row gap-12 items-center', reverse ? 'md:flex-row-reverse' : '']">
@@ -69,7 +69,7 @@ const ca = (c, h) => {
69
69
  const m = c.match(/rgba\((var\([^)]+\)),\s*[\d.]+\)/);
70
70
  return m ? `rgba(${m[1]}, ${Math.round(parseInt(h, 16) / 255 * 1e3) / 1e3})` : c + h;
71
71
  };
72
- import { computed } from "vue";
72
+ import { ref, computed, onMounted } from "vue";
73
73
  const props = defineProps({
74
74
  layout: { type: String, default: "split" },
75
75
  title: { type: String, default: "Our Vision" },
@@ -89,7 +89,29 @@ const props = defineProps({
89
89
  radius: { type: String, default: "rounded" },
90
90
  textDirection: { type: String, default: "ltr" },
91
91
  contentWidth: { type: String, default: "normal" },
92
- textSize: { type: String, default: "md" }
92
+ textSize: { type: String, default: "md" },
93
+ paddingTop: { type: String, default: "" },
94
+ paddingBottom: { type: String, default: "" },
95
+ marginTop: { type: String, default: "" },
96
+ marginBottom: { type: String, default: "" },
97
+ paddingX: { type: String, default: "" }
98
+ });
99
+ const sectionEl = ref(null);
100
+ const isVisible = ref(false);
101
+ onMounted(() => {
102
+ const obs = new IntersectionObserver(
103
+ ([e]) => {
104
+ if (e.isIntersecting) {
105
+ isVisible.value = true;
106
+ obs.disconnect();
107
+ }
108
+ },
109
+ { threshold: 0.06 }
110
+ );
111
+ if (sectionEl.value) obs.observe(sectionEl.value);
112
+ setTimeout(() => {
113
+ if (!isVisible.value) isVisible.value = true;
114
+ }, 300);
93
115
  });
94
116
  const sectionPad = computed(() => ({ compact: "py-8 px-4 md:px-8", normal: "py-14 px-6 md:px-12", spacious: "py-24 px-8 md:px-16" })[props.spacing] || "py-14 px-6 md:px-12");
95
117
  const cardRound = computed(() => ({ sharp: "rounded-xl", rounded: "rounded-3xl", pill: "rounded-[40px]" })[props.radius] || "rounded-3xl");
@@ -108,6 +130,20 @@ const spacingStyle = computed(() => {
108
130
  if (props.paddingBottom && SM[props.paddingBottom]) s.paddingBottom = SM[props.paddingBottom];
109
131
  if (props.marginTop && SM[props.marginTop]) s.marginTop = SM[props.marginTop];
110
132
  if (props.marginBottom && SM[props.marginBottom]) s.marginBottom = SM[props.marginBottom];
133
+ if (props.paddingX && SM[props.paddingX]) {
134
+ const v = SM[props.paddingX];
135
+ if (v.startsWith("-")) {
136
+ s.marginLeft = v;
137
+ s.marginRight = v;
138
+ } else {
139
+ s.paddingLeft = v;
140
+ s.paddingRight = v;
141
+ }
142
+ }
111
143
  return s;
112
144
  });
113
145
  </script>
146
+
147
+ <style scoped>
148
+ .sec-out{opacity:0;transform:translateY(20px)}.sec-in{opacity:1;transform:none;transition:opacity .7s cubic-bezier(.16,1,.3,1),transform .7s cubic-bezier(.16,1,.3,1)}
149
+ </style>
@@ -104,6 +104,7 @@ export const DEFAULT_SECTIONS = {
104
104
  paddingBottom: "",
105
105
  marginTop: "",
106
106
  marginBottom: "",
107
+ paddingX: "",
107
108
  overlay: "auto",
108
109
  overlayDirection: "to top",
109
110
  overlayStrength: "medium",
@@ -165,7 +166,8 @@ export const DEFAULT_SECTIONS = {
165
166
  paddingTop: { type: "select", label: "Padding Top", options: paddingOptions },
166
167
  paddingBottom: { type: "select", label: "Padding Bottom", options: paddingOptions },
167
168
  marginTop: { type: "select", label: "Margin Top", options: marginOptions },
168
- marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions }
169
+ marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions },
170
+ paddingX: { type: "select", label: "Padding X", options: marginOptions }
169
171
  }
170
172
  },
171
173
  HeroSlider: {
@@ -195,7 +197,8 @@ export const DEFAULT_SECTIONS = {
195
197
  paddingTop: "",
196
198
  paddingBottom: "",
197
199
  marginTop: "",
198
- marginBottom: ""
200
+ marginBottom: "",
201
+ paddingX: ""
199
202
  },
200
203
  fields: {
201
204
  slides: { type: "list", label: "Slides", itemLabel: "title", itemFields: {
@@ -247,7 +250,8 @@ export const DEFAULT_SECTIONS = {
247
250
  paddingTop: { type: "select", label: "Padding Top", options: paddingOptions },
248
251
  paddingBottom: { type: "select", label: "Padding Bottom", options: paddingOptions },
249
252
  marginTop: { type: "select", label: "Margin Top", options: marginOptions },
250
- marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions }
253
+ marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions },
254
+ paddingX: { type: "select", label: "Padding X", options: marginOptions }
251
255
  }
252
256
  },
253
257
  HeroFan: {
@@ -276,7 +280,8 @@ export const DEFAULT_SECTIONS = {
276
280
  paddingTop: "",
277
281
  paddingBottom: "",
278
282
  marginTop: "",
279
- marginBottom: ""
283
+ marginBottom: "",
284
+ paddingX: ""
280
285
  },
281
286
  fields: {
282
287
  titleTag: { type: "select", label: "SEO Tag", options: tagOptions },
@@ -310,7 +315,8 @@ export const DEFAULT_SECTIONS = {
310
315
  paddingTop: { type: "select", label: "Padding Top", options: paddingOptions },
311
316
  paddingBottom: { type: "select", label: "Padding Bottom", options: paddingOptions },
312
317
  marginTop: { type: "select", label: "Margin Top", options: marginOptions },
313
- marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions }
318
+ marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions },
319
+ paddingX: { type: "select", label: "Padding X", options: marginOptions }
314
320
  }
315
321
  },
316
322
  TextVisual: {
@@ -339,7 +345,8 @@ export const DEFAULT_SECTIONS = {
339
345
  paddingTop: "",
340
346
  paddingBottom: "",
341
347
  marginTop: "",
342
- marginBottom: ""
348
+ marginBottom: "",
349
+ paddingX: ""
343
350
  },
344
351
  fields: {
345
352
  layout: { type: "cards", label: "Layout", options: [
@@ -378,7 +385,8 @@ export const DEFAULT_SECTIONS = {
378
385
  paddingTop: { type: "select", label: "Padding Top", options: paddingOptions },
379
386
  paddingBottom: { type: "select", label: "Padding Bottom", options: paddingOptions },
380
387
  marginTop: { type: "select", label: "Margin Top", options: marginOptions },
381
- marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions }
388
+ marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions },
389
+ paddingX: { type: "select", label: "Padding X", options: marginOptions }
382
390
  }
383
391
  },
384
392
  ServiceGrid: {
@@ -404,7 +412,8 @@ export const DEFAULT_SECTIONS = {
404
412
  paddingTop: "",
405
413
  paddingBottom: "",
406
414
  marginTop: "",
407
- marginBottom: ""
415
+ marginBottom: "",
416
+ paddingX: ""
408
417
  },
409
418
  fields: {
410
419
  layout: { type: "cards", label: "Layout", options: [
@@ -436,7 +445,8 @@ export const DEFAULT_SECTIONS = {
436
445
  paddingTop: { type: "select", label: "Padding Top", options: paddingOptions },
437
446
  paddingBottom: { type: "select", label: "Padding Bottom", options: paddingOptions },
438
447
  marginTop: { type: "select", label: "Margin Top", options: marginOptions },
439
- marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions }
448
+ marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions },
449
+ paddingX: { type: "select", label: "Padding X", options: marginOptions }
440
450
  }
441
451
  },
442
452
  Features: {
@@ -455,7 +465,8 @@ export const DEFAULT_SECTIONS = {
455
465
  paddingTop: "",
456
466
  paddingBottom: "",
457
467
  marginTop: "",
458
- marginBottom: ""
468
+ marginBottom: "",
469
+ paddingX: ""
459
470
  },
460
471
  fields: {
461
472
  title: { type: "text", label: "Section Title", i18n: true },
@@ -473,7 +484,8 @@ export const DEFAULT_SECTIONS = {
473
484
  paddingTop: { type: "select", label: "Padding Top", options: paddingOptions },
474
485
  paddingBottom: { type: "select", label: "Padding Bottom", options: paddingOptions },
475
486
  marginTop: { type: "select", label: "Margin Top", options: marginOptions },
476
- marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions }
487
+ marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions },
488
+ paddingX: { type: "select", label: "Padding X", options: marginOptions }
477
489
  }
478
490
  },
479
491
  FAQ: {
@@ -492,7 +504,8 @@ export const DEFAULT_SECTIONS = {
492
504
  paddingTop: "",
493
505
  paddingBottom: "",
494
506
  marginTop: "",
495
- marginBottom: ""
507
+ marginBottom: "",
508
+ paddingX: ""
496
509
  },
497
510
  fields: {
498
511
  title: { type: "text", label: "Title", i18n: true },
@@ -510,7 +523,8 @@ export const DEFAULT_SECTIONS = {
510
523
  paddingTop: { type: "select", label: "Padding Top", options: paddingOptions },
511
524
  paddingBottom: { type: "select", label: "Padding Bottom", options: paddingOptions },
512
525
  marginTop: { type: "select", label: "Margin Top", options: marginOptions },
513
- marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions }
526
+ marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions },
527
+ paddingX: { type: "select", label: "Padding X", options: marginOptions }
514
528
  }
515
529
  },
516
530
  Process: {
@@ -533,7 +547,8 @@ export const DEFAULT_SECTIONS = {
533
547
  paddingTop: "",
534
548
  paddingBottom: "",
535
549
  marginTop: "",
536
- marginBottom: ""
550
+ marginBottom: "",
551
+ paddingX: ""
537
552
  },
538
553
  fields: {
539
554
  layout: { type: "cards", label: "Layout", options: [
@@ -562,7 +577,8 @@ export const DEFAULT_SECTIONS = {
562
577
  paddingTop: { type: "select", label: "Padding Top", options: paddingOptions },
563
578
  paddingBottom: { type: "select", label: "Padding Bottom", options: paddingOptions },
564
579
  marginTop: { type: "select", label: "Margin Top", options: marginOptions },
565
- marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions }
580
+ marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions },
581
+ paddingX: { type: "select", label: "Padding X", options: marginOptions }
566
582
  }
567
583
  },
568
584
  Testimonials: {
@@ -585,7 +601,8 @@ export const DEFAULT_SECTIONS = {
585
601
  paddingTop: "",
586
602
  paddingBottom: "",
587
603
  marginTop: "",
588
- marginBottom: ""
604
+ marginBottom: "",
605
+ paddingX: ""
589
606
  },
590
607
  fields: {
591
608
  layout: { type: "cards", label: "Layout", options: [
@@ -616,7 +633,8 @@ export const DEFAULT_SECTIONS = {
616
633
  paddingTop: { type: "select", label: "Padding Top", options: paddingOptions },
617
634
  paddingBottom: { type: "select", label: "Padding Bottom", options: paddingOptions },
618
635
  marginTop: { type: "select", label: "Margin Top", options: marginOptions },
619
- marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions }
636
+ marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions },
637
+ paddingX: { type: "select", label: "Padding X", options: marginOptions }
620
638
  }
621
639
  },
622
640
  Pricing: {
@@ -639,7 +657,8 @@ export const DEFAULT_SECTIONS = {
639
657
  paddingTop: "",
640
658
  paddingBottom: "",
641
659
  marginTop: "",
642
- marginBottom: ""
660
+ marginBottom: "",
661
+ paddingX: ""
643
662
  },
644
663
  fields: {
645
664
  layout: { type: "cards", label: "Layout", options: [
@@ -670,7 +689,8 @@ export const DEFAULT_SECTIONS = {
670
689
  paddingTop: { type: "select", label: "Padding Top", options: paddingOptions },
671
690
  paddingBottom: { type: "select", label: "Padding Bottom", options: paddingOptions },
672
691
  marginTop: { type: "select", label: "Margin Top", options: marginOptions },
673
- marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions }
692
+ marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions },
693
+ paddingX: { type: "select", label: "Padding X", options: marginOptions }
674
694
  }
675
695
  },
676
696
  ContactForm: {
@@ -692,7 +712,8 @@ export const DEFAULT_SECTIONS = {
692
712
  paddingTop: "",
693
713
  paddingBottom: "",
694
714
  marginTop: "",
695
- marginBottom: ""
715
+ marginBottom: "",
716
+ paddingX: ""
696
717
  },
697
718
  fields: {
698
719
  layout: { type: "cards", label: "Layout", options: [
@@ -714,7 +735,8 @@ export const DEFAULT_SECTIONS = {
714
735
  paddingTop: { type: "select", label: "Padding Top", options: paddingOptions },
715
736
  paddingBottom: { type: "select", label: "Padding Bottom", options: paddingOptions },
716
737
  marginTop: { type: "select", label: "Margin Top", options: marginOptions },
717
- marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions }
738
+ marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions },
739
+ paddingX: { type: "select", label: "Padding X", options: marginOptions }
718
740
  }
719
741
  },
720
742
  // ── Blog sections ─────────────────────────────────────────────────────────
@@ -743,7 +765,8 @@ export const DEFAULT_SECTIONS = {
743
765
  paddingTop: "",
744
766
  paddingBottom: "",
745
767
  marginTop: "",
746
- marginBottom: ""
768
+ marginBottom: "",
769
+ paddingX: ""
747
770
  },
748
771
  fields: {
749
772
  layout: { type: "cards", label: "Layout", options: [
@@ -775,7 +798,8 @@ export const DEFAULT_SECTIONS = {
775
798
  paddingTop: { type: "select", label: "Padding Top", options: paddingOptions },
776
799
  paddingBottom: { type: "select", label: "Padding Bottom", options: paddingOptions },
777
800
  marginTop: { type: "select", label: "Margin Top", options: marginOptions },
778
- marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions }
801
+ marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions },
802
+ paddingX: { type: "select", label: "Padding X", options: marginOptions }
779
803
  }
780
804
  },
781
805
  RichText: {
@@ -798,7 +822,8 @@ export const DEFAULT_SECTIONS = {
798
822
  paddingTop: "",
799
823
  paddingBottom: "",
800
824
  marginTop: "",
801
- marginBottom: ""
825
+ marginBottom: "",
826
+ paddingX: ""
802
827
  },
803
828
  fields: {
804
829
  layout: { type: "cards", label: "Width", options: [
@@ -826,7 +851,8 @@ export const DEFAULT_SECTIONS = {
826
851
  paddingTop: { type: "select", label: "Padding Top", options: paddingOptions },
827
852
  paddingBottom: { type: "select", label: "Padding Bottom", options: paddingOptions },
828
853
  marginTop: { type: "select", label: "Margin Top", options: marginOptions },
829
- marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions }
854
+ marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions },
855
+ paddingX: { type: "select", label: "Padding X", options: marginOptions }
830
856
  }
831
857
  },
832
858
  CallToAction: {
@@ -851,7 +877,8 @@ export const DEFAULT_SECTIONS = {
851
877
  paddingTop: "",
852
878
  paddingBottom: "",
853
879
  marginTop: "",
854
- marginBottom: ""
880
+ marginBottom: "",
881
+ paddingX: ""
855
882
  },
856
883
  fields: {
857
884
  layout: { type: "cards", label: "Layout", options: [
@@ -876,7 +903,8 @@ export const DEFAULT_SECTIONS = {
876
903
  paddingTop: { type: "select", label: "Padding Top", options: paddingOptions },
877
904
  paddingBottom: { type: "select", label: "Padding Bottom", options: paddingOptions },
878
905
  marginTop: { type: "select", label: "Margin Top", options: marginOptions },
879
- marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions }
906
+ marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions },
907
+ paddingX: { type: "select", label: "Padding X", options: marginOptions }
880
908
  }
881
909
  },
882
910
  Quote: {
@@ -897,7 +925,8 @@ export const DEFAULT_SECTIONS = {
897
925
  paddingTop: "",
898
926
  paddingBottom: "",
899
927
  marginTop: "",
900
- marginBottom: ""
928
+ marginBottom: "",
929
+ paddingX: ""
901
930
  },
902
931
  fields: {
903
932
  layout: { type: "cards", label: "Layout", options: [
@@ -918,7 +947,8 @@ export const DEFAULT_SECTIONS = {
918
947
  paddingTop: { type: "select", label: "Padding Top", options: paddingOptions },
919
948
  paddingBottom: { type: "select", label: "Padding Bottom", options: paddingOptions },
920
949
  marginTop: { type: "select", label: "Margin Top", options: marginOptions },
921
- marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions }
950
+ marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions },
951
+ paddingX: { type: "select", label: "Padding X", options: marginOptions }
922
952
  }
923
953
  },
924
954
  DataTable: {
@@ -960,7 +990,8 @@ export const DEFAULT_SECTIONS = {
960
990
  paddingTop: "",
961
991
  paddingBottom: "",
962
992
  marginTop: "",
963
- marginBottom: ""
993
+ marginBottom: "",
994
+ paddingX: ""
964
995
  },
965
996
  fields: {
966
997
  layout: { type: "cards", label: "Layout", options: [
@@ -1006,7 +1037,8 @@ export const DEFAULT_SECTIONS = {
1006
1037
  paddingTop: { type: "select", label: "Padding Top", options: paddingOptions },
1007
1038
  paddingBottom: { type: "select", label: "Padding Bottom", options: paddingOptions },
1008
1039
  marginTop: { type: "select", label: "Margin Top", options: marginOptions },
1009
- marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions }
1040
+ marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions },
1041
+ paddingX: { type: "select", label: "Padding X", options: marginOptions }
1010
1042
  }
1011
1043
  },
1012
1044
  Comparison: {
@@ -1036,7 +1068,8 @@ export const DEFAULT_SECTIONS = {
1036
1068
  paddingTop: "",
1037
1069
  paddingBottom: "",
1038
1070
  marginTop: "",
1039
- marginBottom: ""
1071
+ marginBottom: "",
1072
+ paddingX: ""
1040
1073
  },
1041
1074
  fields: {
1042
1075
  layout: { type: "cards", label: "Layout", options: [
@@ -1075,7 +1108,8 @@ export const DEFAULT_SECTIONS = {
1075
1108
  paddingTop: { type: "select", label: "Padding Top", options: paddingOptions },
1076
1109
  paddingBottom: { type: "select", label: "Padding Bottom", options: paddingOptions },
1077
1110
  marginTop: { type: "select", label: "Margin Top", options: marginOptions },
1078
- marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions }
1111
+ marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions },
1112
+ paddingX: { type: "select", label: "Padding X", options: marginOptions }
1079
1113
  }
1080
1114
  },
1081
1115
  RelatedPages: {
@@ -1101,7 +1135,8 @@ export const DEFAULT_SECTIONS = {
1101
1135
  paddingTop: "",
1102
1136
  paddingBottom: "",
1103
1137
  marginTop: "",
1104
- marginBottom: ""
1138
+ marginBottom: "",
1139
+ paddingX: ""
1105
1140
  },
1106
1141
  fields: {
1107
1142
  layout: { type: "cards", label: "Layout", options: [
@@ -1130,7 +1165,8 @@ export const DEFAULT_SECTIONS = {
1130
1165
  paddingTop: { type: "select", label: "Padding Top", options: paddingOptions },
1131
1166
  paddingBottom: { type: "select", label: "Padding Bottom", options: paddingOptions },
1132
1167
  marginTop: { type: "select", label: "Margin Top", options: marginOptions },
1133
- marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions }
1168
+ marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions },
1169
+ paddingX: { type: "select", label: "Padding X", options: marginOptions }
1134
1170
  }
1135
1171
  },
1136
1172
  Newsletter: {
@@ -1154,7 +1190,8 @@ export const DEFAULT_SECTIONS = {
1154
1190
  paddingTop: "",
1155
1191
  paddingBottom: "",
1156
1192
  marginTop: "",
1157
- marginBottom: ""
1193
+ marginBottom: "",
1194
+ paddingX: ""
1158
1195
  },
1159
1196
  fields: {
1160
1197
  layout: { type: "cards", label: "Layout", options: [
@@ -1178,7 +1215,8 @@ export const DEFAULT_SECTIONS = {
1178
1215
  paddingTop: { type: "select", label: "Padding Top", options: paddingOptions },
1179
1216
  paddingBottom: { type: "select", label: "Padding Bottom", options: paddingOptions },
1180
1217
  marginTop: { type: "select", label: "Margin Top", options: marginOptions },
1181
- marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions }
1218
+ marginBottom: { type: "select", label: "Margin Bottom", options: marginOptions },
1219
+ paddingX: { type: "select", label: "Padding X", options: marginOptions }
1182
1220
  }
1183
1221
  }
1184
1222
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cinqcinqdev-seo",
3
- "version": "0.1.71",
3
+ "version": "0.1.72",
4
4
  "description": "A reusable Nuxt 3 admin CMS module with visual page editor powered by Supabase",
5
5
  "license": "MIT",
6
6
  "module": "./dist/module.mjs",