cinqcinqdev-seo 0.1.72 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +94 -11
  2. package/dist/module.json +1 -1
  3. package/dist/runtime/assets/admin-tw.css +1 -1
  4. package/dist/runtime/components/admin-cms/DynamicRenderer.vue +16 -2
  5. package/dist/runtime/components/sections/swiss-style/BlogHero.d.vue.ts +3 -0
  6. package/dist/runtime/components/sections/swiss-style/BlogHero.vue +146 -0
  7. package/dist/runtime/components/sections/swiss-style/BlogHero.vue.d.ts +3 -0
  8. package/dist/runtime/components/sections/swiss-style/CallToAction.d.vue.ts +3 -0
  9. package/dist/runtime/components/sections/swiss-style/CallToAction.vue +138 -0
  10. package/dist/runtime/components/sections/swiss-style/CallToAction.vue.d.ts +3 -0
  11. package/dist/runtime/components/sections/swiss-style/Comparison.d.vue.ts +3 -0
  12. package/dist/runtime/components/sections/swiss-style/Comparison.vue +280 -0
  13. package/dist/runtime/components/sections/swiss-style/Comparison.vue.d.ts +3 -0
  14. package/dist/runtime/components/sections/swiss-style/ContactForm.d.vue.ts +3 -0
  15. package/dist/runtime/components/sections/swiss-style/ContactForm.vue +173 -0
  16. package/dist/runtime/components/sections/swiss-style/ContactForm.vue.d.ts +3 -0
  17. package/dist/runtime/components/sections/swiss-style/DataTable.d.vue.ts +3 -0
  18. package/dist/runtime/components/sections/swiss-style/DataTable.vue +245 -0
  19. package/dist/runtime/components/sections/swiss-style/DataTable.vue.d.ts +3 -0
  20. package/dist/runtime/components/sections/swiss-style/FAQ.d.vue.ts +3 -0
  21. package/dist/runtime/components/sections/swiss-style/FAQ.vue +107 -0
  22. package/dist/runtime/components/sections/swiss-style/FAQ.vue.d.ts +3 -0
  23. package/dist/runtime/components/sections/swiss-style/Features.d.vue.ts +3 -0
  24. package/dist/runtime/components/sections/swiss-style/Features.vue +103 -0
  25. package/dist/runtime/components/sections/swiss-style/Features.vue.d.ts +3 -0
  26. package/dist/runtime/components/sections/swiss-style/HeroFan.d.vue.ts +3 -0
  27. package/dist/runtime/components/sections/swiss-style/HeroFan.vue +108 -0
  28. package/dist/runtime/components/sections/swiss-style/HeroFan.vue.d.ts +3 -0
  29. package/dist/runtime/components/sections/swiss-style/HeroSection.d.vue.ts +3 -0
  30. package/dist/runtime/components/sections/swiss-style/HeroSection.vue +190 -0
  31. package/dist/runtime/components/sections/swiss-style/HeroSection.vue.d.ts +3 -0
  32. package/dist/runtime/components/sections/swiss-style/HeroSlider.d.vue.ts +3 -0
  33. package/dist/runtime/components/sections/swiss-style/HeroSlider.vue +138 -0
  34. package/dist/runtime/components/sections/swiss-style/HeroSlider.vue.d.ts +3 -0
  35. package/dist/runtime/components/sections/swiss-style/Newsletter.d.vue.ts +3 -0
  36. package/dist/runtime/components/sections/swiss-style/Newsletter.vue +125 -0
  37. package/dist/runtime/components/sections/swiss-style/Newsletter.vue.d.ts +3 -0
  38. package/dist/runtime/components/sections/swiss-style/Pricing.d.vue.ts +3 -0
  39. package/dist/runtime/components/sections/swiss-style/Pricing.vue +148 -0
  40. package/dist/runtime/components/sections/swiss-style/Pricing.vue.d.ts +3 -0
  41. package/dist/runtime/components/sections/swiss-style/Process.d.vue.ts +3 -0
  42. package/dist/runtime/components/sections/swiss-style/Process.vue +124 -0
  43. package/dist/runtime/components/sections/swiss-style/Process.vue.d.ts +3 -0
  44. package/dist/runtime/components/sections/swiss-style/Quote.d.vue.ts +3 -0
  45. package/dist/runtime/components/sections/swiss-style/Quote.vue +130 -0
  46. package/dist/runtime/components/sections/swiss-style/Quote.vue.d.ts +3 -0
  47. package/dist/runtime/components/sections/swiss-style/RelatedPages.d.vue.ts +3 -0
  48. package/dist/runtime/components/sections/swiss-style/RelatedPages.vue +212 -0
  49. package/dist/runtime/components/sections/swiss-style/RelatedPages.vue.d.ts +3 -0
  50. package/dist/runtime/components/sections/swiss-style/RichText.d.vue.ts +3 -0
  51. package/dist/runtime/components/sections/swiss-style/RichText.vue +100 -0
  52. package/dist/runtime/components/sections/swiss-style/RichText.vue.d.ts +3 -0
  53. package/dist/runtime/components/sections/swiss-style/ServiceGrid.d.vue.ts +3 -0
  54. package/dist/runtime/components/sections/swiss-style/ServiceGrid.vue +121 -0
  55. package/dist/runtime/components/sections/swiss-style/ServiceGrid.vue.d.ts +3 -0
  56. package/dist/runtime/components/sections/swiss-style/Testimonials.d.vue.ts +3 -0
  57. package/dist/runtime/components/sections/swiss-style/Testimonials.vue +126 -0
  58. package/dist/runtime/components/sections/swiss-style/Testimonials.vue.d.ts +3 -0
  59. package/dist/runtime/components/sections/swiss-style/TextVisual.d.vue.ts +3 -0
  60. package/dist/runtime/components/sections/swiss-style/TextVisual.vue +146 -0
  61. package/dist/runtime/components/sections/swiss-style/TextVisual.vue.d.ts +3 -0
  62. package/dist/runtime/composables/useAdminSettings.d.ts +1 -0
  63. package/dist/runtime/pages/admin/editor/[id].vue +15 -1
  64. package/dist/runtime/pages/admin/setup.vue +77 -2
  65. package/package.json +1 -1
  66. /package/dist/runtime/components/sections/{BlogHero.d.vue.ts → cinqcinq/BlogHero.d.vue.ts} +0 -0
  67. /package/dist/runtime/components/sections/{BlogHero.vue → cinqcinq/BlogHero.vue} +0 -0
  68. /package/dist/runtime/components/sections/{BlogHero.vue.d.ts → cinqcinq/BlogHero.vue.d.ts} +0 -0
  69. /package/dist/runtime/components/sections/{CallToAction.d.vue.ts → cinqcinq/CallToAction.d.vue.ts} +0 -0
  70. /package/dist/runtime/components/sections/{CallToAction.vue → cinqcinq/CallToAction.vue} +0 -0
  71. /package/dist/runtime/components/sections/{CallToAction.vue.d.ts → cinqcinq/CallToAction.vue.d.ts} +0 -0
  72. /package/dist/runtime/components/sections/{Comparison.d.vue.ts → cinqcinq/Comparison.d.vue.ts} +0 -0
  73. /package/dist/runtime/components/sections/{Comparison.vue → cinqcinq/Comparison.vue} +0 -0
  74. /package/dist/runtime/components/sections/{Comparison.vue.d.ts → cinqcinq/Comparison.vue.d.ts} +0 -0
  75. /package/dist/runtime/components/sections/{ContactForm.d.vue.ts → cinqcinq/ContactForm.d.vue.ts} +0 -0
  76. /package/dist/runtime/components/sections/{ContactForm.vue → cinqcinq/ContactForm.vue} +0 -0
  77. /package/dist/runtime/components/sections/{ContactForm.vue.d.ts → cinqcinq/ContactForm.vue.d.ts} +0 -0
  78. /package/dist/runtime/components/sections/{DataTable.d.vue.ts → cinqcinq/DataTable.d.vue.ts} +0 -0
  79. /package/dist/runtime/components/sections/{DataTable.vue → cinqcinq/DataTable.vue} +0 -0
  80. /package/dist/runtime/components/sections/{DataTable.vue.d.ts → cinqcinq/DataTable.vue.d.ts} +0 -0
  81. /package/dist/runtime/components/sections/{FAQ.d.vue.ts → cinqcinq/FAQ.d.vue.ts} +0 -0
  82. /package/dist/runtime/components/sections/{FAQ.vue → cinqcinq/FAQ.vue} +0 -0
  83. /package/dist/runtime/components/sections/{FAQ.vue.d.ts → cinqcinq/FAQ.vue.d.ts} +0 -0
  84. /package/dist/runtime/components/sections/{Features.d.vue.ts → cinqcinq/Features.d.vue.ts} +0 -0
  85. /package/dist/runtime/components/sections/{Features.vue → cinqcinq/Features.vue} +0 -0
  86. /package/dist/runtime/components/sections/{Features.vue.d.ts → cinqcinq/Features.vue.d.ts} +0 -0
  87. /package/dist/runtime/components/sections/{HeroFan.d.vue.ts → cinqcinq/HeroFan.d.vue.ts} +0 -0
  88. /package/dist/runtime/components/sections/{HeroFan.vue → cinqcinq/HeroFan.vue} +0 -0
  89. /package/dist/runtime/components/sections/{HeroFan.vue.d.ts → cinqcinq/HeroFan.vue.d.ts} +0 -0
  90. /package/dist/runtime/components/sections/{HeroSection.d.vue.ts → cinqcinq/HeroSection.d.vue.ts} +0 -0
  91. /package/dist/runtime/components/sections/{HeroSection.vue → cinqcinq/HeroSection.vue} +0 -0
  92. /package/dist/runtime/components/sections/{HeroSection.vue.d.ts → cinqcinq/HeroSection.vue.d.ts} +0 -0
  93. /package/dist/runtime/components/sections/{HeroSlider.d.vue.ts → cinqcinq/HeroSlider.d.vue.ts} +0 -0
  94. /package/dist/runtime/components/sections/{HeroSlider.vue → cinqcinq/HeroSlider.vue} +0 -0
  95. /package/dist/runtime/components/sections/{HeroSlider.vue.d.ts → cinqcinq/HeroSlider.vue.d.ts} +0 -0
  96. /package/dist/runtime/components/sections/{Newsletter.d.vue.ts → cinqcinq/Newsletter.d.vue.ts} +0 -0
  97. /package/dist/runtime/components/sections/{Newsletter.vue → cinqcinq/Newsletter.vue} +0 -0
  98. /package/dist/runtime/components/sections/{Newsletter.vue.d.ts → cinqcinq/Newsletter.vue.d.ts} +0 -0
  99. /package/dist/runtime/components/sections/{Pricing.d.vue.ts → cinqcinq/Pricing.d.vue.ts} +0 -0
  100. /package/dist/runtime/components/sections/{Pricing.vue → cinqcinq/Pricing.vue} +0 -0
  101. /package/dist/runtime/components/sections/{Pricing.vue.d.ts → cinqcinq/Pricing.vue.d.ts} +0 -0
  102. /package/dist/runtime/components/sections/{Process.d.vue.ts → cinqcinq/Process.d.vue.ts} +0 -0
  103. /package/dist/runtime/components/sections/{Process.vue → cinqcinq/Process.vue} +0 -0
  104. /package/dist/runtime/components/sections/{Process.vue.d.ts → cinqcinq/Process.vue.d.ts} +0 -0
  105. /package/dist/runtime/components/sections/{Quote.d.vue.ts → cinqcinq/Quote.d.vue.ts} +0 -0
  106. /package/dist/runtime/components/sections/{Quote.vue → cinqcinq/Quote.vue} +0 -0
  107. /package/dist/runtime/components/sections/{Quote.vue.d.ts → cinqcinq/Quote.vue.d.ts} +0 -0
  108. /package/dist/runtime/components/sections/{RelatedPages.d.vue.ts → cinqcinq/RelatedPages.d.vue.ts} +0 -0
  109. /package/dist/runtime/components/sections/{RelatedPages.vue → cinqcinq/RelatedPages.vue} +0 -0
  110. /package/dist/runtime/components/sections/{RelatedPages.vue.d.ts → cinqcinq/RelatedPages.vue.d.ts} +0 -0
  111. /package/dist/runtime/components/sections/{RichText.d.vue.ts → cinqcinq/RichText.d.vue.ts} +0 -0
  112. /package/dist/runtime/components/sections/{RichText.vue → cinqcinq/RichText.vue} +0 -0
  113. /package/dist/runtime/components/sections/{RichText.vue.d.ts → cinqcinq/RichText.vue.d.ts} +0 -0
  114. /package/dist/runtime/components/sections/{ServiceGrid.d.vue.ts → cinqcinq/ServiceGrid.d.vue.ts} +0 -0
  115. /package/dist/runtime/components/sections/{ServiceGrid.vue → cinqcinq/ServiceGrid.vue} +0 -0
  116. /package/dist/runtime/components/sections/{ServiceGrid.vue.d.ts → cinqcinq/ServiceGrid.vue.d.ts} +0 -0
  117. /package/dist/runtime/components/sections/{Testimonials.d.vue.ts → cinqcinq/Testimonials.d.vue.ts} +0 -0
  118. /package/dist/runtime/components/sections/{Testimonials.vue → cinqcinq/Testimonials.vue} +0 -0
  119. /package/dist/runtime/components/sections/{Testimonials.vue.d.ts → cinqcinq/Testimonials.vue.d.ts} +0 -0
  120. /package/dist/runtime/components/sections/{TextVisual.d.vue.ts → cinqcinq/TextVisual.d.vue.ts} +0 -0
  121. /package/dist/runtime/components/sections/{TextVisual.vue → cinqcinq/TextVisual.vue} +0 -0
  122. /package/dist/runtime/components/sections/{TextVisual.vue.d.ts → cinqcinq/TextVisual.vue.d.ts} +0 -0
@@ -0,0 +1,190 @@
1
+ <template>
2
+ <section ref="sectionEl" class="relative" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
3
+
4
+ <!-- CENTERED -->
5
+ <div v-if="layout === 'centered'" class="px-8 md:px-16 pt-20 pb-16" :class="[containerClass]">
6
+ <div class="border-l-4 pl-8 mb-12" :style="{ borderColor: textColor }">
7
+ <span v-if="badge" class="block text-[9px] font-black uppercase tracking-[0.5em] mb-4" :style="{ opacity: 0.4 }">{{ badge }}</span>
8
+ <component :is="titleTag" :class="['font-black uppercase leading-[0.85] mb-0', isVisible ? 'sv-in' : 'sv-out']" :style="{ fontSize: titleSize === 'sm' ? 'clamp(2.5rem,5vw,4rem)' : titleSize === 'lg' ? 'clamp(3.5rem,9vw,8rem)' : 'clamp(3rem,7vw,6rem)', letterSpacing: '-0.03em' }">{{ title }}</component>
9
+ </div>
10
+ <div class="grid md:grid-cols-2 gap-12 items-end border-t pt-8" :style="{ borderColor: ca(textColor,'15') }">
11
+ <p class="text-sm leading-relaxed" :style="{ opacity: 0.55 }">{{ subtitle }}</p>
12
+ <div class="flex items-center gap-6">
13
+ <div class="h-px flex-1" :style="{ backgroundColor: ca(textColor,'20') }"></div>
14
+ <NuxtLink :to="ctaLink || '#'" class="inline-flex items-center gap-3 px-6 py-3 border text-[11px] font-black uppercase tracking-widest transition-all hover:gap-6" :style="{ borderColor: textColor, color: textColor }">{{ ctaText }}<span>→</span></NuxtLink>
15
+ </div>
16
+ </div>
17
+ <div v-if="image" class="mt-12 aspect-video overflow-hidden border" :style="{ borderColor: ca(textColor,'15') }">
18
+ <img :src="image" class="w-full h-full object-cover" />
19
+ </div>
20
+ </div>
21
+
22
+ <!-- SPLIT -->
23
+ <div v-else-if="layout === 'split'" class="px-8 md:px-16 py-16">
24
+ <div :class="[containerClass, 'grid md:grid-cols-2 gap-0 border', isVisible ? 'sv-in' : 'sv-out']" :style="{ borderColor: ca(textColor,'20') }">
25
+ <div class="p-10 md:p-14 border-b md:border-b-0 md:border-r flex flex-col justify-between" :style="{ borderColor: ca(textColor,'15') }">
26
+ <span v-if="badge" class="text-[9px] font-black uppercase tracking-[0.5em] mb-8 block" :style="{ opacity: 0.35 }">{{ badge }}</span>
27
+ <component :is="titleTag" class="font-black uppercase leading-[0.85] mb-0" :style="{ fontSize: 'clamp(2.2rem,5vw,4.5rem)', letterSpacing: '-0.03em' }">{{ title }}</component>
28
+ </div>
29
+ <div class="p-10 md:p-14 flex flex-col justify-between">
30
+ <p class="text-sm leading-relaxed mb-10" :style="{ opacity: 0.55 }">{{ subtitle }}</p>
31
+ <div>
32
+ <div class="h-px mb-8" :style="{ backgroundColor: ca(textColor,'15') }"></div>
33
+ <NuxtLink :to="ctaLink || '#'" class="inline-flex items-center gap-3 px-6 py-3 border text-[11px] font-black uppercase tracking-widest transition-all hover:gap-6" :style="{ borderColor: textColor, color: textColor }">{{ ctaText }}<span>→</span></NuxtLink>
34
+ </div>
35
+ </div>
36
+ <div v-if="image" class="md:col-span-2 border-t overflow-hidden" :style="{ borderColor: ca(textColor,'15'), height: '320px' }">
37
+ <img :src="image" class="w-full h-full object-cover" />
38
+ </div>
39
+ </div>
40
+ </div>
41
+
42
+ <!-- FULLSCREEN -->
43
+ <div v-else-if="layout === 'fullscreen'" class="relative flex flex-col justify-end" style="min-height: 88vh">
44
+ <div class="absolute inset-0">
45
+ <img v-if="image" :src="image" class="w-full h-full object-cover" />
46
+ <div v-else class="w-full h-full" :style="{ backgroundColor: ca(textColor,'08') }"></div>
47
+ <div v-if="showOverlay" class="absolute inset-0" :style="overlayStyle"></div>
48
+ </div>
49
+ <div :class="[containerClass, 'relative z-10 w-full px-8 md:px-16 pb-12 border-t', isVisible ? 'sv-in' : 'sv-out']" :style="{ borderColor: ca(textColor,'30') }">
50
+ <div class="grid md:grid-cols-3 gap-8 pt-8">
51
+ <div class="md:col-span-2">
52
+ <span v-if="badge" class="block text-[9px] font-black uppercase tracking-[0.5em] mb-4" :style="{ opacity: 0.5 }">{{ badge }}</span>
53
+ <component :is="titleTag" class="font-black uppercase leading-[0.85]" :style="{ fontSize: 'clamp(2.5rem,7vw,6rem)', letterSpacing: '-0.03em' }">{{ title }}</component>
54
+ </div>
55
+ <div class="flex flex-col justify-end">
56
+ <p class="text-sm leading-relaxed mb-6" :style="{ opacity: 0.65 }">{{ subtitle }}</p>
57
+ <NuxtLink :to="ctaLink || '#'" class="inline-flex items-center gap-3 px-6 py-3 border text-[11px] font-black uppercase tracking-widest transition-all hover:gap-6 self-start" :style="{ borderColor: textColor, color: textColor }">{{ ctaText }}<span>→</span></NuxtLink>
58
+ </div>
59
+ </div>
60
+ </div>
61
+ </div>
62
+
63
+ <!-- MINIMAL -->
64
+ <div v-else-if="layout === 'minimal'" class="px-8 md:px-16 py-14" :class="[containerClass]">
65
+ <div class="flex items-center gap-6 mb-10 border-b pb-6" :style="{ borderColor: ca(textColor,'15') }">
66
+ <span class="text-[9px] font-black uppercase tracking-[0.5em]" :style="{ opacity: 0.3 }">{{ badge || '—' }}</span>
67
+ <div class="h-px flex-1" :style="{ backgroundColor: ca(textColor,'10') }"></div>
68
+ <span class="text-[9px] font-black uppercase tracking-[0.5em]" :style="{ opacity: 0.2 }">{{ ctaText }}</span>
69
+ </div>
70
+ <component :is="titleTag" :class="['font-black uppercase leading-[0.85] mb-10', isVisible ? 'sv-in' : 'sv-out']" :style="{ fontSize: 'clamp(3.5rem,10vw,9rem)', letterSpacing: '-0.04em' }">{{ title }}</component>
71
+ <div v-if="image" class="mb-10 overflow-hidden border aspect-video" :style="{ borderColor: ca(textColor,'15') }">
72
+ <img :src="image" class="w-full h-full object-cover" />
73
+ </div>
74
+ <div class="flex flex-col md:flex-row items-start md:items-end justify-between gap-6 border-t pt-8" :style="{ borderColor: ca(textColor,'15') }">
75
+ <p class="text-sm leading-relaxed max-w-xs" :style="{ opacity: 0.5 }">{{ subtitle }}</p>
76
+ <NuxtLink :to="ctaLink || '#'" class="inline-flex items-center gap-3 text-[11px] font-black uppercase tracking-widest transition-all hover:gap-6">{{ ctaText }}<span>→</span></NuxtLink>
77
+ </div>
78
+ </div>
79
+
80
+ <!-- EDITORIAL (default) -->
81
+ <div v-else class="px-8 md:px-16 py-14">
82
+ <div :class="[containerClass]">
83
+ <div class="mb-8 border-b pb-6" :style="{ borderColor: ca(textColor,'12') }">
84
+ <span v-if="badge" class="text-[9px] font-black uppercase tracking-[0.5em]" :style="{ opacity: 0.3 }">{{ badge }}</span>
85
+ </div>
86
+ <component :is="titleTag" :class="['font-black uppercase leading-[0.85] mb-8', isVisible ? 'sv-in' : 'sv-out']" :style="{ fontSize: 'clamp(3rem,9vw,9rem)', letterSpacing: '-0.04em' }">{{ title }}</component>
87
+ <div class="grid md:grid-cols-2 gap-10 border-t pt-8" :style="{ borderColor: ca(textColor,'12') }">
88
+ <p class="text-sm leading-relaxed" :style="{ opacity: 0.5 }">{{ subtitle }}</p>
89
+ <div class="flex items-start justify-end">
90
+ <NuxtLink :to="ctaLink || '#'" class="inline-flex items-center gap-3 px-6 py-3 border text-[11px] font-black uppercase tracking-widest transition-all hover:gap-6" :style="{ borderColor: textColor, color: textColor }">{{ ctaText }}<span>→</span></NuxtLink>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+
96
+ </section>
97
+ </template>
98
+
99
+ <script setup>
100
+ const ca = (c, h) => {
101
+ const m = c?.match(/rgba\((var\([^)]+\)),\s*[\d.]+\)/);
102
+ return m ? `rgba(${m[1]}, ${Math.round(parseInt(h, 16) / 255 * 1e3) / 1e3})` : (c || "") + h;
103
+ };
104
+ import { ref, computed, onMounted } from "vue";
105
+ const props = defineProps({
106
+ layout: { type: String, default: "centered" },
107
+ animation: { type: String, default: "fadeUp" },
108
+ titleSize: { type: String, default: "md" },
109
+ title: { type: String, default: "Impactful Title" },
110
+ titleTag: { type: String, default: "h1" },
111
+ subtitle: { type: String, default: "" },
112
+ badge: { type: String, default: "" },
113
+ ctaText: { type: String, default: "Get Started" },
114
+ ctaLink: { type: String, default: "#" },
115
+ image: { type: String, default: "" },
116
+ videoUrl: { type: String, default: "" },
117
+ imageStyle: { type: String, default: "cover" },
118
+ imagePosition: { type: String, default: "center" },
119
+ bgColor: { type: String, default: "rgba(var(--primary-color), 1)" },
120
+ textColor: { type: String, default: "rgba(var(--body-bg), 1)" },
121
+ spacing: { type: String, default: "normal" },
122
+ radius: { type: String, default: "sharp" },
123
+ textDirection: { type: String, default: "ltr" },
124
+ contentWidth: { type: String, default: "normal" },
125
+ textSize: { type: String, default: "md" },
126
+ paddingTop: { type: String, default: "" },
127
+ paddingBottom: { type: String, default: "" },
128
+ marginTop: { type: String, default: "" },
129
+ marginBottom: { type: String, default: "" },
130
+ paddingX: { type: String, default: "" },
131
+ overlay: { type: String, default: "auto" },
132
+ overlayDirection: { type: String, default: "to top" },
133
+ overlayStrength: { type: String, default: "medium" },
134
+ overlayColor: { type: String, default: "" }
135
+ });
136
+ const sectionEl = ref(null);
137
+ const isVisible = ref(false);
138
+ onMounted(() => {
139
+ if (!sectionEl.value) return;
140
+ const obs = new IntersectionObserver(([e]) => {
141
+ if (e.isIntersecting) {
142
+ isVisible.value = true;
143
+ obs.disconnect();
144
+ }
145
+ }, { threshold: 0.05 });
146
+ obs.observe(sectionEl.value);
147
+ setTimeout(() => {
148
+ if (!isVisible.value) isVisible.value = true;
149
+ }, 400);
150
+ });
151
+ const showOverlay = computed(() => {
152
+ if (props.overlay === "off") return false;
153
+ if (props.overlay === "on") return true;
154
+ return ["fullscreen", "parallax", "video"].includes(props.layout);
155
+ });
156
+ const overlayStyle = computed(() => {
157
+ const dir = props.overlayDirection || "to top";
158
+ const c = props.overlayColor || props.bgColor;
159
+ const stops = {
160
+ light: `linear-gradient(${dir}, ${ca(c, "99")} 0%, ${ca(c, "44")} 40%, transparent 70%)`,
161
+ medium: `linear-gradient(${dir}, ${c} 15%, ${ca(c, "cc")} 40%, ${ca(c, "44")} 65%, transparent 100%)`,
162
+ strong: `linear-gradient(${dir}, ${c} 35%, ${ca(c, "ee")} 55%, ${ca(c, "88")} 75%, transparent 100%)`
163
+ };
164
+ return { background: stops[props.overlayStrength] || stops.medium };
165
+ });
166
+ const containerClass = computed(() => ({ narrow: "max-w-3xl mx-auto", normal: "max-w-6xl mx-auto", wide: "max-w-7xl mx-auto", full: "max-w-full" })[props.contentWidth] || "max-w-6xl mx-auto");
167
+ const SM = { none: "0", xs: "8px", sm: "24px", md: "48px", lg: "80px", xl: "128px", "-xs": "-8px", "-sm": "-24px", "-md": "-48px", "-lg": "-80px", "-xl": "-128px" };
168
+ const spacingStyle = computed(() => {
169
+ const s = {};
170
+ if (props.paddingTop && SM[props.paddingTop]) s.paddingTop = SM[props.paddingTop];
171
+ if (props.paddingBottom && SM[props.paddingBottom]) s.paddingBottom = SM[props.paddingBottom];
172
+ if (props.marginTop && SM[props.marginTop]) s.marginTop = SM[props.marginTop];
173
+ if (props.marginBottom && SM[props.marginBottom]) s.marginBottom = SM[props.marginBottom];
174
+ if (props.paddingX && SM[props.paddingX]) {
175
+ const v = SM[props.paddingX];
176
+ if (v.startsWith("-")) {
177
+ s.marginLeft = v;
178
+ s.marginRight = v;
179
+ } else {
180
+ s.paddingLeft = v;
181
+ s.paddingRight = v;
182
+ }
183
+ }
184
+ return s;
185
+ });
186
+ </script>
187
+
188
+ <style scoped>
189
+ .sv-out{opacity:0;transform:translateY(16px)}.sv-in{opacity:1;transform:none;transition:opacity .6s ease,transform .6s ease}
190
+ </style>
@@ -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,138 @@
1
+ <template>
2
+ <section
3
+ class="relative overflow-hidden select-none"
4
+ :dir="textDirection"
5
+ :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]"
6
+ @mouseenter="pauseAutoplay"
7
+ @mouseleave="resumeAutoplay"
8
+ >
9
+ <!-- Slides -->
10
+ <div class="relative overflow-hidden" :style="{ height: heightValue }">
11
+ <div
12
+ v-for="(slide, i) in slides" :key="i"
13
+ class="absolute inset-0 w-full h-full transition-opacity duration-500"
14
+ :style="{ opacity: i === current ? 1 : 0, pointerEvents: i === current ? 'auto' : 'none' }"
15
+ >
16
+ <img v-if="slide.image" :src="slide.image" class="absolute inset-0 w-full h-full object-cover" />
17
+ <div v-if="slide.image && overlay" class="absolute inset-0" :style="{ background: `linear-gradient(to top, ${bgColor} 20%, ${ca(bgColor,'aa')} 50%, transparent 100%)` }"></div>
18
+ <div v-if="!slide.image" class="absolute inset-0" :style="{ backgroundColor: ca(textColor,'08') }"></div>
19
+
20
+ <!-- Content -->
21
+ <div class="relative z-10 h-full flex flex-col justify-end px-8 md:px-16 pb-12 border-t" :style="{ borderColor: ca(textColor,'25') }">
22
+ <div class="max-w-6xl mx-auto w-full grid md:grid-cols-2 gap-8 pt-8">
23
+ <div>
24
+ <span v-if="slide.badge" class="block text-[9px] font-black uppercase tracking-[0.5em] mb-4 border-l-4 pl-3" :style="{ borderColor: textColor, opacity: 0.5 }">{{ slide.badge }}</span>
25
+ <div class="font-black uppercase leading-[0.88]" :style="{ fontSize: titleFontSize, letterSpacing: '-0.04em' }">{{ slide.title || 'Slide Title' }}</div>
26
+ </div>
27
+ <div class="flex flex-col justify-end">
28
+ <p v-if="slide.subtitle" :class="[bodyClass, 'leading-relaxed mb-6']" :style="{ opacity: 0.65 }">{{ slide.subtitle }}</p>
29
+ <div class="flex flex-wrap gap-3">
30
+ <NuxtLink v-if="slide.ctaText" :to="slide.ctaLink || '#'"
31
+ class="inline-flex items-center gap-3 px-6 py-3 text-[11px] font-black uppercase tracking-widest transition-all hover:gap-6 border"
32
+ :style="{ backgroundColor: textColor, color: bgColor, borderColor: textColor }">
33
+ {{ slide.ctaText }}<span>→</span>
34
+ </NuxtLink>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ </div>
41
+
42
+ <!-- Slide counter + controls -->
43
+ <div v-if="slides.length > 1" class="absolute bottom-4 right-8 md:right-16 z-20 flex items-center gap-4">
44
+ <button v-if="showArrows" @click="prev" class="w-8 h-8 border flex items-center justify-center text-xs font-black transition-all hover:opacity-70" :style="{ borderColor: ca(textColor,'30'), color: textColor }">←</button>
45
+ <span class="text-[10px] font-black tabular-nums" :style="{ opacity: 0.5 }">{{ String(current + 1).padStart(2,'0') }} / {{ String(slides.length).padStart(2,'0') }}</span>
46
+ <button v-if="showArrows" @click="next" class="w-8 h-8 border flex items-center justify-center text-xs font-black transition-all hover:opacity-70" :style="{ borderColor: ca(textColor,'30'), color: textColor }">→</button>
47
+ </div>
48
+
49
+ <!-- Progress bar -->
50
+ <div v-if="autoplay && slides.length > 1" class="absolute bottom-0 left-0 h-[2px] z-30"
51
+ :style="{ width: `${progress}%`, backgroundColor: textColor, opacity: 0.3, transition: 'width 0.05s linear' }"></div>
52
+ </section>
53
+ </template>
54
+
55
+ <script setup>
56
+ import { ref, computed, onMounted, onUnmounted } from "vue";
57
+ const ca = (c, h) => {
58
+ if (!c) return "#000" + h;
59
+ const m = c.match(/rgba\((var\([^)]+\)),\s*[\d.]+\)/);
60
+ return m ? `rgba(${m[1]}, ${Math.round(parseInt(h, 16) / 255 * 1e3) / 1e3})` : c + h;
61
+ };
62
+ const props = defineProps({
63
+ slides: { type: Array, default: () => [
64
+ { badge: "New", title: "First Slide Title", subtitle: "Describe your offer here.", ctaText: "Discover", ctaLink: "#", image: "" },
65
+ { badge: "", title: "Second Slide Title", subtitle: "Another compelling message.", ctaText: "Learn More", ctaLink: "#", image: "" }
66
+ ] },
67
+ transition: { type: String, default: "fade" },
68
+ autoplay: { type: Boolean, default: true },
69
+ interval: { type: Number, default: 5e3 },
70
+ height: { type: String, default: "large" },
71
+ overlay: { type: Boolean, default: true },
72
+ showDots: { type: Boolean, default: false },
73
+ showArrows: { type: Boolean, default: true },
74
+ contentAlign: { type: String, default: "bottom" },
75
+ titleSize: { type: String, default: "md" },
76
+ bgColor: { type: String, default: "rgba(var(--primary-color), 1)" },
77
+ textColor: { type: String, default: "rgba(var(--body-bg), 1)" },
78
+ radius: { type: String, default: "sharp" },
79
+ contentWidth: { type: String, default: "normal" },
80
+ textSize: { type: String, default: "md" },
81
+ textDirection: { type: String, default: "ltr" },
82
+ paddingTop: { type: String, default: "" },
83
+ paddingBottom: { type: String, default: "" },
84
+ marginTop: { type: String, default: "" },
85
+ marginBottom: { type: String, default: "" }
86
+ });
87
+ const current = ref(0);
88
+ const paused = ref(false);
89
+ const progress = ref(0);
90
+ const heightValue = computed(() => ({ small: "50vh", medium: "65vh", large: "80vh", full: "100vh" })[props.height] || "80vh");
91
+ const titleFontSize = computed(() => ({ sm: "clamp(2rem,4vw,3.5rem)", md: "clamp(2.5rem,5vw,4.5rem)", lg: "clamp(3rem,7vw,6rem)" })[props.titleSize] || "clamp(2.5rem,5vw,4.5rem)");
92
+ const bodyClass = computed(() => ({ sm: "text-xs", md: "text-sm", lg: "text-base" })[props.textSize] || "text-sm");
93
+ function next() {
94
+ current.value = (current.value + 1) % props.slides.length;
95
+ }
96
+ function prev() {
97
+ current.value = (current.value - 1 + props.slides.length) % props.slides.length;
98
+ }
99
+ let timer = null;
100
+ let progressTimer = null;
101
+ function startAutoplay() {
102
+ if (!props.autoplay || props.slides.length <= 1 || paused.value) return;
103
+ progress.value = 0;
104
+ const step = 100 / (props.interval / 50);
105
+ progressTimer = setInterval(() => {
106
+ progress.value = Math.min(progress.value + step, 100);
107
+ }, 50);
108
+ timer = setTimeout(() => {
109
+ next();
110
+ progress.value = 0;
111
+ clearInterval(progressTimer);
112
+ startAutoplay();
113
+ }, props.interval);
114
+ }
115
+ function pauseAutoplay() {
116
+ paused.value = true;
117
+ clearTimeout(timer);
118
+ clearInterval(progressTimer);
119
+ }
120
+ function resumeAutoplay() {
121
+ paused.value = false;
122
+ startAutoplay();
123
+ }
124
+ onMounted(startAutoplay);
125
+ onUnmounted(() => {
126
+ clearTimeout(timer);
127
+ clearInterval(progressTimer);
128
+ });
129
+ const SM = { none: "0", xs: "8px", sm: "24px", md: "48px", lg: "80px", xl: "128px", "-xs": "-8px", "-sm": "-24px", "-md": "-48px", "-lg": "-80px", "-xl": "-128px" };
130
+ const spacingStyle = computed(() => {
131
+ const s = {};
132
+ if (props.paddingTop && SM[props.paddingTop]) s.paddingTop = SM[props.paddingTop];
133
+ if (props.paddingBottom && SM[props.paddingBottom]) s.paddingBottom = SM[props.paddingBottom];
134
+ if (props.marginTop && SM[props.marginTop]) s.marginTop = SM[props.marginTop];
135
+ if (props.marginBottom && SM[props.marginBottom]) s.marginBottom = SM[props.marginBottom];
136
+ return s;
137
+ });
138
+ </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,125 @@
1
+ <template>
2
+ <section ref="sectionEl" :class="[sectionPad, isVisible ? 'sv-in' : 'sv-out']" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
3
+
4
+ <!-- CENTERED -->
5
+ <div v-if="layout === 'centered'" :class="[containerClass]">
6
+ <div class="border py-12 px-10 md:px-16" :style="{ borderColor: ca(textColor,'20') }">
7
+ <div class="grid md:grid-cols-2 gap-10 items-end">
8
+ <div>
9
+ <span v-if="badge" class="block text-[9px] font-black uppercase tracking-[0.5em] mb-4" :style="{ opacity: 0.35 }">{{ badge }}</span>
10
+ <h2 class="font-black uppercase leading-[0.88]" :style="{ fontSize: titleFontSize, letterSpacing: '-0.03em' }">{{ title }}</h2>
11
+ <p v-if="description" :class="[bodyClass, 'leading-relaxed mt-3']" :style="{ opacity: 0.6 }">{{ description }}</p>
12
+ </div>
13
+ <div>
14
+ <div class="flex border" :style="{ borderColor: ca(textColor,'30') }">
15
+ <input type="email" :placeholder="placeholder || 'your@email.com'" class="flex-1 px-4 py-3 text-sm outline-none bg-transparent" :style="{ color: textColor }" />
16
+ <button class="px-5 py-3 text-[11px] font-black uppercase tracking-wider border-l shrink-0" :style="{ borderColor: ca(textColor,'30'), backgroundColor: textColor, color: bgColor }">{{ buttonText || 'Subscribe' }}</button>
17
+ </div>
18
+ <p v-if="disclaimer" class="text-[11px] mt-2" :style="{ opacity: 0.35 }">{{ disclaimer }}</p>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ </div>
23
+
24
+ <!-- SPLIT -->
25
+ <div v-else-if="layout === 'split'" :class="[containerClass, 'grid grid-cols-1 md:grid-cols-2 border divide-x']" :style="{ borderColor: ca(textColor,'15') }">
26
+ <div class="p-10">
27
+ <span v-if="badge" class="block text-[9px] font-black uppercase tracking-[0.5em] mb-4" :style="{ opacity: 0.35 }">{{ badge }}</span>
28
+ <h2 class="font-black uppercase leading-[0.88]" :style="{ fontSize: titleFontSize, letterSpacing: '-0.03em' }">{{ title }}</h2>
29
+ <p v-if="description" :class="[bodyClass, 'leading-relaxed mt-3']" :style="{ opacity: 0.6 }">{{ description }}</p>
30
+ </div>
31
+ <div class="p-10 flex flex-col justify-center">
32
+ <div class="flex border" :style="{ borderColor: ca(textColor,'30') }">
33
+ <input type="email" :placeholder="placeholder || 'your@email.com'" class="flex-1 px-4 py-3 text-sm outline-none bg-transparent" :style="{ color: textColor }" />
34
+ <button class="px-5 py-3 text-[11px] font-black uppercase tracking-wider border-l shrink-0" :style="{ borderColor: ca(textColor,'30'), backgroundColor: textColor, color: bgColor }">{{ buttonText || 'Subscribe' }}</button>
35
+ </div>
36
+ <p v-if="disclaimer" class="text-[11px] mt-2" :style="{ opacity: 0.35 }">{{ disclaimer }}</p>
37
+ </div>
38
+ </div>
39
+
40
+ <!-- MINIMAL (default) -->
41
+ <div v-else :class="[containerClass]">
42
+ <div class="flex flex-col md:flex-row items-start md:items-center gap-8 border-t border-b py-10" :style="{ borderColor: ca(textColor,'15') }">
43
+ <div class="flex-1">
44
+ <h2 class="font-black uppercase leading-[0.9]" :style="{ fontSize: titleFontSize, letterSpacing: '-0.03em' }">{{ title }}</h2>
45
+ <p v-if="description" :class="[bodyClass, 'mt-1']" :style="{ opacity: 0.55 }">{{ description }}</p>
46
+ </div>
47
+ <div class="flex gap-0 w-full md:w-auto border" :style="{ borderColor: ca(textColor,'30') }">
48
+ <input type="email" :placeholder="placeholder || 'your@email.com'" class="flex-1 md:w-48 px-4 py-2.5 text-sm outline-none bg-transparent" :style="{ color: textColor }" />
49
+ <button class="px-5 py-2.5 text-[11px] font-black uppercase tracking-wider border-l shrink-0" :style="{ borderColor: ca(textColor,'30'), backgroundColor: textColor, color: bgColor }">{{ buttonText || 'OK' }}</button>
50
+ </div>
51
+ </div>
52
+ </div>
53
+
54
+ </section>
55
+ </template>
56
+
57
+ <script setup>
58
+ const ca = (c, h) => {
59
+ const m = c?.match(/rgba\((var\([^)]+\)),\s*[\d.]+\)/);
60
+ return m ? `rgba(${m[1]}, ${Math.round(parseInt(h, 16) / 255 * 1e3) / 1e3})` : (c || "") + h;
61
+ };
62
+ import { ref, computed, onMounted } from "vue";
63
+ const props = defineProps({
64
+ layout: { type: String, default: "centered" },
65
+ badge: { type: String, default: "" },
66
+ title: { type: String, default: "Stay in the loop" },
67
+ description: { type: String, default: "" },
68
+ placeholder: { type: String, default: "your@email.com" },
69
+ buttonText: { type: String, default: "Subscribe" },
70
+ disclaimer: { type: String, default: "" },
71
+ bgColor: { type: String, default: "rgba(var(--body-bg), 1)" },
72
+ textColor: { type: String, default: "rgba(var(--text-color), 1)" },
73
+ spacing: { type: String, default: "normal" },
74
+ radius: { type: String, default: "sharp" },
75
+ textDirection: { type: String, default: "ltr" },
76
+ contentWidth: { type: String, default: "normal" },
77
+ textSize: { type: String, default: "md" },
78
+ paddingTop: { type: String, default: "" },
79
+ paddingBottom: { type: String, default: "" },
80
+ marginTop: { type: String, default: "" },
81
+ marginBottom: { type: String, default: "" },
82
+ paddingX: { type: String, default: "" }
83
+ });
84
+ const sectionEl = ref(null);
85
+ const isVisible = ref(false);
86
+ onMounted(() => {
87
+ const obs = new IntersectionObserver(([e]) => {
88
+ if (e.isIntersecting) {
89
+ isVisible.value = true;
90
+ obs.disconnect();
91
+ }
92
+ }, { threshold: 0.06 });
93
+ if (sectionEl.value) obs.observe(sectionEl.value);
94
+ setTimeout(() => {
95
+ if (!isVisible.value) isVisible.value = true;
96
+ }, 300);
97
+ });
98
+ const sectionPad = computed(() => ({ compact: "py-8 px-4 md:px-8", normal: "py-14 px-8 md:px-16", spacious: "py-24 px-8 md:px-16" })[props.spacing] || "py-14 px-8 md:px-16");
99
+ const containerClass = computed(() => ({ narrow: "max-w-3xl mx-auto", normal: "max-w-6xl mx-auto", wide: "max-w-7xl mx-auto", full: "max-w-full" })[props.contentWidth] || "max-w-6xl mx-auto");
100
+ const titleFontSize = computed(() => ({ sm: "clamp(1rem,1.5vw,1.3rem)", md: "clamp(1.5rem,2.5vw,2rem)", lg: "clamp(2.5rem,6vw,5rem)" })[props.textSize] || "clamp(1.5rem,2.5vw,2rem)");
101
+ const bodyClass = computed(() => ({ sm: "text-xs", md: "text-sm", lg: "text-base" })[props.textSize] || "text-sm");
102
+ const SM = { none: "0", xs: "8px", sm: "24px", md: "48px", lg: "80px", xl: "128px", "-xs": "-8px", "-sm": "-24px", "-md": "-48px", "-lg": "-80px", "-xl": "-128px" };
103
+ const spacingStyle = computed(() => {
104
+ const s = {};
105
+ if (props.paddingTop && SM[props.paddingTop]) s.paddingTop = SM[props.paddingTop];
106
+ if (props.paddingBottom && SM[props.paddingBottom]) s.paddingBottom = SM[props.paddingBottom];
107
+ if (props.marginTop && SM[props.marginTop]) s.marginTop = SM[props.marginTop];
108
+ if (props.marginBottom && SM[props.marginBottom]) s.marginBottom = SM[props.marginBottom];
109
+ if (props.paddingX && SM[props.paddingX]) {
110
+ const v = SM[props.paddingX];
111
+ if (v.startsWith("-")) {
112
+ s.marginLeft = v;
113
+ s.marginRight = v;
114
+ } else {
115
+ s.paddingLeft = v;
116
+ s.paddingRight = v;
117
+ }
118
+ }
119
+ return s;
120
+ });
121
+ </script>
122
+
123
+ <style scoped>
124
+ .sv-out{opacity:0;transform:translateY(16px)}.sv-in{opacity:1;transform:none;transition:opacity .6s ease,transform .6s ease}
125
+ </style>
@@ -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,148 @@
1
+ <template>
2
+ <section ref="sectionEl" :class="[sectionPad, isVisible ? 'sv-in' : 'sv-out']" :dir="textDirection" :style="[{ backgroundColor: bgColor, color: textColor }, spacingStyle]">
3
+ <div :class="[containerClass]">
4
+
5
+ <!-- Header -->
6
+ <div v-if="title || description" class="border-b pb-6 mb-0" :style="{ borderColor: ca(textColor,'15') }">
7
+ <span v-if="badge" class="block text-[9px] font-black uppercase tracking-[0.5em] mb-3" :style="{ opacity: 0.35 }">{{ badge }}</span>
8
+ <component :is="titleTag" class="font-black uppercase leading-[0.88]" :style="{ fontSize: titleFontSize, letterSpacing: '-0.03em' }">{{ title }}</component>
9
+ <p v-if="description" :class="[bodyClass, 'leading-relaxed mt-3 max-w-xl']" :style="{ opacity: 0.55 }">{{ description }}</p>
10
+ </div>
11
+
12
+ <!-- CARDS -->
13
+ <div v-if="layout === 'cards'" class="grid grid-cols-1 md:grid-cols-3 border divide-x" :style="{ borderColor: ca(textColor,'15') }">
14
+ <div v-for="(plan, i) in items" :key="i" class="p-8 flex flex-col relative" :style="plan.highlighted ? { backgroundColor: textColor, color: bgColor } : {}">
15
+ <div v-if="plan.highlighted" class="absolute top-0 right-0 px-3 py-1 text-[9px] font-black uppercase tracking-widest" :style="{ backgroundColor: bgColor, color: textColor }">Popular</div>
16
+ <p class="text-[10px] font-black uppercase tracking-[0.4em] mb-4" :style="{ opacity: 0.5 }">{{ plan.name }}</p>
17
+ <div class="mb-4 border-b pb-6" :style="{ borderColor: plan.highlighted ? ca(bgColor,'25') : ca(textColor,'12') }">
18
+ <span class="text-4xl font-black tracking-tight">{{ plan.price }}</span>
19
+ <span v-if="plan.period" class="text-sm ml-1" :style="{ opacity: 0.5 }">{{ plan.period }}</span>
20
+ </div>
21
+ <p v-if="plan.description" class="text-xs leading-relaxed mb-6" :style="{ opacity: 0.55 }">{{ plan.description }}</p>
22
+ <ul v-if="plan.features && plan.features.length" class="space-y-2.5 mb-8 flex-1">
23
+ <li v-for="(feat, fi) in plan.features" :key="fi" class="flex items-start gap-3 text-sm">
24
+ <span class="text-[10px] mt-0.5 shrink-0 font-black">—</span>
25
+ <span :style="{ opacity: 0.75 }">{{ typeof feat === 'object' ? (feat.fr || feat) : feat }}</span>
26
+ </li>
27
+ </ul>
28
+ <div class="flex-1"></div>
29
+ <NuxtLink v-if="plan.ctaText || plan.ctaLink" :to="plan.ctaLink || '#'"
30
+ class="block text-center py-3 px-6 text-[11px] font-black uppercase tracking-widest transition-all hover:opacity-80 mt-6 border"
31
+ :style="plan.highlighted ? { backgroundColor: bgColor, color: textColor, borderColor: bgColor } : { borderColor: textColor, color: textColor }">
32
+ {{ plan.ctaText || 'Get started' }}
33
+ </NuxtLink>
34
+ </div>
35
+ </div>
36
+
37
+ <!-- MINIMAL (list) -->
38
+ <div v-else-if="layout === 'minimal'" class="divide-y max-w-2xl" :style="{ borderColor: ca(textColor,'12') }">
39
+ <div v-for="(plan, i) in items" :key="i" class="flex items-center gap-6 py-7" :style="{ borderColor: ca(textColor,'12') }">
40
+ <div class="flex-1">
41
+ <p class="font-black uppercase tracking-tight text-lg leading-none mb-1">{{ plan.name }}</p>
42
+ <p v-if="plan.description" class="text-xs" :style="{ opacity: 0.5 }">{{ plan.description }}</p>
43
+ </div>
44
+ <div class="text-right shrink-0">
45
+ <p class="text-2xl font-black tracking-tighter">{{ plan.price }}</p>
46
+ <p v-if="plan.period" class="text-[10px]" :style="{ opacity: 0.4 }">{{ plan.period }}</p>
47
+ </div>
48
+ <NuxtLink v-if="plan.ctaText || plan.ctaLink" :to="plan.ctaLink || '#'" class="inline-flex items-center gap-2 text-[10px] font-black uppercase tracking-widest transition-all hover:gap-4 shrink-0 border px-4 py-2" :style="{ borderColor: ca(textColor,'30'), color: textColor }">{{ plan.ctaText || '→' }}</NuxtLink>
49
+ </div>
50
+ </div>
51
+
52
+ <!-- TABLE (default) -->
53
+ <div v-else class="overflow-x-auto">
54
+ <table class="w-full min-w-[540px] border-collapse">
55
+ <thead>
56
+ <tr class="border-b" :style="{ borderColor: ca(textColor,'15') }">
57
+ <th class="text-left py-6 pr-6 align-bottom"><span class="text-[10px] font-black uppercase tracking-wider" :style="{ opacity: 0.35 }">Plan</span></th>
58
+ <th v-for="(plan, i) in items" :key="i" class="py-6 px-4 align-bottom border-l" :style="{ borderColor: ca(textColor,'10') }">
59
+ <div :style="plan.highlighted ? { color: textColor } : {}">
60
+ <div class="text-[10px] font-black uppercase tracking-[0.35em] mb-2" :style="{ opacity: 0.4 }">{{ plan.name }}</div>
61
+ <div class="font-black text-2xl tracking-tight">{{ plan.price }}</div>
62
+ </div>
63
+ </th>
64
+ </tr>
65
+ </thead>
66
+ <tbody>
67
+ <tr v-for="(feat, fi) in (items[0]?.features || [])" :key="fi" class="border-b" :style="{ borderColor: ca(textColor,'08') }">
68
+ <td class="py-4 pr-6 text-sm" :style="{ opacity: 0.6 }">{{ typeof feat === 'object' ? (feat.fr || feat) : feat }}</td>
69
+ <td v-for="(plan, pi) in items" :key="pi" class="py-4 px-4 text-center border-l" :style="{ borderColor: ca(textColor,'08') }">
70
+ <span class="text-[11px] font-black">✓</span>
71
+ </td>
72
+ </tr>
73
+ </tbody>
74
+ </table>
75
+ </div>
76
+
77
+ </div>
78
+ </section>
79
+ </template>
80
+
81
+ <script setup>
82
+ const ca = (c, h) => {
83
+ const m = c?.match(/rgba\((var\([^)]+\)),\s*[\d.]+\)/);
84
+ return m ? `rgba(${m[1]}, ${Math.round(parseInt(h, 16) / 255 * 1e3) / 1e3})` : (c || "") + h;
85
+ };
86
+ import { ref, computed, onMounted } from "vue";
87
+ const props = defineProps({
88
+ layout: { type: String, default: "cards" },
89
+ badge: { type: String, default: "" },
90
+ title: { type: String, default: "" },
91
+ titleTag: { type: String, default: "h2" },
92
+ description: { type: String, default: "" },
93
+ items: { type: Array, default: () => [] },
94
+ bgColor: { type: String, default: "rgba(var(--body-bg), 1)" },
95
+ textColor: { type: String, default: "rgba(var(--text-color), 1)" },
96
+ spacing: { type: String, default: "normal" },
97
+ radius: { type: String, default: "sharp" },
98
+ textDirection: { type: String, default: "ltr" },
99
+ contentWidth: { type: String, default: "normal" },
100
+ textSize: { type: String, default: "md" },
101
+ paddingTop: { type: String, default: "" },
102
+ paddingBottom: { type: String, default: "" },
103
+ marginTop: { 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(([e]) => {
111
+ if (e.isIntersecting) {
112
+ isVisible.value = true;
113
+ obs.disconnect();
114
+ }
115
+ }, { threshold: 0.06 });
116
+ if (sectionEl.value) obs.observe(sectionEl.value);
117
+ setTimeout(() => {
118
+ if (!isVisible.value) isVisible.value = true;
119
+ }, 300);
120
+ });
121
+ const sectionPad = computed(() => ({ compact: "py-8 px-4 md:px-8", normal: "py-14 px-8 md:px-16", spacious: "py-24 px-8 md:px-16" })[props.spacing] || "py-14 px-8 md:px-16");
122
+ const containerClass = computed(() => ({ narrow: "max-w-3xl mx-auto", normal: "max-w-6xl mx-auto", wide: "max-w-7xl mx-auto", full: "max-w-full" })[props.contentWidth] || "max-w-6xl mx-auto");
123
+ const titleFontSize = computed(() => ({ sm: "clamp(1rem,1.5vw,1.3rem)", md: "clamp(1.5rem,2.5vw,2rem)", lg: "clamp(2.5rem,6vw,5rem)" })[props.textSize] || "clamp(1.5rem,2.5vw,2rem)");
124
+ const bodyClass = computed(() => ({ sm: "text-xs", md: "text-sm", lg: "text-base" })[props.textSize] || "text-sm");
125
+ const SM = { none: "0", xs: "8px", sm: "24px", md: "48px", lg: "80px", xl: "128px", "-xs": "-8px", "-sm": "-24px", "-md": "-48px", "-lg": "-80px", "-xl": "-128px" };
126
+ const spacingStyle = computed(() => {
127
+ const s = {};
128
+ if (props.paddingTop && SM[props.paddingTop]) s.paddingTop = SM[props.paddingTop];
129
+ if (props.paddingBottom && SM[props.paddingBottom]) s.paddingBottom = SM[props.paddingBottom];
130
+ if (props.marginTop && SM[props.marginTop]) s.marginTop = SM[props.marginTop];
131
+ if (props.marginBottom && SM[props.marginBottom]) s.marginBottom = SM[props.marginBottom];
132
+ if (props.paddingX && SM[props.paddingX]) {
133
+ const v = SM[props.paddingX];
134
+ if (v.startsWith("-")) {
135
+ s.marginLeft = v;
136
+ s.marginRight = v;
137
+ } else {
138
+ s.paddingLeft = v;
139
+ s.paddingRight = v;
140
+ }
141
+ }
142
+ return s;
143
+ });
144
+ </script>
145
+
146
+ <style scoped>
147
+ .sv-out{opacity:0;transform:translateY(16px)}.sv-in{opacity:1;transform:none;transition:opacity .6s ease,transform .6s ease}
148
+ </style>
@@ -0,0 +1,3 @@
1
+ declare const _default: typeof __VLS_export;
2
+ export default _default;
3
+ declare const __VLS_export: any;