astro-component-kit 0.3.1 → 0.4.1

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 (249) hide show
  1. package/README.md +133 -36
  2. package/package.json +17 -4
  3. package/src/components/index.ts +18 -202
  4. package/src/components/lib/Galaxy3D.astro +249 -137
  5. package/src/components/lib/app/AppSidebar.astro +249 -0
  6. package/src/components/lib/app/AppTopbar.astro +284 -0
  7. package/src/components/lib/app/ArrivalsBoard.astro +176 -0
  8. package/src/components/lib/app/AuthCard.astro +213 -0
  9. package/src/components/lib/app/DataTable.astro +346 -0
  10. package/src/components/lib/app/GuestProfileCard.astro +133 -0
  11. package/src/components/lib/app/HousekeepingBoard.astro +189 -0
  12. package/src/components/lib/app/PageHeader.astro +157 -0
  13. package/src/components/lib/app/PlanUsageCard.astro +112 -0
  14. package/src/components/lib/app/RoomRack.astro +207 -0
  15. package/src/components/lib/app/SettingsSection.astro +96 -0
  16. package/src/components/lib/app/SwitchRow.astro +71 -0
  17. package/src/components/lib/app/TeamMembers.astro +190 -0
  18. package/src/components/lib/app/app-icons.ts +52 -0
  19. package/src/components/lib/app/index.ts +19 -0
  20. package/src/components/lib/buttons/NeonBorderButton.astro +162 -0
  21. package/src/components/lib/buttons/RippleButton.astro +199 -0
  22. package/src/components/lib/buttons/SlideRevealButton.astro +159 -0
  23. package/src/components/lib/buttons/index.ts +4 -0
  24. package/src/components/lib/cards/GithubRepoCard.astro +176 -0
  25. package/src/components/lib/cards/PricingCompareMiniCard.astro +196 -0
  26. package/src/components/lib/cards/StatsTrendCard.astro +149 -0
  27. package/src/components/lib/cards/index.ts +3 -0
  28. package/src/components/lib/charts/ActivityRings.astro +105 -0
  29. package/src/components/lib/charts/AreaChart.astro +4 -4
  30. package/src/components/lib/charts/BarChart.astro +3 -3
  31. package/src/components/lib/charts/BoxPlotChart.astro +109 -0
  32. package/src/components/lib/charts/BubbleChart.astro +101 -0
  33. package/src/components/lib/charts/BulletChart.astro +160 -0
  34. package/src/components/lib/charts/BumpChart.astro +92 -0
  35. package/src/components/lib/charts/CandlestickChart.astro +105 -0
  36. package/src/components/lib/charts/ChartFrame.astro +373 -0
  37. package/src/components/lib/charts/DivergingBarChart.astro +98 -0
  38. package/src/components/lib/charts/DonutChart.astro +2 -2
  39. package/src/components/lib/charts/DumbbellChart.astro +92 -0
  40. package/src/components/lib/charts/ForecastLineChart.astro +125 -0
  41. package/src/components/lib/charts/FunnelChart.astro +122 -0
  42. package/src/components/lib/charts/GaugeChart.astro +111 -0
  43. package/src/components/lib/charts/GoalProgressChart.astro +97 -0
  44. package/src/components/lib/charts/GroupedBarChart.astro +125 -0
  45. package/src/components/lib/charts/HeatmapChart.astro +131 -0
  46. package/src/components/lib/charts/HistogramChart.astro +111 -0
  47. package/src/components/lib/charts/HorizontalBarChart.astro +155 -0
  48. package/src/components/lib/charts/KpiStatTile.astro +116 -0
  49. package/src/components/lib/charts/KpiTrendGrid.astro +60 -0
  50. package/src/components/lib/charts/LineChart.astro +160 -0
  51. package/src/components/lib/charts/LinearProgressGauge.astro +136 -0
  52. package/src/components/lib/charts/LollipopChart.astro +100 -0
  53. package/src/components/lib/charts/MeterGauge.astro +122 -0
  54. package/src/components/lib/charts/MetricDeltaTable.astro +125 -0
  55. package/src/components/lib/charts/PieChart.astro +135 -0
  56. package/src/components/lib/charts/PolarAreaChart.astro +85 -0
  57. package/src/components/lib/charts/PopulationPyramid.astro +122 -0
  58. package/src/components/lib/charts/PunchCardChart.astro +89 -0
  59. package/src/components/lib/charts/RadarChart.astro +94 -0
  60. package/src/components/lib/charts/RadarMetricChart.astro +168 -0
  61. package/src/components/lib/charts/RadialBarChart.astro +124 -0
  62. package/src/components/lib/charts/RangeAreaChart.astro +113 -0
  63. package/src/components/lib/charts/RetentionCohortTable.astro +126 -0
  64. package/src/components/lib/charts/SankeyChart.astro +128 -0
  65. package/src/components/lib/charts/ScatterPlot.astro +139 -0
  66. package/src/components/lib/charts/SegmentedBarChart.astro +119 -0
  67. package/src/components/lib/charts/SlopeChart.astro +105 -0
  68. package/src/components/lib/charts/SparklineChart.astro +87 -0
  69. package/src/components/lib/charts/StackedAreaChart.astro +130 -0
  70. package/src/components/lib/charts/StackedBarChart.astro +136 -0
  71. package/src/components/lib/charts/SunburstChart.astro +103 -0
  72. package/src/components/lib/charts/TreemapChart.astro +94 -0
  73. package/src/components/lib/charts/WaffleChart.astro +121 -0
  74. package/src/components/lib/charts/WaterfallChart.astro +118 -0
  75. package/src/components/lib/charts/chart-tooltip.ts +75 -0
  76. package/src/components/lib/charts/chart-utils.ts +271 -0
  77. package/src/components/lib/charts/index.ts +46 -0
  78. package/src/components/lib/data/ActivityHeatmapGrid.astro +157 -0
  79. package/src/components/lib/data/MetricComparisonRow.astro +109 -0
  80. package/src/components/lib/data/StatusTimelineFeed.astro +168 -0
  81. package/src/components/lib/data/index.ts +3 -0
  82. package/src/components/lib/ecommerce/CartDrawer.astro +483 -0
  83. package/src/components/lib/ecommerce/CartPageList.astro +290 -0
  84. package/src/components/lib/ecommerce/CartToast.astro +276 -0
  85. package/src/components/lib/ecommerce/CategoryTiles.astro +143 -0
  86. package/src/components/lib/ecommerce/CheckoutForm.astro +423 -0
  87. package/src/components/lib/ecommerce/CountdownBanner.astro +230 -0
  88. package/src/components/lib/ecommerce/FilterBar.astro +278 -0
  89. package/src/components/lib/ecommerce/FilterSidebar.astro +405 -0
  90. package/src/components/lib/ecommerce/LiveActivityToast.astro +213 -0
  91. package/src/components/lib/ecommerce/OrderSummary.astro +295 -0
  92. package/src/components/lib/ecommerce/ProductBuyBox.astro +247 -0
  93. package/src/components/lib/ecommerce/ProductCardMinimal.astro +229 -0
  94. package/src/components/lib/ecommerce/ProductGallery.astro +145 -0
  95. package/src/components/lib/ecommerce/ProductGrid.astro +118 -0
  96. package/src/components/lib/ecommerce/ProductMedia.astro +113 -0
  97. package/src/components/lib/ecommerce/ProductRating.astro +78 -0
  98. package/src/components/lib/ecommerce/ProductToolbar.astro +93 -0
  99. package/src/components/lib/ecommerce/PromoBar.astro +145 -0
  100. package/src/components/lib/ecommerce/StockIndicator.astro +127 -0
  101. package/src/components/lib/ecommerce/StoreHeader.astro +446 -0
  102. package/src/components/lib/ecommerce/StoreHero.astro +260 -0
  103. package/src/components/lib/ecommerce/StoreProductCard.astro +307 -0
  104. package/src/components/lib/ecommerce/TrustBadges.astro +101 -0
  105. package/src/components/lib/ecommerce/VariantPicker.astro +145 -0
  106. package/src/components/lib/ecommerce/cart-store.ts +151 -0
  107. package/src/components/lib/ecommerce/catalog-filter.ts +62 -0
  108. package/src/components/lib/ecommerce/index.ts +34 -0
  109. package/src/components/lib/ecommerce/product-icons.ts +28 -0
  110. package/src/components/lib/ecommerce/types.ts +70 -0
  111. package/src/components/lib/feedback/ProgressRingCounter.astro +118 -0
  112. package/src/components/lib/feedback/ToastNotificationGroup.astro +173 -0
  113. package/src/components/lib/feedback/index.ts +2 -0
  114. package/src/components/lib/forms/AnimatedInput.astro +4 -2
  115. package/src/components/lib/forms/CustomRadioGroup.astro +3 -1
  116. package/src/components/lib/forms/GlassInlineToggleGroup.astro +161 -0
  117. package/src/components/lib/forms/PasswordStrengthInput.astro +242 -0
  118. package/src/components/lib/forms/PhoneCountryInput.astro +145 -0
  119. package/src/components/lib/forms/TagsAutocompleteInput.astro +249 -0
  120. package/src/components/lib/forms/index.ts +5 -0
  121. package/src/components/lib/index.ts +2 -0
  122. package/src/components/lib/layout/BentoGrid3.astro +6 -4
  123. package/src/components/lib/layout/CenterFixedBox.astro +2 -2
  124. package/src/components/lib/layout/CenteredContainer.astro +4 -2
  125. package/src/components/lib/layout/ClusterHorizontal.astro +2 -2
  126. package/src/components/lib/layout/DiagonalSectionDivider.astro +69 -0
  127. package/src/components/lib/layout/FeatureHero.astro +4 -2
  128. package/src/components/lib/layout/FooterCenteredMinimal.astro +4 -2
  129. package/src/components/lib/layout/FullBleedSection.astro +2 -2
  130. package/src/components/lib/layout/GalleryGrid4.astro +2 -2
  131. package/src/components/lib/layout/HolyGrailGrid.astro +127 -0
  132. package/src/components/lib/layout/LandingSectionSplit.astro +5 -3
  133. package/src/components/lib/layout/MasonryGrid.astro +2 -2
  134. package/src/components/lib/layout/MinimalFooterGrid.astro +1 -1
  135. package/src/components/lib/layout/ScrollSnapSection.astro +1 -1
  136. package/src/components/lib/layout/SidebarLayoutWrapper.astro +4 -2
  137. package/src/components/lib/layout/SplitScreenLayout.astro +3 -3
  138. package/src/components/lib/layout/StepLayoutVertical.astro +1 -1
  139. package/src/components/lib/layout/StickySidebarWrapper.astro +4 -2
  140. package/src/components/lib/layout/index.ts +3 -1
  141. package/src/components/lib/navigation/QuickJumpDock.astro +125 -0
  142. package/src/components/lib/navigation/StepBreadcrumbBar.astro +151 -0
  143. package/src/components/lib/navigation/SubNavbarPills.astro +102 -0
  144. package/src/components/lib/navigation/index.ts +3 -0
  145. package/src/components/lib/overlays/CookiePreferencesDrawer.astro +226 -0
  146. package/src/components/lib/overlays/FloatingFeedbackWidget.astro +203 -0
  147. package/src/components/lib/overlays/ImageCompareSlider.astro +172 -0
  148. package/src/components/lib/overlays/index.ts +3 -0
  149. package/src/components/lib/sections/AgencyContactBrief.astro +194 -0
  150. package/src/components/lib/sections/AgencyFooterCta.astro +94 -0
  151. package/src/components/lib/sections/AgencyHeader.astro +161 -0
  152. package/src/components/lib/sections/AgencyHeroKinetic.astro +172 -0
  153. package/src/components/lib/sections/AgencyServicesList.astro +113 -0
  154. package/src/components/lib/sections/AgencyTeamGrid.astro +111 -0
  155. package/src/components/lib/sections/AgencyWorkGrid.astro +147 -0
  156. package/src/components/lib/sections/BlogGridSection.astro +133 -0
  157. package/src/components/lib/sections/ButtonsShowcase.astro +20 -18
  158. package/src/components/lib/sections/CardsShowcase.astro +28 -26
  159. package/src/components/lib/sections/CareersOpenings.astro +141 -0
  160. package/src/components/lib/sections/ChangelogSection.astro +151 -0
  161. package/src/components/lib/sections/ContactGlassForm.astro +10 -8
  162. package/src/components/lib/sections/ContactSplitMap.astro +141 -0
  163. package/src/components/lib/sections/CtaAppDownload.astro +13 -11
  164. package/src/components/lib/sections/CtaGlowBanner.astro +12 -10
  165. package/src/components/lib/sections/CtaNewsletterSection.astro +165 -0
  166. package/src/components/lib/sections/CtaSplitImage.astro +149 -0
  167. package/src/components/lib/sections/DataShowcase.astro +23 -21
  168. package/src/components/lib/sections/FaqAccordionSection.astro +8 -8
  169. package/src/components/lib/sections/FaqTwoColumn.astro +121 -0
  170. package/src/components/lib/sections/FeatureAlternatingRows.astro +192 -0
  171. package/src/components/lib/sections/FeatureBentoGrid.astro +13 -11
  172. package/src/components/lib/sections/FeatureIconGrid.astro +155 -0
  173. package/src/components/lib/sections/FeatureTabsShowcase.astro +182 -0
  174. package/src/components/lib/sections/FeatureTimelineFlow.astro +8 -6
  175. package/src/components/lib/sections/FeedbackShowcase.astro +15 -13
  176. package/src/components/lib/sections/FooterMegaSection.astro +157 -0
  177. package/src/components/lib/sections/FormsShowcase.astro +22 -20
  178. package/src/components/lib/sections/HeroDashboardPreview.astro +182 -0
  179. package/src/components/lib/sections/HeroGlass.astro +22 -20
  180. package/src/components/lib/sections/HeroGradient.astro +15 -13
  181. package/src/components/lib/sections/HeroMinimalCentered.astro +175 -0
  182. package/src/components/lib/sections/HeroShowcaseSplit.astro +306 -0
  183. package/src/components/lib/sections/HeroSplit.astro +13 -11
  184. package/src/components/lib/sections/HeroTerminal.astro +253 -0
  185. package/src/components/lib/sections/HeroWaitlist.astro +231 -0
  186. package/src/components/lib/sections/HowItWorksSteps.astro +180 -0
  187. package/src/components/lib/sections/IntegrationsGrid.astro +167 -0
  188. package/src/components/lib/sections/LayoutShowcase.astro +21 -19
  189. package/src/components/lib/sections/LogoCloudSection.astro +96 -0
  190. package/src/components/lib/sections/LogoWallGrid.astro +93 -0
  191. package/src/components/lib/sections/NavigationShowcase.astro +15 -13
  192. package/src/components/lib/sections/OverlaysShowcase.astro +21 -19
  193. package/src/components/lib/sections/PricingComparisonMatrix.astro +15 -13
  194. package/src/components/lib/sections/PricingSinglePlan.astro +159 -0
  195. package/src/components/lib/sections/PricingTierCards.astro +11 -9
  196. package/src/components/lib/sections/PricingToggleCards.astro +171 -0
  197. package/src/components/lib/sections/ShopCategoryHero.astro +103 -0
  198. package/src/components/lib/sections/ShopCollectionMosaic.astro +131 -0
  199. package/src/components/lib/sections/ShopFooterWordmark.astro +100 -0
  200. package/src/components/lib/sections/ShopHeaderEditorial.astro +396 -0
  201. package/src/components/lib/sections/ShopHeroEditorial.astro +151 -0
  202. package/src/components/lib/sections/ShopLookbook.astro +250 -0
  203. package/src/components/lib/sections/ShopNewsletterEditorial.astro +128 -0
  204. package/src/components/lib/sections/ShopProductRail.astro +150 -0
  205. package/src/components/lib/sections/ShopReviewsEditorial.astro +133 -0
  206. package/src/components/lib/sections/ShopValuesMarquee.astro +91 -0
  207. package/src/components/lib/sections/StatsCounterBand.astro +124 -0
  208. package/src/components/lib/sections/StatsDashboardSection.astro +85 -0
  209. package/src/components/lib/sections/StatsMetricShowcase.astro +9 -7
  210. package/src/components/lib/sections/TeamGridSection.astro +12 -10
  211. package/src/components/lib/sections/TestimonialCarousel.astro +167 -0
  212. package/src/components/lib/sections/TestimonialFeatured.astro +122 -0
  213. package/src/components/lib/sections/TestimonialGridSection.astro +188 -0
  214. package/src/components/lib/sections/TestimonialWallGlass.astro +11 -9
  215. package/src/components/lib/sections/TypographyShowcase.astro +10 -8
  216. package/src/components/lib/sections/index.ts +39 -0
  217. package/src/components/lib/templates/ecommerce/StoreCategoryPage.astro +160 -0
  218. package/src/components/lib/templates/ecommerce/StoreCheckoutPage.astro +98 -0
  219. package/src/components/lib/templates/ecommerce/StoreHomePage.astro +191 -0
  220. package/src/components/lib/templates/ecommerce/StoreLayout.astro +169 -0
  221. package/src/components/lib/templates/ecommerce/StoreProductPage.astro +165 -0
  222. package/src/components/lib/templates/ecommerce/data.ts +187 -0
  223. package/src/components/lib/templates/ecommerce/index.ts +14 -0
  224. package/src/components/lib/templates/ecommerce-editorial/EditorialBagPage.astro +52 -0
  225. package/src/components/lib/templates/ecommerce-editorial/EditorialCheckoutPage.astro +123 -0
  226. package/src/components/lib/templates/ecommerce-editorial/EditorialHomePage.astro +102 -0
  227. package/src/components/lib/templates/ecommerce-editorial/EditorialLayout.astro +216 -0
  228. package/src/components/lib/templates/ecommerce-editorial/EditorialProductPage.astro +205 -0
  229. package/src/components/lib/templates/ecommerce-editorial/EditorialShopPage.astro +112 -0
  230. package/src/components/lib/templates/ecommerce-editorial/data.ts +125 -0
  231. package/src/components/lib/templates/ecommerce-editorial/index.ts +16 -0
  232. package/src/components/lib/templates/saas-hotel/AppLayout.astro +83 -0
  233. package/src/components/lib/templates/saas-hotel/DashboardPage.astro +66 -0
  234. package/src/components/lib/templates/saas-hotel/GuestsPage.astro +65 -0
  235. package/src/components/lib/templates/saas-hotel/LandingPage.astro +213 -0
  236. package/src/components/lib/templates/saas-hotel/ReportsPage.astro +63 -0
  237. package/src/components/lib/templates/saas-hotel/ReservationsPage.astro +49 -0
  238. package/src/components/lib/templates/saas-hotel/RoomsPage.astro +48 -0
  239. package/src/components/lib/templates/saas-hotel/SettingsPage.astro +115 -0
  240. package/src/components/lib/templates/saas-hotel/SignInPage.astro +50 -0
  241. package/src/components/lib/templates/saas-hotel/data.ts +229 -0
  242. package/src/components/lib/templates/saas-hotel/index.ts +19 -0
  243. package/src/components/lib/templates/saas-hotel/lobby-theme.css +90 -0
  244. package/src/components/lib/typography/AnimatedCounterText.astro +102 -0
  245. package/src/components/lib/typography/CurvedPathHeading.astro +86 -0
  246. package/src/components/lib/typography/GradientTextMarquee.astro +105 -0
  247. package/src/components/lib/typography/HackerDecodeText.astro +85 -0
  248. package/src/components/lib/typography/index.ts +4 -0
  249. package/src/styles/global.css +113 -1
@@ -0,0 +1,141 @@
1
+ ---
2
+ /**
3
+ * ContactSplitMap — Contact section with office cards and contact channels on
4
+ * one side and a stylised SVG dot-map with location pins on the other.
5
+ *
6
+ * @param {string} title - Section heading.
7
+ * @param {string} subtitle - Section description.
8
+ * @param {Array<{city: string, address: string, x: number, y: number}>} offices - Offices with map coords (0–100).
9
+ * @param {Array<{label: string, value: string, href: string, icon: 'mail'|'phone'|'chat'}>} channels - Contact channels.
10
+ * @param {string} variant - Map style (dots|grid|plain).
11
+ * @param {string} size - Vertical rhythm (sm|md|lg).
12
+ * @param {string} className - Extra CSS class.
13
+ */
14
+ interface Office { city: string; address: string; x: number; y: number }
15
+ interface Channel { label: string; value: string; href: string; icon: 'mail' | 'phone' | 'chat' }
16
+ interface Props {
17
+ title?: string;
18
+ subtitle?: string;
19
+ offices?: Office[];
20
+ channels?: Channel[];
21
+ variant?: 'dots' | 'grid' | 'plain';
22
+ size?: 'sm' | 'md' | 'lg';
23
+ className?: string;
24
+ }
25
+
26
+ const {
27
+ title = 'Talk to a human',
28
+ subtitle = 'We’re distributed across three time zones, so someone is usually awake.',
29
+ offices = [
30
+ { city: 'San Francisco', address: '548 Market St, CA 94104', x: 17, y: 40 },
31
+ { city: 'Barcelona', address: 'Carrer de Pallars 99, 08018', x: 49, y: 36 },
32
+ { city: 'Singapore', address: '1 Raffles Place, 048616', x: 77, y: 58 },
33
+ ],
34
+ channels = [
35
+ { label: 'Email', value: 'hello@example.com', href: 'mailto:hello@example.com', icon: 'mail' },
36
+ { label: 'Phone', value: '+1 (555) 010-2030', href: 'tel:+15550102030', icon: 'phone' },
37
+ { label: 'Live chat', value: 'Mon–Fri, 9am–6pm', href: '#', icon: 'chat' },
38
+ ],
39
+ variant = 'dots',
40
+ size = 'md',
41
+ className = '',
42
+ } = Astro.props;
43
+
44
+ const id = `csm-${Math.random().toString(36).slice(2, 8)}`;
45
+ const icon = {
46
+ mail: '<rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 6L2 7"/>',
47
+ phone: '<path d="M22 16.9v3a2 2 0 0 1-2.2 2 19.8 19.8 0 0 1-8.6-3.1 19.5 19.5 0 0 1-6-6A19.8 19.8 0 0 1 2.1 4.2 2 2 0 0 1 4.1 2h3a2 2 0 0 1 2 1.7c.1 1 .4 1.9.7 2.8a2 2 0 0 1-.5 2.1L8 9.9a16 16 0 0 0 6 6l1.3-1.3a2 2 0 0 1 2.1-.4c.9.3 1.8.6 2.8.7a2 2 0 0 1 1.7 2z"/>',
48
+ chat: '<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>',
49
+ };
50
+
51
+ // Very rough continent silhouettes as a coarse dot grid (deterministic).
52
+ const land = (x: number, y: number) =>
53
+ (x > 8 && x < 30 && y > 22 && y < 52) ||
54
+ (x > 22 && x < 34 && y > 52 && y < 88) ||
55
+ (x > 44 && x < 58 && y > 22 && y < 46) ||
56
+ (x > 46 && x < 60 && y > 46 && y < 80) ||
57
+ (x > 58 && x < 88 && y > 20 && y < 56) ||
58
+ (x > 78 && x < 92 && y > 68 && y < 84);
59
+ const dots: Array<[number, number]> = [];
60
+ for (let y = 6; y < 94; y += 3) for (let x = 2; x < 98; x += 2) if (land(x, y)) dots.push([x, y]);
61
+
62
+ // The map box is 16:11, so the SVG uses the same aspect ratio and scales 0–100 coords onto it.
63
+ const VW = 160;
64
+ const VH = 110;
65
+ const SX = VW / 100;
66
+ const SY = VH / 100;
67
+ ---
68
+
69
+ <div class="cq-contact-split-map-cq">
70
+ <section class={`csm csm--${variant} csm--${size} ${className}`} aria-labelledby={id}>
71
+ <div class="csm__inner">
72
+ <div class="csm__info">
73
+ <h2 id={id} class="csm__title">{title}</h2>
74
+ <p class="csm__subtitle">{subtitle}</p>
75
+ <ul class="csm__channels">
76
+ {channels.map((c) => (
77
+ <li>
78
+ <a class="csm__channel" href={c.href}>
79
+ <span class="csm__icon" aria-hidden="true"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" set:html={icon[c.icon]}></svg></span>
80
+ <span><span class="csm__label">{c.label}</span><span class="csm__value">{c.value}</span></span>
81
+ </a>
82
+ </li>
83
+ ))}
84
+ </ul>
85
+ <ul class="csm__offices">
86
+ {offices.map((o) => (
87
+ <li><address><strong>{o.city}</strong>{o.address}</address></li>
88
+ ))}
89
+ </ul>
90
+ </div>
91
+ <div class="csm__map" role="img" aria-label={`Map showing offices in ${offices.map((o) => o.city).join(', ')}`}>
92
+ <svg viewBox={`0 0 ${VW} ${VH}`} aria-hidden="true">
93
+ {variant !== 'plain' && dots.map(([x, y]) => (
94
+ variant === 'grid'
95
+ ? <rect x={x * SX - 0.8} y={y * SY - 0.8} width="1.6" height="1.6" fill="rgb(var(--ink-rgb, 255 255 255) / 0.14)" />
96
+ : <circle cx={x * SX} cy={y * SY} r="0.75" fill="rgb(var(--ink-rgb, 255 255 255) / 0.18)" />
97
+ ))}
98
+ {offices.map((o) => (
99
+ <g>
100
+ <circle class="csm__ping" cx={o.x * SX} cy={o.y * SY} r="3" fill="none" stroke="var(--c-primary-light, #d4a017)" stroke-width="0.4" />
101
+ <circle cx={o.x * SX} cy={o.y * SY} r="1.6" fill="var(--c-primary-light, #d4a017)" />
102
+ </g>
103
+ ))}
104
+ </svg>
105
+ {offices.map((o) => (
106
+ <span class="csm__pin" style={`left:${o.x}%; top:${o.y}%`} aria-hidden="true">{o.city}</span>
107
+ ))}
108
+ </div>
109
+ </div>
110
+ </section>
111
+ </div>
112
+ <style>
113
+ .cq-contact-split-map-cq { container-type: inline-size; }
114
+ .csm {
115
+ --csm-py: 5.5rem;
116
+ padding: var(--csm-py) var(--sp-6, 1.5rem);
117
+ background: var(--c-bg, #050505);
118
+ }
119
+ .csm--sm { --csm-py: 3.5rem; }
120
+ .csm--lg { --csm-py: 7.5rem; }
121
+ .csm__inner { max-width: 1180px; margin: 0 auto; display: grid; grid-template-columns: 1fr 1.3fr; gap: clamp(2rem, 5vw, 4rem); align-items: center; }
122
+ .csm__title { margin: 0 0 0.75rem; font-size: clamp(1.9rem, 4vw, 2.75rem); font-weight: 800; letter-spacing: -0.03em; color: var(--c-text-1, #fff); }
123
+ .csm__subtitle { margin: 0 0 2rem; font-size: 1.0625rem; line-height: 1.6; color: var(--c-text-2, #a3a3a3); }
124
+ .csm__channels { list-style: none; margin: 0 0 2rem; padding: 0; display: grid; gap: 0.6rem; }
125
+ .csm__channel { display: flex; align-items: center; gap: 0.9rem; min-height: 60px; padding: 0.6rem 0.9rem; border-radius: var(--r-md, 14px); border: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.07)); background: rgb(var(--ink-rgb, 255 255 255) / 0.02); color: inherit; text-decoration: none; transition: border-color 0.2s ease, background 0.2s ease; }
126
+ .csm__channel:hover { border-color: rgb(var(--ink-rgb, 255 255 255) / 0.2); background: rgb(var(--ink-rgb, 255 255 255) / 0.04); }
127
+ .csm__channel:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; }
128
+ .csm__icon { display: grid; place-items: center; width: 40px; height: 40px; border-radius: 10px; background: var(--c-primary-glow, color-mix(in srgb, var(--c-primary, #b8860b) 14%, transparent)); color: var(--c-primary-light, #d4a017); flex-shrink: 0; }
129
+ .csm__label { display: block; font-size: 0.75rem; color: var(--c-text-3, #8a8a8a); }
130
+ .csm__value { display: block; font-size: 0.9375rem; font-weight: 600; color: var(--c-text-1, #fff); }
131
+ .csm__offices { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem; }
132
+ .csm__offices address { font-style: normal; font-size: 0.8125rem; line-height: 1.5; color: var(--c-text-2, #a3a3a3); }
133
+ .csm__offices strong { display: block; font-size: 0.9375rem; color: var(--c-text-1, #fff); }
134
+ .csm__map { position: relative; aspect-ratio: 16 / 11; border-radius: var(--r-xl, 24px); border: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.07)); background: radial-gradient(circle at 50% 40%, rgb(var(--ink-rgb, 255 255 255) / 0.04), transparent 70%), var(--c-bg-1, #0a0a0a); overflow: hidden; }
135
+ .csm__map svg { position: absolute; inset: 0; width: 100%; height: 100%; }
136
+ .csm__ping { transform-box: fill-box; transform-origin: center; animation: csm-ping 2.4s ease-out infinite; }
137
+ @keyframes csm-ping { from { transform: scale(0.6); opacity: 1; } to { transform: scale(2.4); opacity: 0; } }
138
+ .csm__pin { position: absolute; transform: translate(-50%, calc(-100% - 12px)); padding: 0.2rem 0.55rem; border-radius: 6px; background: rgba(10, 10, 12, 0.85); border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.12); font-size: 0.6875rem; font-weight: 600; color: var(--c-text-1, #fff); white-space: nowrap; }
139
+ @container (max-width: 860px) { .csm__inner { grid-template-columns: 1fr; } }
140
+ @media (prefers-reduced-motion: reduce) { .csm__ping { animation: none; } }
141
+ </style>
@@ -21,6 +21,7 @@ const {
21
21
  } = Astro.props;
22
22
  ---
23
23
 
24
+ <div class="cq-cta-app-download-cq">
24
25
  <section class={`app-download-section ${className}`} aria-labelledby="app-title">
25
26
  <div class="container">
26
27
  <div class="app-card">
@@ -72,8 +73,9 @@ const {
72
73
  </div>
73
74
  </div>
74
75
  </section>
75
-
76
+ </div>
76
77
  <style>
78
+ .cq-cta-app-download-cq { container-type: inline-size; }
77
79
  .app-download-section {
78
80
  padding: var(--sp-20, 5rem) var(--sp-6, 1.5rem);
79
81
  background: var(--c-bg-primary, #030712);
@@ -86,7 +88,7 @@ const {
86
88
 
87
89
  .app-card {
88
90
  background: linear-gradient(135deg, rgba(15, 23, 42, 0.8) 0%, rgba(10, 15, 30, 0.9) 100%);
89
- border: 1px solid rgba(255, 255, 255, 0.1);
91
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.1);
90
92
  border-radius: var(--r-2xl, 24px);
91
93
  padding: clamp(2rem, 5vw, 4rem);
92
94
  display: grid;
@@ -103,7 +105,7 @@ const {
103
105
  font-weight: 700;
104
106
  letter-spacing: 0.06em;
105
107
  text-transform: uppercase;
106
- background: rgba(184, 134, 11, 0.12);
108
+ background: color-mix(in srgb, var(--c-primary, #b8860b) 12%, transparent);
107
109
  color: #eab308;
108
110
  border: 1px solid rgba(234, 179, 8, 0.25);
109
111
  margin-bottom: var(--sp-4, 1rem);
@@ -134,8 +136,8 @@ const {
134
136
 
135
137
  .feat-pill {
136
138
  padding: 0.4rem 0.85rem;
137
- background: rgba(255, 255, 255, 0.05);
138
- border: 1px solid rgba(255, 255, 255, 0.08);
139
+ background: rgb(var(--ink-rgb, 255 255 255) / 0.05);
140
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.08);
139
141
  border-radius: 9999px;
140
142
  font-size: 0.82rem;
141
143
  color: #cbd5e1;
@@ -153,7 +155,7 @@ const {
153
155
  gap: 0.75rem;
154
156
  padding: 0.65rem 1.25rem;
155
157
  background: rgba(0, 0, 0, 0.6);
156
- border: 1px solid rgba(255, 255, 255, 0.15);
158
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.15);
157
159
  border-radius: 12px;
158
160
  color: #fff;
159
161
  text-decoration: none;
@@ -161,8 +163,8 @@ const {
161
163
  }
162
164
 
163
165
  .store-btn:hover {
164
- background: rgba(255, 255, 255, 0.1);
165
- border-color: rgba(255, 255, 255, 0.3);
166
+ background: rgb(var(--ink-rgb, 255 255 255) / 0.1);
167
+ border-color: rgb(var(--ink-rgb, 255 255 255) / 0.3);
166
168
  }
167
169
 
168
170
  .store-btn__text {
@@ -192,7 +194,7 @@ const {
192
194
  width: 250px;
193
195
  height: 400px;
194
196
  background: #020617;
195
- border: 3px solid rgba(255, 255, 255, 0.2);
197
+ border: 3px solid rgb(var(--ink-rgb, 255 255 255) / 0.2);
196
198
  border-radius: 36px;
197
199
  padding: 12px;
198
200
  box-shadow: 0 20px 50px -10px rgba(0, 0, 0, 0.8);
@@ -244,13 +246,13 @@ const {
244
246
 
245
247
  .phone-card-mini {
246
248
  padding: 0.5rem;
247
- background: rgba(255, 255, 255, 0.05);
249
+ background: rgb(var(--ink-rgb, 255 255 255) / 0.05);
248
250
  border-radius: 8px;
249
251
  font-size: 0.72rem;
250
252
  color: #cbd5e1;
251
253
  }
252
254
 
253
- @media (max-width: 960px) {
255
+ @container (max-width: 960px) {
254
256
  .app-card {
255
257
  grid-template-columns: 1fr;
256
258
  }
@@ -27,6 +27,7 @@ const {
27
27
  } = Astro.props;
28
28
  ---
29
29
 
30
+ <div class="cq-cta-glow-banner-cq">
30
31
  <section class={`cta-banner-section ${className}`} aria-labelledby="cta-title">
31
32
  <div class="container">
32
33
  <div class="cta-banner-card">
@@ -75,8 +76,9 @@ const {
75
76
  </div>
76
77
  </div>
77
78
  </section>
78
-
79
+ </div>
79
80
  <style>
81
+ .cq-cta-glow-banner-cq { container-type: inline-size; }
80
82
  .cta-banner-section {
81
83
  padding: var(--sp-20, 5rem) var(--sp-6, 1.5rem);
82
84
  background: var(--c-bg-primary, #030712);
@@ -127,7 +129,7 @@ const {
127
129
  font-weight: 700;
128
130
  letter-spacing: 0.06em;
129
131
  text-transform: uppercase;
130
- background: rgba(184, 134, 11, 0.15);
132
+ background: color-mix(in srgb, var(--c-primary, #b8860b) 15%, transparent);
131
133
  color: #facc15;
132
134
  border: 1px solid rgba(234, 179, 8, 0.3);
133
135
  margin-bottom: var(--sp-4, 1.25rem);
@@ -163,7 +165,7 @@ const {
163
165
  align-items: center;
164
166
  gap: 0.5rem;
165
167
  background: rgba(0, 0, 0, 0.4);
166
- border: 1px solid rgba(255, 255, 255, 0.12);
168
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.12);
167
169
  border-radius: 12px;
168
170
  padding: 0.35rem;
169
171
  max-width: 460px;
@@ -211,16 +213,16 @@ const {
211
213
  }
212
214
 
213
215
  .cta-btn--ghost {
214
- background: rgba(255, 255, 255, 0.05);
216
+ background: rgb(var(--ink-rgb, 255 255 255) / 0.05);
215
217
  color: #fff;
216
- border: 1px solid rgba(255, 255, 255, 0.12);
218
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.12);
217
219
  height: 48px;
218
220
  box-sizing: border-box;
219
221
  }
220
222
 
221
223
  .cta-btn--ghost:hover {
222
- background: rgba(255, 255, 255, 0.12);
223
- border-color: rgba(255, 255, 255, 0.25);
224
+ background: rgb(var(--ink-rgb, 255 255 255) / 0.12);
225
+ border-color: rgb(var(--ink-rgb, 255 255 255) / 0.25);
224
226
  }
225
227
 
226
228
  .cta-trust-row {
@@ -230,7 +232,7 @@ const {
230
232
  justify-content: center;
231
233
  gap: 1.5rem;
232
234
  padding-top: 1.5rem;
233
- border-top: 1px solid rgba(255, 255, 255, 0.08);
235
+ border-top: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.08);
234
236
  }
235
237
 
236
238
  .trust-item {
@@ -246,7 +248,7 @@ const {
246
248
  color: #eab308;
247
249
  }
248
250
 
249
- @media (max-width: 640px) {
251
+ @container (max-width: 640px) {
250
252
  .cta-form {
251
253
  flex-direction: column;
252
254
  background: transparent;
@@ -256,7 +258,7 @@ const {
256
258
  .cta-input {
257
259
  width: 100%;
258
260
  background: rgba(0, 0, 0, 0.4);
259
- border: 1px solid rgba(255, 255, 255, 0.12);
261
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.12);
260
262
  border-radius: 8px;
261
263
  box-sizing: border-box;
262
264
  }
@@ -0,0 +1,165 @@
1
+ ---
2
+ /**
3
+ * CtaNewsletterSection — Newsletter signup band with benefits list, accessible
4
+ * email form, inline validation and privacy note.
5
+ *
6
+ * @param {string} title - Heading.
7
+ * @param {string} subtitle - Supporting copy.
8
+ * @param {string[]} perks - Short benefit bullets.
9
+ * @param {string} action - Form action URL.
10
+ * @param {string} buttonLabel - Submit label.
11
+ * @param {string} privacy - Privacy note under the form.
12
+ * @param {string} variant - Band style (gradient|glass|bordered).
13
+ * @param {string} size - Vertical rhythm (sm|md|lg).
14
+ * @param {string} className - Extra CSS class.
15
+ */
16
+ interface Props {
17
+ title?: string;
18
+ subtitle?: string;
19
+ perks?: string[];
20
+ action?: string;
21
+ buttonLabel?: string;
22
+ privacy?: string;
23
+ variant?: 'gradient' | 'glass' | 'bordered';
24
+ size?: 'sm' | 'md' | 'lg';
25
+ className?: string;
26
+ }
27
+
28
+ const {
29
+ title = 'Get new components every week',
30
+ subtitle = 'One short email with fresh sections, charts and Astro tips. Join 12,000+ developers.',
31
+ perks = ['Weekly, never spam', 'Early access to new blocks', 'Unsubscribe in one click'],
32
+ action = '#',
33
+ buttonLabel = 'Subscribe',
34
+ privacy = 'We respect your privacy. Read our privacy policy.',
35
+ variant = 'gradient',
36
+ size = 'md',
37
+ className = '',
38
+ } = Astro.props;
39
+
40
+ const id = `cns-${Math.random().toString(36).slice(2, 8)}`;
41
+ ---
42
+
43
+ <div class="cq-cta-newsletter-section-cq">
44
+ <section class={`cns cns--${variant} cns--${size} ${className}`} aria-labelledby={id}>
45
+ <div class="cns__box">
46
+ <div class="cns__copy">
47
+ <span class="cns__icon" aria-hidden="true">
48
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2" /><path d="m22 7-10 6L2 7" /></svg>
49
+ </span>
50
+ <h2 id={id} class="cns__title">{title}</h2>
51
+ <p class="cns__subtitle">{subtitle}</p>
52
+ <ul class="cns__perks">
53
+ {perks.map((p) => (
54
+ <li><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg>{p}</li>
55
+ ))}
56
+ </ul>
57
+ </div>
58
+ <form class="cns__form" action={action} method="post" novalidate>
59
+ <label class="cns__label" for={`${id}-email`}>Email address</label>
60
+ <div class="cns__field">
61
+ <input class="cns__input" id={`${id}-email`} name="email" type="email" required autocomplete="email" placeholder="you@company.com" aria-describedby={`${id}-msg ${id}-privacy`} />
62
+ <button class="cns__btn" type="submit">{buttonLabel}</button>
63
+ </div>
64
+ <p id={`${id}-msg`} class="cns__msg" role="status" aria-live="polite"></p>
65
+ <p id={`${id}-privacy`} class="cns__privacy">{privacy}</p>
66
+ </form>
67
+ </div>
68
+ </section>
69
+ </div>
70
+ <script>
71
+ document.querySelectorAll<HTMLFormElement>('.cns__form').forEach((form) => {
72
+ const input = form.querySelector<HTMLInputElement>('.cns__input');
73
+ const msg = form.querySelector<HTMLElement>('.cns__msg');
74
+ form.addEventListener('submit', (e) => {
75
+ e.preventDefault();
76
+ if (!input || !msg) return;
77
+ if (!input.value || !input.checkValidity()) {
78
+ input.setAttribute('aria-invalid', 'true');
79
+ msg.dataset.state = 'error';
80
+ msg.textContent = 'Please enter a valid email address.';
81
+ input.focus();
82
+ return;
83
+ }
84
+ input.removeAttribute('aria-invalid');
85
+ msg.dataset.state = 'ok';
86
+ msg.textContent = 'Thanks! Please confirm your subscription in your inbox.';
87
+ input.value = '';
88
+ });
89
+ });
90
+ </script>
91
+
92
+ <style>
93
+ .cq-cta-newsletter-section-cq { container-type: inline-size; }
94
+ .cns {
95
+ --cns-py: 5rem;
96
+ padding: var(--cns-py) var(--sp-6, 1.5rem);
97
+ background: var(--c-bg, #050505);
98
+ }
99
+ .cns--sm { --cns-py: 3rem; }
100
+ .cns--lg { --cns-py: 7rem; }
101
+ .cns__box {
102
+ max-width: 1080px;
103
+ margin: 0 auto;
104
+ display: grid;
105
+ grid-template-columns: 1.2fr 1fr;
106
+ gap: 2.5rem;
107
+ align-items: center;
108
+ padding: clamp(2rem, 5vw, 3.5rem);
109
+ border-radius: var(--r-xl, 28px);
110
+ }
111
+ .cns--gradient .cns__box {
112
+ background: radial-gradient(circle at 100% 0%, rgba(144, 133, 233, 0.22), transparent 55%), radial-gradient(circle at 0% 100%, var(--c-primary-glow, color-mix(in srgb, var(--c-primary, #b8860b) 20%, transparent)), transparent 55%), #0c0c10;
113
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.08);
114
+ }
115
+ .cns--glass .cns__box { background: rgb(var(--ink-rgb, 255 255 255) / 0.035); border: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.08)); backdrop-filter: blur(16px); }
116
+ .cns--bordered .cns__box { border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.14); }
117
+ .cns__icon { display: grid; place-items: center; width: 48px; height: 48px; margin-bottom: 1.25rem; border-radius: 14px; background: rgb(var(--ink-rgb, 255 255 255) / 0.06); border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.1); color: var(--c-text-1, #fff); }
118
+ .cns__title { margin: 0 0 0.75rem; font-size: clamp(1.6rem, 3.5vw, 2.25rem); font-weight: 800; letter-spacing: -0.03em; line-height: 1.15; color: var(--c-text-1, #fff); }
119
+ .cns__subtitle { margin: 0 0 1.25rem; font-size: 1rem; line-height: 1.6; color: var(--c-text-2, #a3a3a3); }
120
+ .cns__perks { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem; }
121
+ .cns__perks li { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.875rem; color: var(--c-text-1, #fff); }
122
+ .cns__perks svg { color: var(--chart-pos, #34c38f); }
123
+ .cns__label { display: block; margin-bottom: 0.5rem; font-size: 0.875rem; font-weight: 600; color: var(--c-text-1, #fff); }
124
+ .cns__field { display: flex; gap: 0.5rem; }
125
+ .cns__input {
126
+ flex: 1;
127
+ min-width: 0;
128
+ min-height: 50px;
129
+ padding: 0 1rem;
130
+ border-radius: var(--r-md, 12px);
131
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.14);
132
+ background: rgba(0, 0, 0, 0.35);
133
+ color: var(--c-text-1, #fff);
134
+ font: inherit;
135
+ font-size: 1rem;
136
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
137
+ }
138
+ .cns__input::placeholder { color: var(--c-text-3, #737373); }
139
+ .cns__input:focus { outline: none; border-color: var(--c-primary-light, #d4a017); box-shadow: 0 0 0 3px var(--c-primary-glow, color-mix(in srgb, var(--c-primary, #b8860b) 25%, transparent)); }
140
+ .cns__input[aria-invalid='true'] { border-color: var(--chart-neg, #f06a6a); }
141
+ .cns__btn {
142
+ min-height: 50px;
143
+ padding: 0 1.4rem;
144
+ border: 0;
145
+ border-radius: var(--r-md, 12px);
146
+ background: var(--c-text-1, #fff);
147
+ color: var(--c-on-primary, #0a0a0a);
148
+ font: inherit;
149
+ font-weight: 650;
150
+ cursor: pointer;
151
+ transition: background 0.2s ease;
152
+ }
153
+ .cns__btn:hover { background: var(--c-accent, #fef3c7); }
154
+ .cns__btn:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; }
155
+ .cns__msg { min-height: 1.4rem; margin: 0.6rem 0 0; font-size: 0.875rem; }
156
+ .cns__msg[data-state='error'] { color: #ff9b9b; }
157
+ .cns__msg[data-state='ok'] { color: #7ee2b8; }
158
+ .cns__privacy { margin: 0.25rem 0 0; font-size: 0.8125rem; color: var(--c-text-3, #8a8a8a); }
159
+ @container (max-width: 820px) {
160
+ .cns__box { grid-template-columns: 1fr; }
161
+ }
162
+ @container (max-width: 460px) {
163
+ .cns__field { flex-direction: column; }
164
+ }
165
+ </style>
@@ -0,0 +1,149 @@
1
+ ---
2
+ /**
3
+ * CtaSplitImage — Conversion banner split into copy + CTA and a decorative
4
+ * visual panel (CSS gradient mesh with floating stat chips). Optional real image.
5
+ *
6
+ * @param {string} title - Heading.
7
+ * @param {string} text - Supporting copy.
8
+ * @param {{label: string, href: string}} primary - Primary CTA.
9
+ * @param {{label: string, href: string}} secondary - Secondary CTA.
10
+ * @param {string} image - Optional image URL (replaces the CSS visual).
11
+ * @param {string} imageAlt - Alt text when an image is used.
12
+ * @param {Array<{value: string, label: string}>} chips - Floating stat chips on the visual.
13
+ * @param {string} variant - Panel style (mesh|solid|outline).
14
+ * @param {string} size - Vertical rhythm (sm|md|lg).
15
+ * @param {string} className - Extra CSS class.
16
+ */
17
+ interface Props {
18
+ title?: string;
19
+ text?: string;
20
+ primary?: { label: string; href: string };
21
+ secondary?: { label: string; href: string };
22
+ image?: string;
23
+ imageAlt?: string;
24
+ chips?: Array<{ value: string; label: string }>;
25
+ variant?: 'mesh' | 'solid' | 'outline';
26
+ size?: 'sm' | 'md' | 'lg';
27
+ className?: string;
28
+ }
29
+
30
+ const {
31
+ title = 'Ready to ship your best page yet?',
32
+ text = 'Join thousands of teams building fast, accessible Astro sites. Free forever for personal projects.',
33
+ primary = { label: 'Get started free', href: '#' },
34
+ secondary = { label: 'Talk to sales', href: '#' },
35
+ image,
36
+ imageAlt = '',
37
+ chips = [
38
+ { value: '99.9%', label: 'uptime' },
39
+ { value: '< 50ms', label: 'TTFB' },
40
+ { value: '100', label: 'Lighthouse' },
41
+ ],
42
+ variant = 'mesh',
43
+ size = 'md',
44
+ className = '',
45
+ } = Astro.props;
46
+
47
+ const id = `csi-${Math.random().toString(36).slice(2, 8)}`;
48
+ ---
49
+
50
+ <div class="cq-cta-split-image-cq">
51
+ <section class={`csi csi--${variant} csi--${size} ${className}`} aria-labelledby={id}>
52
+ <div class="csi__box">
53
+ <div class="csi__copy">
54
+ <h2 id={id} class="csi__title">{title}</h2>
55
+ <p class="csi__text">{text}</p>
56
+ <div class="csi__actions">
57
+ <a class="csi__btn csi__btn--primary" href={primary.href}>{primary.label}</a>
58
+ {secondary && <a class="csi__btn csi__btn--ghost" href={secondary.href}>{secondary.label}</a>}
59
+ </div>
60
+ </div>
61
+ <div class="csi__visual">
62
+ {image ? (
63
+ <img src={image} alt={imageAlt} loading="lazy" decoding="async" />
64
+ ) : (
65
+ <div class="csi__mesh" aria-hidden="true">
66
+ {chips.map((c, i) => (
67
+ <span class="csi__chip" style={`--i:${i}`}><strong>{c.value}</strong>{c.label}</span>
68
+ ))}
69
+ </div>
70
+ )}
71
+ </div>
72
+ </div>
73
+ </section>
74
+ </div>
75
+ <style>
76
+ .cq-cta-split-image-cq { container-type: inline-size; }
77
+ .csi {
78
+ --csi-py: 5rem;
79
+ padding: var(--csi-py) var(--sp-6, 1.5rem);
80
+ background: var(--c-bg, #050505);
81
+ }
82
+ .csi--sm { --csi-py: 3rem; }
83
+ .csi--lg { --csi-py: 7rem; }
84
+ .csi__box {
85
+ max-width: 1180px;
86
+ margin: 0 auto;
87
+ display: grid;
88
+ grid-template-columns: 1fr 1fr;
89
+ border-radius: var(--r-xl, 28px);
90
+ overflow: hidden;
91
+ border: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.08));
92
+ background: var(--c-bg-1, #0a0a0a);
93
+ }
94
+ .csi--outline .csi__box { background: transparent; border-color: rgb(var(--ink-rgb, 255 255 255) / 0.15); }
95
+ .csi__copy { padding: clamp(2rem, 6vw, 4rem); display: flex; flex-direction: column; justify-content: center; }
96
+ .csi__title { margin: 0 0 1rem; font-size: clamp(1.75rem, 4vw, 2.75rem); font-weight: 800; letter-spacing: -0.03em; line-height: 1.1; color: var(--c-text-1, #fff); text-wrap: balance; }
97
+ .csi__text { margin: 0 0 2rem; font-size: 1.0625rem; line-height: 1.6; color: var(--c-text-2, #a3a3a3); max-width: 460px; }
98
+ .csi__actions { display: flex; flex-wrap: wrap; gap: 0.75rem; }
99
+ .csi__btn { display: inline-flex; align-items: center; min-height: 48px; padding: 0 1.5rem; border-radius: var(--r-md, 12px); font-weight: 650; text-decoration: none; transition: background 0.2s ease, border-color 0.2s ease; }
100
+ .csi__btn--primary { background: var(--c-primary-light, #d4a017); color: var(--c-on-primary, #0a0a0a); }
101
+ .csi__btn--primary:hover { background: var(--c-accent, #fef3c7); color: var(--c-on-primary, #0a0a0a); }
102
+ .csi__btn--ghost { border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.15); color: var(--c-text-1, #fff); }
103
+ .csi__btn--ghost:hover { border-color: rgb(var(--ink-rgb, 255 255 255) / 0.32); color: var(--c-text-1, #fff); }
104
+ .csi__btn:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 3px; }
105
+ .csi__visual { position: relative; min-height: 320px; }
106
+ .csi__visual img { width: 100%; height: 100%; object-fit: cover; display: block; }
107
+ .csi__mesh {
108
+ position: absolute;
109
+ inset: 0;
110
+ display: flex;
111
+ flex-direction: column;
112
+ justify-content: center;
113
+ align-items: center;
114
+ gap: 1rem;
115
+ padding: 2rem;
116
+ }
117
+ .csi--mesh .csi__mesh {
118
+ background:
119
+ radial-gradient(circle at 20% 20%, color-mix(in srgb, var(--c-primary-light, #d4a017) 55%, transparent), transparent 45%),
120
+ radial-gradient(circle at 80% 30%, rgba(144, 133, 233, 0.55), transparent 45%),
121
+ radial-gradient(circle at 50% 90%, rgba(57, 135, 229, 0.5), transparent 50%),
122
+ #121218;
123
+ }
124
+ .csi--solid .csi__mesh { background: linear-gradient(135deg, #1a1408, #0c0c10); }
125
+ .csi--outline .csi__mesh { background: repeating-linear-gradient(45deg, rgb(var(--ink-rgb, 255 255 255) / 0.03) 0 12px, transparent 12px 24px); }
126
+ .csi__chip {
127
+ display: inline-flex;
128
+ align-items: baseline;
129
+ gap: 0.5rem;
130
+ padding: 0.75rem 1.1rem;
131
+ border-radius: var(--r-md, 14px);
132
+ background: rgba(10, 10, 12, 0.6);
133
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.14);
134
+ backdrop-filter: blur(12px);
135
+ color: var(--c-text-2, #c4c4c4);
136
+ font-size: 0.875rem;
137
+ animation: csi-float 6s ease-in-out infinite;
138
+ animation-delay: calc(var(--i) * -2s);
139
+ }
140
+ .csi__chip:nth-child(2) { transform: translateX(2.5rem); }
141
+ .csi__chip:nth-child(3) { transform: translateX(-2rem); }
142
+ .csi__chip strong { font-size: 1.25rem; color: var(--c-text-1, #fff); font-variant-numeric: tabular-nums; }
143
+ @keyframes csi-float { 50% { translate: 0 -8px; } }
144
+ @container (max-width: 820px) {
145
+ .csi__box { grid-template-columns: 1fr; }
146
+ .csi__visual { min-height: 240px; }
147
+ }
148
+ @media (prefers-reduced-motion: reduce) { .csi__chip { animation: none; } }
149
+ </style>