cinqcinqdev-seo 0.1.14 → 0.1.16

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 (54) hide show
  1. package/README.md +554 -113
  2. package/dist/module.d.mts +6 -0
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +14 -6
  5. package/dist/runtime/assets/admin-tw.css +1 -1
  6. package/dist/runtime/components/admin/Navbar.vue +6 -5
  7. package/dist/runtime/components/admin/Sidebar.vue +21 -8
  8. package/dist/runtime/components/sections/ContactForm.d.vue.ts +10 -0
  9. package/dist/runtime/components/sections/ContactForm.vue +138 -0
  10. package/dist/runtime/components/sections/ContactForm.vue.d.ts +10 -0
  11. package/dist/runtime/components/sections/FAQ.d.vue.ts +3 -0
  12. package/dist/runtime/components/sections/FAQ.vue +46 -0
  13. package/dist/runtime/components/sections/FAQ.vue.d.ts +3 -0
  14. package/dist/runtime/components/sections/Features.d.vue.ts +3 -0
  15. package/dist/runtime/components/sections/Features.vue +41 -0
  16. package/dist/runtime/components/sections/Features.vue.d.ts +3 -0
  17. package/dist/runtime/components/sections/HeroSection.d.vue.ts +3 -0
  18. package/dist/runtime/components/sections/HeroSection.vue +133 -0
  19. package/dist/runtime/components/sections/HeroSection.vue.d.ts +3 -0
  20. package/dist/runtime/components/sections/Pricing.d.vue.ts +3 -0
  21. package/dist/runtime/components/sections/Pricing.vue +108 -0
  22. package/dist/runtime/components/sections/Pricing.vue.d.ts +3 -0
  23. package/dist/runtime/components/sections/Process.d.vue.ts +3 -0
  24. package/dist/runtime/components/sections/Process.vue +95 -0
  25. package/dist/runtime/components/sections/Process.vue.d.ts +3 -0
  26. package/dist/runtime/components/sections/ServiceGrid.d.vue.ts +3 -0
  27. package/dist/runtime/components/sections/ServiceGrid.vue +97 -0
  28. package/dist/runtime/components/sections/ServiceGrid.vue.d.ts +3 -0
  29. package/dist/runtime/components/sections/Testimonials.d.vue.ts +3 -0
  30. package/dist/runtime/components/sections/Testimonials.vue +95 -0
  31. package/dist/runtime/components/sections/Testimonials.vue.d.ts +3 -0
  32. package/dist/runtime/components/sections/TextVisual.d.vue.ts +3 -0
  33. package/dist/runtime/components/sections/TextVisual.vue +94 -0
  34. package/dist/runtime/components/sections/TextVisual.vue.d.ts +3 -0
  35. package/dist/runtime/composables/useAdminBasePath.d.ts +1 -0
  36. package/dist/runtime/composables/useAdminBasePath.js +5 -0
  37. package/dist/runtime/composables/useAdminSections.d.ts +0 -7
  38. package/dist/runtime/composables/useAdminSections.js +68 -17
  39. package/dist/runtime/composables/useAdminSettings.d.ts +31 -0
  40. package/dist/runtime/composables/useAdminSettings.js +31 -0
  41. package/dist/runtime/middleware/admin-auth.js +3 -2
  42. package/dist/runtime/pages/admin/editor/[id].vue +10 -5
  43. package/dist/runtime/pages/admin/index.vue +3 -1
  44. package/dist/runtime/pages/admin/pages/[type].vue +5 -3
  45. package/dist/runtime/pages/admin/personas/[id].vue +3 -2
  46. package/dist/runtime/pages/admin/personas/index.vue +3 -2
  47. package/dist/runtime/pages/admin/setup.d.vue.ts +3 -0
  48. package/dist/runtime/pages/admin/setup.vue +220 -0
  49. package/dist/runtime/pages/admin/setup.vue.d.ts +3 -0
  50. package/dist/runtime/pages/admin/strategies/[id].vue +3 -2
  51. package/dist/runtime/pages/admin/strategies/index.vue +3 -2
  52. package/dist/runtime/server/api/ai/generate.post.js +8 -3
  53. package/dist/runtime/server/api/ai/seo-audit.post.js +8 -3
  54. package/package.json +1 -1
@@ -0,0 +1,46 @@
1
+ <template>
2
+ <section :class="[sectionPad]" :style="{ backgroundColor: bgColor, color: textColor }">
3
+ <div class="max-w-3xl mx-auto">
4
+ <div class="text-center mb-16">
5
+ <component :is="titleTag" class="text-4xl md:text-6xl font-black uppercase tracking-tighter italic">{{ title }}</component>
6
+ </div>
7
+ <div class="space-y-3">
8
+ <div v-for="(item, index) in items" :key="index"
9
+ :class="['overflow-hidden border transition-all duration-300', cardRound]"
10
+ :style="{ backgroundColor: textColor + '04', borderColor: textColor + '10' }">
11
+ <button @click="toggle(index)" class="w-full flex items-center justify-between p-7 text-left cursor-pointer">
12
+ <span class="text-sm md:text-base font-black uppercase tracking-tight pr-8 leading-snug">{{ item.question }}</span>
13
+ <span class="text-2xl font-light shrink-0 transition-transform duration-300" :style="{ transform: openIndex === index ? 'rotate(45deg)' : 'none' }">+</span>
14
+ </button>
15
+ <div v-if="openIndex === index" class="px-7 pb-7">
16
+ <p class="text-sm leading-relaxed" :style="{ opacity: 0.65 }">{{ item.answer }}</p>
17
+ </div>
18
+ </div>
19
+ <div v-if="!items.length" v-for="n in 3" :key="n"
20
+ :class="['border p-7', cardRound]"
21
+ :style="{ backgroundColor: textColor + '04', borderColor: textColor + '10' }">
22
+ <div class="h-4 rounded" :style="{ backgroundColor: textColor + '15', width: n === 1 ? '70%' : n === 2 ? '55%' : '65%' }"></div>
23
+ </div>
24
+ </div>
25
+ </div>
26
+ </section>
27
+ </template>
28
+
29
+ <script setup>
30
+ import { ref, computed } from "vue";
31
+ const props = defineProps({
32
+ title: { type: String, default: "FAQ" },
33
+ titleTag: { type: String, default: "h2" },
34
+ items: { type: Array, default: () => [] },
35
+ bgColor: { type: String, default: "#F9F9FB" },
36
+ textColor: { type: String, default: "#000000" },
37
+ spacing: { type: String, default: "normal" },
38
+ radius: { type: String, default: "rounded" }
39
+ });
40
+ 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");
41
+ const cardRound = computed(() => ({ sharp: "rounded-xl", rounded: "rounded-[24px]", pill: "rounded-[36px]" })[props.radius] || "rounded-[24px]");
42
+ const openIndex = ref(null);
43
+ const toggle = (index) => {
44
+ openIndex.value = openIndex.value === index ? null : index;
45
+ };
46
+ </script>
@@ -0,0 +1,3 @@
1
+ declare const _default: typeof __VLS_export;
2
+ export default _default;
3
+ declare const __VLS_export: any;
@@ -0,0 +1,3 @@
1
+ declare const _default: typeof __VLS_export;
2
+ export default _default;
3
+ declare const __VLS_export: any;
@@ -0,0 +1,41 @@
1
+ <template>
2
+ <section :class="[sectionPad, 'overflow-hidden']" :style="{ backgroundColor: bgColor, color: textColor }">
3
+ <div class="max-w-6xl mx-auto">
4
+ <div class="mb-16 md:mb-20">
5
+ <component :is="titleTag" class="text-4xl md:text-6xl font-black uppercase tracking-tighter leading-none max-w-4xl italic">{{ title }}</component>
6
+ </div>
7
+ <div v-if="items.length" :class="['grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-px overflow-hidden', cardRound]" :style="{ backgroundColor: textColor + '10', border: `1px solid ${textColor}10` }">
8
+ <div v-for="(item, index) in items" :key="index" class="p-10 transition-all duration-300 hover:brightness-95" :style="{ backgroundColor: bgColor }">
9
+ <div class="flex flex-col h-full">
10
+ <span class="text-[10px] font-black opacity-20 mb-8 tracking-widest">{{ index < 9 ? '0' : '' }}{{ index + 1 }}</span>
11
+ <component :is="itemTag" class="text-xl font-black uppercase tracking-tight mb-4 leading-tight">{{ item.title }}</component>
12
+ <p class="text-sm leading-relaxed" :style="{ opacity: 0.6 }">{{ item.description || item.text }}</p>
13
+ </div>
14
+ </div>
15
+ </div>
16
+ <div v-else :class="['grid grid-cols-1 md:grid-cols-3 gap-px overflow-hidden', cardRound]" :style="{ backgroundColor: textColor + '10', border: `1px solid ${textColor}10` }">
17
+ <div v-for="n in 3" :key="n" class="p-10" :style="{ backgroundColor: bgColor }">
18
+ <span class="text-[10px] font-black opacity-20 mb-8 tracking-widest block">0{{ n }}</span>
19
+ <div class="h-4 rounded mb-3" :style="{ backgroundColor: textColor + '12', width: '60%' }"></div>
20
+ <div class="h-3 rounded" :style="{ backgroundColor: textColor + '08', width: '80%' }"></div>
21
+ </div>
22
+ </div>
23
+ </div>
24
+ </section>
25
+ </template>
26
+
27
+ <script setup>
28
+ import { computed } from "vue";
29
+ const props = defineProps({
30
+ title: { type: String, default: "Why choose us?" },
31
+ titleTag: { type: String, default: "h2" },
32
+ itemTag: { type: String, default: "h3" },
33
+ items: { type: Array, default: () => [] },
34
+ bgColor: { type: String, default: "#ffffff" },
35
+ textColor: { type: String, default: "#000000" },
36
+ spacing: { type: String, default: "normal" },
37
+ radius: { type: String, default: "rounded" }
38
+ });
39
+ 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");
40
+ const cardRound = computed(() => ({ sharp: "rounded-xl", rounded: "rounded-[32px]", pill: "rounded-[48px]" })[props.radius] || "rounded-[32px]");
41
+ </script>
@@ -0,0 +1,3 @@
1
+ declare const _default: typeof __VLS_export;
2
+ export default _default;
3
+ declare const __VLS_export: any;
@@ -0,0 +1,3 @@
1
+ declare const _default: typeof __VLS_export;
2
+ export default _default;
3
+ declare const __VLS_export: any;
@@ -0,0 +1,133 @@
1
+ <template>
2
+ <section class="relative overflow-hidden" :style="{ backgroundColor: bgColor, color: textColor }">
3
+
4
+ <!-- CENTERED -->
5
+ <div v-if="layout === 'centered'" class="px-6 md:px-12 pt-20 pb-16 text-center">
6
+ <div class="max-w-4xl mx-auto">
7
+ <span v-if="badge" class="inline-flex items-center gap-1.5 text-[10px] font-bold tracking-[0.4em] uppercase px-4 py-1.5 rounded-full border mb-8" :style="{ borderColor: textColor + '30', color: textColor + '80' }">
8
+ <span class="w-1.5 h-1.5 rounded-full inline-block" :style="{ backgroundColor: textColor + '60' }"></span>{{ badge }}
9
+ </span>
10
+ <component :is="titleTag" class="font-black uppercase tracking-tighter leading-[0.88] mb-6" :style="{ fontSize: titleSize === 'sm' ? 'clamp(2.5rem,5vw,4rem)' : titleSize === 'lg' ? 'clamp(3.5rem,9vw,8rem)' : 'clamp(3rem,7vw,6rem)' }">{{ title }}</component>
11
+ <p class="text-sm leading-relaxed max-w-lg mx-auto mb-10" :style="{ opacity: 0.5 }">{{ subtitle }}</p>
12
+ <NuxtLink :to="ctaLink || '#'" class="inline-flex items-center gap-2.5 px-8 py-3.5 rounded-full text-[11px] font-black uppercase tracking-widest transition-all hover:gap-5 active:scale-95" :style="{ backgroundColor: textColor, color: bgColor }">{{ ctaText }}<span>→</span></NuxtLink>
13
+ <div v-if="image" :class="['mt-14 mx-auto aspect-video overflow-hidden max-w-3xl', cardRound]">
14
+ <img :src="image" class="w-full h-full object-cover" />
15
+ </div>
16
+ </div>
17
+ </div>
18
+
19
+ <!-- SPLIT -->
20
+ <div v-else-if="layout === 'split'" class="px-6 md:px-12 py-16">
21
+ <div class="max-w-6xl mx-auto grid md:grid-cols-2 gap-12 md:gap-20 items-center">
22
+ <div>
23
+ <span v-if="badge" class="inline-block text-[9px] font-black tracking-[0.5em] uppercase mb-6 opacity-35">{{ badge }}</span>
24
+ <component :is="titleTag" class="font-black uppercase tracking-tighter leading-[0.88] mb-6" :style="{ fontSize: 'clamp(2.2rem,5vw,4.5rem)' }">{{ title }}</component>
25
+ <p class="text-sm leading-relaxed mb-10 max-w-sm" :style="{ opacity: 0.5 }">{{ subtitle }}</p>
26
+ <NuxtLink :to="ctaLink || '#'" class="inline-flex items-center gap-2.5 px-7 py-3 rounded-full text-[11px] font-black uppercase tracking-widest transition-all hover:gap-5 active:scale-95" :style="{ backgroundColor: textColor, color: bgColor }">{{ ctaText }}<span>→</span></NuxtLink>
27
+ </div>
28
+ <div :class="['aspect-[4/3] overflow-hidden', cardRound]">
29
+ <img v-if="image" :src="image" class="w-full h-full object-cover" />
30
+ <div v-else class="w-full h-full" :style="{ backgroundColor: textColor + '06' }"></div>
31
+ </div>
32
+ </div>
33
+ </div>
34
+
35
+ <!-- BENTO -->
36
+ <div v-else-if="layout === 'bento'" class="p-3 md:p-5">
37
+ <div class="max-w-6xl mx-auto grid grid-cols-12 gap-3">
38
+ <div :class="['col-span-12 md:col-span-7 p-8 md:p-10 border flex flex-col justify-between min-h-[320px]', cardRound]" :style="{ borderColor: textColor + '10', backgroundColor: textColor + '04' }">
39
+ <span v-if="badge" class="text-[9px] font-black uppercase tracking-[0.4em] opacity-30">{{ badge }}</span>
40
+ <div>
41
+ <component :is="titleTag" class="font-black uppercase tracking-tighter leading-[0.88] mb-4" :style="{ fontSize: 'clamp(2rem,4.5vw,3.8rem)' }">{{ title }}</component>
42
+ <p class="text-sm leading-relaxed mb-8 max-w-xs" :style="{ opacity: 0.45 }">{{ subtitle }}</p>
43
+ <NuxtLink :to="ctaLink || '#'" class="inline-flex items-center gap-2.5 px-6 py-3 rounded-full text-[10px] font-black uppercase tracking-widest transition-all hover:gap-5 active:scale-95" :style="{ backgroundColor: textColor, color: bgColor }">{{ ctaText }}<span>→</span></NuxtLink>
44
+ </div>
45
+ </div>
46
+ <div :class="['col-span-12 md:col-span-5 overflow-hidden min-h-[260px] md:min-h-[320px]', cardRound]">
47
+ <img v-if="image" :src="image" class="w-full h-full object-cover" />
48
+ <div v-else class="w-full h-full" :style="{ backgroundColor: textColor + '08' }"></div>
49
+ </div>
50
+ </div>
51
+ </div>
52
+
53
+ <!-- FULLSCREEN -->
54
+ <div v-else-if="layout === 'fullscreen'" class="relative min-h-[88vh] flex flex-col justify-end">
55
+ <div class="absolute inset-0">
56
+ <img v-if="image" :src="image" class="w-full h-full object-cover" />
57
+ <div v-else class="w-full h-full" :style="{ backgroundColor: textColor + '08' }"></div>
58
+ <div class="absolute inset-0" :style="{ background: `linear-gradient(to top, ${bgColor} 20%, ${bgColor}cc 40%, ${bgColor}44 65%, transparent 100%)` }"></div>
59
+ </div>
60
+ <div class="relative z-10 max-w-6xl mx-auto w-full px-6 md:px-12 pb-14">
61
+ <span v-if="badge" class="inline-block text-[9px] font-black uppercase tracking-[0.5em] opacity-40 mb-4">{{ badge }}</span>
62
+ <component :is="titleTag" class="font-black uppercase tracking-tighter leading-[0.88] mb-6" :style="{ fontSize: 'clamp(3rem,8vw,7rem)' }">{{ title }}</component>
63
+ <div class="flex flex-col sm:flex-row sm:items-center gap-6 sm:gap-10">
64
+ <p class="text-sm leading-relaxed max-w-xs" :style="{ opacity: 0.6 }">{{ subtitle }}</p>
65
+ <NuxtLink :to="ctaLink || '#'" class="inline-flex items-center gap-2.5 px-7 py-3 rounded-full text-[11px] font-black uppercase tracking-widest border transition-all hover:gap-5 shrink-0" :style="{ borderColor: textColor + '50', color: textColor }">{{ ctaText }}<span>→</span></NuxtLink>
66
+ </div>
67
+ </div>
68
+ </div>
69
+
70
+ <!-- MINIMAL -->
71
+ <div v-else-if="layout === 'minimal'" class="px-6 md:px-12 py-14">
72
+ <div class="max-w-6xl mx-auto">
73
+ <div class="flex items-center gap-4 mb-10 border-b pb-5" :style="{ borderColor: textColor + '10' }">
74
+ <span v-if="badge" class="text-[9px] font-black uppercase tracking-[0.5em] opacity-30">{{ badge }}</span>
75
+ <div class="flex-1 h-px" :style="{ backgroundColor: textColor + '08' }"></div>
76
+ <span class="text-[9px] font-black uppercase tracking-[0.5em] opacity-20">{{ ctaText }}</span>
77
+ </div>
78
+ <component :is="titleTag" class="font-black uppercase tracking-tighter leading-[0.85] mb-10" :style="{ fontSize: 'clamp(3.5rem,10vw,9rem)' }">{{ title }}</component>
79
+ <div v-if="image" :class="['mb-10 overflow-hidden aspect-video', cardRound]">
80
+ <img :src="image" class="w-full h-full object-cover" />
81
+ </div>
82
+ <div class="flex flex-col md:flex-row items-start md:items-end justify-between gap-6 border-t pt-8" :style="{ borderColor: textColor + '10' }">
83
+ <p class="text-sm leading-relaxed max-w-xs" :style="{ opacity: 0.5 }">{{ subtitle }}</p>
84
+ <NuxtLink :to="ctaLink || '#'" class="inline-flex items-center gap-2.5 text-[11px] font-black uppercase tracking-widest transition-all hover:gap-5 shrink-0">{{ ctaText }}<span>→</span></NuxtLink>
85
+ </div>
86
+ </div>
87
+ </div>
88
+
89
+ <!-- EDITORIAL -->
90
+ <div v-else class="px-6 md:px-12 py-14">
91
+ <div class="max-w-6xl mx-auto">
92
+ <div class="flex items-center gap-4 mb-8">
93
+ <div class="w-8 h-px" :style="{ backgroundColor: textColor + '30' }"></div>
94
+ <span class="text-[9px] font-black uppercase tracking-[0.5em] opacity-30">{{ badge || '—' }}</span>
95
+ </div>
96
+ <div class="flex flex-col md:flex-row md:items-end gap-8 mb-8">
97
+ <component :is="titleTag" class="font-black uppercase tracking-tighter leading-[0.85] flex-1" :style="{ fontSize: 'clamp(3rem,9vw,9rem)' }">{{ title }}</component>
98
+ <div v-if="image" :class="['md:w-56 md:shrink-0 aspect-[3/4] overflow-hidden', cardRound]">
99
+ <img :src="image" class="w-full h-full object-cover" />
100
+ </div>
101
+ </div>
102
+ <div class="flex flex-col md:flex-row items-start md:items-center justify-between gap-6 border-t pt-8" :style="{ borderColor: textColor + '10' }">
103
+ <p class="text-sm leading-relaxed max-w-sm" :style="{ opacity: 0.5 }">{{ subtitle }}</p>
104
+ <NuxtLink :to="ctaLink || '#'" class="inline-flex items-center gap-2.5 px-7 py-3 rounded-full text-[11px] font-black uppercase tracking-widest transition-all hover:gap-5 shrink-0 active:scale-95" :style="{ backgroundColor: textColor, color: bgColor }">{{ ctaText }}<span>→</span></NuxtLink>
105
+ </div>
106
+ </div>
107
+ </div>
108
+
109
+ </section>
110
+ </template>
111
+
112
+ <script setup>
113
+ import { computed } from "vue";
114
+ const props = defineProps({
115
+ layout: { type: String, default: "centered" },
116
+ animation: { type: String, default: "fadeUp" },
117
+ titleSize: { type: String, default: "md" },
118
+ title: { type: String, default: "Impactful Title" },
119
+ titleTag: { type: String, default: "h1" },
120
+ subtitle: { type: String, default: "" },
121
+ badge: { type: String, default: "" },
122
+ ctaText: { type: String, default: "Get Started" },
123
+ ctaLink: { type: String, default: "#" },
124
+ image: { type: String, default: "" },
125
+ imageStyle: { type: String, default: "cover" },
126
+ imagePosition: { type: String, default: "center" },
127
+ bgColor: { type: String, default: "#3d35ff" },
128
+ textColor: { type: String, default: "#ffffff" },
129
+ spacing: { type: String, default: "normal" },
130
+ radius: { type: String, default: "rounded" }
131
+ });
132
+ const cardRound = computed(() => ({ sharp: "rounded-xl", rounded: "rounded-3xl", pill: "rounded-[40px]" })[props.radius] || "rounded-3xl");
133
+ </script>
@@ -0,0 +1,3 @@
1
+ declare const _default: typeof __VLS_export;
2
+ export default _default;
3
+ declare const __VLS_export: any;
@@ -0,0 +1,3 @@
1
+ declare const _default: typeof __VLS_export;
2
+ export default _default;
3
+ declare const __VLS_export: any;
@@ -0,0 +1,108 @@
1
+ <template>
2
+ <section :class="[sectionPad]" :style="{ backgroundColor: bgColor, color: textColor }">
3
+ <div class="max-w-6xl mx-auto">
4
+
5
+ <div v-if="title || description" class="mb-12 text-center max-w-2xl mx-auto">
6
+ <span v-if="badge" class="inline-block text-[10px] font-bold tracking-[0.5em] uppercase px-3 py-1 rounded-full border mb-4" :style="{ borderColor: textColor + '25', color: textColor + 'aa' }">{{ badge }}</span>
7
+ <component :is="titleTag" class="text-3xl md:text-5xl font-black uppercase tracking-tighter leading-[0.9] mb-4">{{ title }}</component>
8
+ <p v-if="description" class="text-sm leading-relaxed" :style="{ opacity: 0.55 }">{{ description }}</p>
9
+ </div>
10
+
11
+ <!-- CARDS -->
12
+ <div v-if="layout === 'cards'" class="grid grid-cols-1 md:grid-cols-3 gap-6 items-end">
13
+ <div v-for="(plan, i) in items" :key="i"
14
+ :class="['border p-8 flex flex-col transition-all duration-300 relative', cardRound, plan.highlighted ? 'md:-translate-y-4 shadow-2xl' : '']"
15
+ :style="plan.highlighted ? { backgroundColor: textColor, color: bgColor, borderColor: textColor } : { backgroundColor: textColor + '04', borderColor: textColor + '12' }">
16
+ <div v-if="plan.highlighted" class="absolute -top-3 left-1/2 -translate-x-1/2">
17
+ <span class="px-4 py-1 rounded-full text-[9px] font-black uppercase tracking-widest" :style="{ backgroundColor: bgColor, color: textColor }">Populaire</span>
18
+ </div>
19
+ <p class="text-[11px] font-black uppercase tracking-widest mb-4" :style="{ opacity: 0.6 }">{{ plan.name }}</p>
20
+ <div class="mb-2">
21
+ <span class="text-4xl font-black tracking-tighter">{{ plan.price }}</span>
22
+ <span v-if="plan.period" class="text-sm ml-1" :style="{ opacity: 0.5 }">{{ plan.period }}</span>
23
+ </div>
24
+ <p v-if="plan.description" class="text-xs leading-relaxed mb-6" :style="{ opacity: 0.55 }">{{ plan.description }}</p>
25
+ <ul v-if="plan.features && plan.features.length" class="space-y-3 mb-8 flex-1">
26
+ <li v-for="(feat, fi) in plan.features" :key="fi" class="flex items-start gap-2.5 text-sm">
27
+ <span class="text-[10px] mt-0.5 shrink-0">✓</span>
28
+ <span :style="{ opacity: 0.75 }">{{ typeof feat === 'object' ? (feat.fr || feat) : feat }}</span>
29
+ </li>
30
+ </ul>
31
+ <div class="flex-1"></div>
32
+ <NuxtLink v-if="plan.ctaText || plan.ctaLink" :to="plan.ctaLink || '#'"
33
+ class="block text-center py-3 px-6 rounded-full text-[11px] font-black uppercase tracking-widest transition-all hover:opacity-80 active:scale-95 mt-6"
34
+ :style="plan.highlighted ? { backgroundColor: bgColor, color: textColor } : { backgroundColor: textColor, color: bgColor }">
35
+ {{ plan.ctaText || 'Get started' }}
36
+ </NuxtLink>
37
+ </div>
38
+ </div>
39
+
40
+ <!-- MINIMAL -->
41
+ <div v-else-if="layout === 'minimal'" class="divide-y max-w-2xl mx-auto" :style="{ borderColor: textColor + '12' }">
42
+ <div v-for="(plan, i) in items" :key="i" class="flex items-center gap-6 py-7" :style="{ borderColor: textColor + '12' }">
43
+ <div class="flex-1">
44
+ <p class="font-black uppercase tracking-tight text-lg leading-none mb-1">{{ plan.name }}</p>
45
+ <p v-if="plan.description" class="text-xs" :style="{ opacity: 0.5 }">{{ plan.description }}</p>
46
+ </div>
47
+ <div class="text-right shrink-0">
48
+ <p class="text-2xl font-black tracking-tighter">{{ plan.price }}</p>
49
+ <p v-if="plan.period" class="text-[10px]" :style="{ opacity: 0.4 }">{{ plan.period }}</p>
50
+ </div>
51
+ <NuxtLink v-if="plan.ctaText || plan.ctaLink" :to="plan.ctaLink || '#'"
52
+ class="px-5 py-2 rounded-full text-[10px] font-black uppercase tracking-widest shrink-0"
53
+ :style="{ backgroundColor: textColor, color: bgColor }">
54
+ {{ plan.ctaText || '→' }}
55
+ </NuxtLink>
56
+ </div>
57
+ </div>
58
+
59
+ <!-- TABLE -->
60
+ <div v-else class="overflow-x-auto">
61
+ <table class="w-full text-sm border-collapse">
62
+ <thead>
63
+ <tr class="border-b" :style="{ borderColor: textColor + '12' }">
64
+ <th class="text-left py-4 pr-6 font-black uppercase tracking-widest text-[10px]" :style="{ opacity: 0.4 }">Plan</th>
65
+ <th v-for="(plan, i) in items" :key="i" class="text-center py-4 px-4 font-black uppercase tracking-tight text-sm" :style="plan.highlighted ? { color: textColor } : { opacity: 0.7 }">
66
+ {{ plan.name }}<br/><span class="text-2xl font-black tracking-tighter">{{ plan.price }}</span>
67
+ </th>
68
+ </tr>
69
+ </thead>
70
+ <tbody>
71
+ <tr v-for="(feat, fi) in allFeatures" :key="fi" class="border-b" :style="{ borderColor: textColor + '08' }">
72
+ <td class="py-4 pr-6 text-xs" :style="{ opacity: 0.6 }">{{ typeof feat === 'object' ? (feat.fr || feat) : feat }}</td>
73
+ <td v-for="(plan, pi) in items" :key="pi" class="py-4 px-4 text-center text-sm">
74
+ <span v-if="planHasFeature(plan, feat)">✓</span>
75
+ <span v-else :style="{ opacity: 0.2 }">—</span>
76
+ </td>
77
+ </tr>
78
+ </tbody>
79
+ </table>
80
+ </div>
81
+
82
+ </div>
83
+ </section>
84
+ </template>
85
+
86
+ <script setup>
87
+ import { computed } from "vue";
88
+ const props = defineProps({
89
+ layout: { type: String, default: "cards" },
90
+ title: { type: String, default: "Our Pricing" },
91
+ titleTag: { type: String, default: "h2" },
92
+ badge: { type: String, default: "" },
93
+ description: { type: String, default: "" },
94
+ items: { type: Array, default: () => [] },
95
+ bgColor: { type: String, default: "#ffffff" },
96
+ textColor: { type: String, default: "#000000" },
97
+ spacing: { type: String, default: "normal" },
98
+ radius: { type: String, default: "rounded" }
99
+ });
100
+ 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");
101
+ const cardRound = computed(() => ({ sharp: "rounded-xl", rounded: "rounded-3xl", pill: "rounded-[40px]" })[props.radius] || "rounded-3xl");
102
+ const allFeatures = computed(() => {
103
+ const set = /* @__PURE__ */ new Set();
104
+ props.items.forEach((p) => (p.features || []).forEach((f) => set.add(typeof f === "object" ? f.fr || JSON.stringify(f) : f)));
105
+ return [...set];
106
+ });
107
+ const planHasFeature = (plan, feat) => (plan.features || []).some((f) => (typeof f === "object" ? f.fr || JSON.stringify(f) : f) === (typeof feat === "object" ? feat.fr || JSON.stringify(feat) : feat));
108
+ </script>
@@ -0,0 +1,3 @@
1
+ declare const _default: typeof __VLS_export;
2
+ export default _default;
3
+ declare const __VLS_export: any;
@@ -0,0 +1,3 @@
1
+ declare const _default: typeof __VLS_export;
2
+ export default _default;
3
+ declare const __VLS_export: any;
@@ -0,0 +1,95 @@
1
+ <template>
2
+ <section :class="[sectionPad]" :style="{ backgroundColor: bgColor, color: textColor }">
3
+ <div class="max-w-6xl mx-auto">
4
+
5
+ <div v-if="title || description" class="mb-12" :class="['horizontal','cards'].includes(layout) ? 'text-center max-w-2xl mx-auto' : 'max-w-xl'">
6
+ <span v-if="badge" class="inline-block text-[10px] font-bold tracking-[0.5em] uppercase px-3 py-1 rounded-full border mb-4" :style="{ borderColor: textColor + '25', color: textColor + 'aa' }">{{ badge }}</span>
7
+ <component :is="titleTag" class="text-3xl md:text-5xl font-black uppercase tracking-tighter leading-[0.9] mb-4">{{ title }}</component>
8
+ <p v-if="description" class="text-sm leading-relaxed" :style="{ opacity: 0.55 }">{{ description }}</p>
9
+ </div>
10
+
11
+ <!-- STEPS (vertical) -->
12
+ <div v-if="layout === 'steps'" class="relative max-w-2xl">
13
+ <div class="absolute left-5 top-0 bottom-0 w-px" :style="{ backgroundColor: textColor + '12' }"></div>
14
+ <div v-for="(step, i) in items" :key="i" class="relative flex gap-8 pb-12 last:pb-0">
15
+ <div class="relative z-10 w-10 h-10 rounded-full flex items-center justify-center text-[11px] font-black shrink-0" :style="{ backgroundColor: textColor, color: bgColor }">
16
+ {{ step.icon || String(i+1).padStart(2,'0') }}
17
+ </div>
18
+ <div class="pt-1.5 flex-1">
19
+ <component :is="itemTag" class="text-lg font-black uppercase tracking-tight mb-2 leading-tight">{{ step.title }}</component>
20
+ <p class="text-sm leading-relaxed" :style="{ opacity: 0.55 }">{{ step.description }}</p>
21
+ </div>
22
+ </div>
23
+ </div>
24
+
25
+ <!-- HORIZONTAL -->
26
+ <div v-else-if="layout === 'horizontal'" class="flex flex-col md:flex-row gap-8">
27
+ <div v-for="(step, i) in items" :key="i" class="flex-1 flex flex-col items-center text-center relative">
28
+ <div v-if="i < items.length - 1" class="hidden md:block absolute top-5 left-1/2 w-full h-px" :style="{ backgroundColor: textColor + '15' }"></div>
29
+ <div class="relative z-10 w-10 h-10 rounded-full flex items-center justify-center text-[11px] font-black mb-5 shrink-0" :style="{ backgroundColor: textColor, color: bgColor }">
30
+ {{ step.icon || String(i+1).padStart(2,'0') }}
31
+ </div>
32
+ <component :is="itemTag" class="text-sm font-black uppercase tracking-tight mb-2 leading-tight">{{ step.title }}</component>
33
+ <p class="text-xs leading-relaxed px-4" :style="{ opacity: 0.5 }">{{ step.description }}</p>
34
+ </div>
35
+ </div>
36
+
37
+ <!-- CARDS -->
38
+ <div v-else-if="layout === 'cards'" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
39
+ <div v-for="(step, i) in items" :key="i" :class="['p-7 border', cardRound]" :style="{ backgroundColor: textColor + '04', borderColor: textColor + '10' }">
40
+ <div class="w-10 h-10 rounded-full flex items-center justify-center text-[11px] font-black mb-5" :style="{ backgroundColor: textColor, color: bgColor }">
41
+ {{ step.icon || String(i+1).padStart(2,'0') }}
42
+ </div>
43
+ <component :is="itemTag" class="font-black uppercase tracking-tight mb-3 leading-tight">{{ step.title }}</component>
44
+ <p class="text-sm leading-relaxed" :style="{ opacity: 0.55 }">{{ step.description }}</p>
45
+ </div>
46
+ </div>
47
+
48
+ <!-- TIMELINE -->
49
+ <div v-else-if="layout === 'timeline'" class="relative max-w-3xl mx-auto">
50
+ <div class="absolute left-1/2 -translate-x-1/2 top-0 bottom-0 w-px" :style="{ backgroundColor: textColor + '12' }"></div>
51
+ <div v-for="(step, i) in items" :key="i" :class="['relative flex items-start gap-8 pb-12 last:pb-0', i % 2 === 0 ? 'flex-row' : 'flex-row-reverse']">
52
+ <div class="flex-1 text-right" :class="i % 2 === 0 ? '' : 'text-left'">
53
+ <component :is="itemTag" class="font-black uppercase tracking-tight mb-2 leading-tight">{{ step.title }}</component>
54
+ <p class="text-sm leading-relaxed" :style="{ opacity: 0.55 }">{{ step.description }}</p>
55
+ </div>
56
+ <div class="relative z-10 w-10 h-10 rounded-full flex items-center justify-center text-[11px] font-black shrink-0" :style="{ backgroundColor: textColor, color: bgColor }">
57
+ {{ step.icon || String(i+1).padStart(2,'0') }}
58
+ </div>
59
+ <div class="flex-1"></div>
60
+ </div>
61
+ </div>
62
+
63
+ <!-- MINIMAL -->
64
+ <div v-else class="divide-y max-w-2xl" :style="{ borderColor: textColor + '10' }">
65
+ <div v-for="(step, i) in items" :key="i" class="flex items-start gap-6 py-6" :style="{ borderColor: textColor + '10' }">
66
+ <span class="text-[11px] font-black opacity-20 shrink-0 w-5">{{ String(i+1).padStart(2,'0') }}</span>
67
+ <div>
68
+ <component :is="itemTag" class="font-black uppercase tracking-tight mb-1 leading-tight">{{ step.title }}</component>
69
+ <p class="text-sm" :style="{ opacity: 0.5 }">{{ step.description }}</p>
70
+ </div>
71
+ </div>
72
+ </div>
73
+
74
+ </div>
75
+ </section>
76
+ </template>
77
+
78
+ <script setup>
79
+ import { computed } from "vue";
80
+ const props = defineProps({
81
+ layout: { type: String, default: "steps" },
82
+ title: { type: String, default: "How it works" },
83
+ titleTag: { type: String, default: "h2" },
84
+ badge: { type: String, default: "" },
85
+ description: { type: String, default: "" },
86
+ itemTag: { type: String, default: "h3" },
87
+ items: { type: Array, default: () => [] },
88
+ bgColor: { type: String, default: "#ffffff" },
89
+ textColor: { type: String, default: "#000000" },
90
+ spacing: { type: String, default: "normal" },
91
+ radius: { type: String, default: "rounded" }
92
+ });
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");
94
+ const cardRound = computed(() => ({ sharp: "rounded-xl", rounded: "rounded-3xl", pill: "rounded-[40px]" })[props.radius] || "rounded-3xl");
95
+ </script>
@@ -0,0 +1,3 @@
1
+ declare const _default: typeof __VLS_export;
2
+ export default _default;
3
+ declare const __VLS_export: any;
@@ -0,0 +1,3 @@
1
+ declare const _default: typeof __VLS_export;
2
+ export default _default;
3
+ declare const __VLS_export: any;
@@ -0,0 +1,97 @@
1
+ <template>
2
+ <section :class="[sectionPad]" :style="{ backgroundColor: bgColor, color: textColor }">
3
+ <div class="max-w-6xl mx-auto">
4
+
5
+ <div v-if="title || description" class="mb-12" :class="headerAlign === 'center' ? 'text-center max-w-2xl mx-auto' : 'max-w-xl'">
6
+ <span v-if="badge" class="inline-block text-[10px] font-bold tracking-[0.5em] uppercase px-3 py-1 rounded-full border mb-4" :style="{ borderColor: textColor + '25', color: textColor + 'aa' }">{{ badge }}</span>
7
+ <component :is="titleTag" class="text-3xl md:text-5xl font-black uppercase tracking-tighter leading-[0.9] mb-4">{{ title }}</component>
8
+ <p v-if="description" class="text-sm leading-relaxed mb-6" :style="{ opacity: 0.55 }">{{ description }}</p>
9
+ <div v-if="ctas && ctas.length" class="flex flex-wrap gap-3">
10
+ <NuxtLink v-for="(cta, i) in ctas" :key="i" :to="cta.link || '#'"
11
+ class="inline-flex items-center gap-2.5 px-6 py-3 rounded-full text-[11px] font-black uppercase tracking-widest transition-all hover:gap-5"
12
+ :style="i === 0 ? { backgroundColor: textColor, color: bgColor } : { border: `1px solid ${textColor}40`, color: textColor }">
13
+ {{ cta.text }}<span>→</span>
14
+ </NuxtLink>
15
+ </div>
16
+ </div>
17
+
18
+ <!-- GRID -->
19
+ <div v-if="layout === 'grid'" class="grid grid-cols-1 md:grid-cols-3 gap-6">
20
+ <div v-for="(service, idx) in items" :key="idx"
21
+ :class="['group relative p-8 border transition-all duration-300 overflow-hidden hover:-translate-y-1 hover:shadow-lg', cardRound]"
22
+ :style="{ backgroundColor: textColor + '04', borderColor: textColor + '12' }">
23
+ <span class="absolute -top-6 -right-2 text-[100px] font-black italic opacity-[0.04] select-none">{{ String(idx+1).padStart(2,'0') }}</span>
24
+ <img v-if="service.image" :src="service.image" class="w-10 h-10 object-contain mb-5 rounded-xl" />
25
+ <component :is="itemTag" class="text-lg font-black uppercase tracking-tight mb-3 leading-tight">{{ service.title }}</component>
26
+ <p class="text-sm leading-relaxed mb-4" :style="{ opacity: 0.5 }">{{ service.description }}</p>
27
+ <p v-if="service.price" class="text-2xl font-black tracking-tight mb-4">{{ service.price }}</p>
28
+ <NuxtLink v-if="service.ctaText || service.link" :to="service.link || '#'" class="inline-flex items-center gap-2 text-[10px] font-black uppercase tracking-widest transition-all hover:gap-4" :style="{ color: textColor }">{{ service.ctaText || 'En savoir +' }}<span>→</span></NuxtLink>
29
+ </div>
30
+ </div>
31
+
32
+ <!-- LIST -->
33
+ <div v-else-if="layout === 'list'" :style="{ borderColor: textColor + '12' }">
34
+ <div v-for="(service, idx) in items" :key="idx" class="group flex items-start gap-8 py-8 border-b last:border-b-0" :style="{ borderColor: textColor + '10' }">
35
+ <span class="text-[11px] font-black opacity-20 pt-1 w-6 shrink-0">{{ String(idx+1).padStart(2,'0') }}</span>
36
+ <img v-if="service.image" :src="service.image" class="w-12 h-12 object-contain rounded-xl shrink-0" />
37
+ <div class="flex-1 min-w-0">
38
+ <component :is="itemTag" class="text-xl font-black uppercase tracking-tight mb-2">{{ service.title }}</component>
39
+ <p class="text-sm leading-relaxed" :style="{ opacity: 0.5 }">{{ service.description }}</p>
40
+ <p v-if="service.price" class="text-xl font-black tracking-tight mt-3">{{ service.price }}</p>
41
+ </div>
42
+ <NuxtLink v-if="service.ctaText || service.link" :to="service.link || '#'" class="inline-flex items-center gap-2 text-[10px] font-black uppercase tracking-widest shrink-0 mt-1 transition-all hover:gap-4" :style="{ color: textColor }">{{ service.ctaText || '→' }}</NuxtLink>
43
+ </div>
44
+ </div>
45
+
46
+ <!-- CARDS -->
47
+ <div v-else-if="layout === 'cards'" class="grid grid-cols-1 md:grid-cols-2 gap-6">
48
+ <div v-for="(service, idx) in items" :key="idx"
49
+ :class="['flex gap-6 p-7 border transition-all hover:shadow-md', cardRound]"
50
+ :style="{ backgroundColor: textColor + '03', borderColor: textColor + '10' }">
51
+ <div class="w-12 h-12 rounded-2xl flex items-center justify-center shrink-0" :style="{ backgroundColor: textColor + '10' }">
52
+ <img v-if="service.image" :src="service.image" class="w-7 h-7 object-contain" />
53
+ <span v-else class="text-lg font-black opacity-30">{{ String(idx+1).padStart(2,'0') }}</span>
54
+ </div>
55
+ <div>
56
+ <component :is="itemTag" class="text-base font-black uppercase tracking-tight mb-2 leading-tight">{{ service.title }}</component>
57
+ <p class="text-sm leading-relaxed" :style="{ opacity: 0.5 }">{{ service.description }}</p>
58
+ <p v-if="service.price" class="text-xl font-black mt-3">{{ service.price }}</p>
59
+ </div>
60
+ </div>
61
+ </div>
62
+
63
+ <!-- MINIMAL -->
64
+ <div v-else class="max-w-2xl">
65
+ <div v-for="(service, idx) in items" :key="idx" class="flex items-start justify-between gap-6 py-6 border-b last:border-b-0" :style="{ borderColor: textColor + '10' }">
66
+ <div>
67
+ <component :is="itemTag" class="font-black uppercase tracking-tight mb-1">{{ service.title }}</component>
68
+ <p v-if="service.description" class="text-sm" :style="{ opacity: 0.5 }">{{ service.description }}</p>
69
+ </div>
70
+ <p v-if="service.price" class="text-xl font-black tracking-tighter shrink-0">{{ service.price }}</p>
71
+ </div>
72
+ </div>
73
+
74
+ </div>
75
+ </section>
76
+ </template>
77
+
78
+ <script setup>
79
+ import { computed } from "vue";
80
+ const props = defineProps({
81
+ layout: { type: String, default: "grid" },
82
+ title: { type: String, default: "Our Services" },
83
+ titleTag: { type: String, default: "h2" },
84
+ badge: { type: String, default: "" },
85
+ description: { type: String, default: "" },
86
+ headerAlign: { type: String, default: "left" },
87
+ itemTag: { type: String, default: "h3" },
88
+ items: { type: Array, default: () => [] },
89
+ ctas: { type: Array, default: () => [] },
90
+ bgColor: { type: String, default: "#ffffff" },
91
+ textColor: { type: String, default: "#000000" },
92
+ spacing: { type: String, default: "normal" },
93
+ radius: { type: String, default: "rounded" }
94
+ });
95
+ 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");
96
+ const cardRound = computed(() => ({ sharp: "rounded-xl", rounded: "rounded-3xl", pill: "rounded-[40px]" })[props.radius] || "rounded-3xl");
97
+ </script>
@@ -0,0 +1,3 @@
1
+ declare const _default: typeof __VLS_export;
2
+ export default _default;
3
+ declare const __VLS_export: any;
@@ -0,0 +1,3 @@
1
+ declare const _default: typeof __VLS_export;
2
+ export default _default;
3
+ declare const __VLS_export: any;