@thorprovider/blocks 0.1.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 (184) hide show
  1. package/.turbo/turbo-build.log +24 -0
  2. package/.turbo/turbo-test.log +10 -0
  3. package/.turbo/turbo-type-check.log +0 -0
  4. package/AGENTS.md +1 -0
  5. package/README.md +161 -0
  6. package/components.json +33 -0
  7. package/dist/.tsbuildinfo +1 -0
  8. package/dist/contracts.d.mts +563 -0
  9. package/dist/contracts.d.ts +563 -0
  10. package/dist/contracts.js +207 -0
  11. package/dist/contracts.js.map +1 -0
  12. package/dist/contracts.mjs +168 -0
  13. package/dist/contracts.mjs.map +1 -0
  14. package/dist/index.d.mts +2408 -0
  15. package/dist/index.d.ts +2408 -0
  16. package/dist/index.js +16422 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/index.mjs +16352 -0
  19. package/dist/index.mjs.map +1 -0
  20. package/docs/standards/data-contracts.md +70 -0
  21. package/docs/standards/theming.md +85 -0
  22. package/package.json +62 -0
  23. package/src/blocks/application/app-shells/app-shell-1-sidebar.tsx +198 -0
  24. package/src/blocks/application/app-shells/app-shell-1.tsx +196 -0
  25. package/src/blocks/application/login-forms/login-page-1.tsx +246 -0
  26. package/src/blocks/application/login-forms/login-page-2.tsx +207 -0
  27. package/src/blocks/application/password-recovery/forgot-password-page-1-brand-mark.tsx +38 -0
  28. package/src/blocks/application/password-recovery/forgot-password-page-1.tsx +320 -0
  29. package/src/blocks/application/signup-forms/signup-page-1.tsx +231 -0
  30. package/src/blocks/application/verification/verify-1.tsx +307 -0
  31. package/src/blocks/application/verification/verify-confetti.tsx +192 -0
  32. package/src/blocks/e-commerce/category-filters/category-filter-1.tsx +260 -0
  33. package/src/blocks/e-commerce/category-filters/category-filter-2-filter-panel.tsx +179 -0
  34. package/src/blocks/e-commerce/category-filters/category-filter-2.tsx +341 -0
  35. package/src/blocks/e-commerce/category-filters/category-filter-4-chip-rail.tsx +57 -0
  36. package/src/blocks/e-commerce/category-filters/category-filter-4-drawer.tsx +124 -0
  37. package/src/blocks/e-commerce/category-filters/category-filter-4-fields.tsx +170 -0
  38. package/src/blocks/e-commerce/category-filters/category-filter-4-sidebar.tsx +52 -0
  39. package/src/blocks/e-commerce/category-filters/category-filter-4-sort-sheet.tsx +57 -0
  40. package/src/blocks/e-commerce/category-filters/category-filter-4-state.ts +68 -0
  41. package/src/blocks/e-commerce/category-filters/category-filter-4.tsx +298 -0
  42. package/src/blocks/e-commerce/checkout-forms/card-formatting.test.ts +47 -0
  43. package/src/blocks/e-commerce/checkout-forms/card-formatting.ts +9 -0
  44. package/src/blocks/e-commerce/checkout-forms/checkbox-check.tsx +74 -0
  45. package/src/blocks/e-commerce/checkout-forms/checkout-confirmation-fulfilment-progress.tsx +189 -0
  46. package/src/blocks/e-commerce/checkout-forms/checkout-confirmation.tsx +283 -0
  47. package/src/blocks/e-commerce/checkout-forms/checkout-field-feedback.tsx +29 -0
  48. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-contact-step.tsx +96 -0
  49. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-country-field.tsx +51 -0
  50. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-order-summary.tsx +139 -0
  51. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-payment-step.tsx +184 -0
  52. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-progress.tsx +36 -0
  53. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-shipping-step.tsx +107 -0
  54. package/src/blocks/e-commerce/checkout-forms/checkout-form-1.tsx +235 -0
  55. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-order-summary.tsx +130 -0
  56. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-payment-card.tsx +175 -0
  57. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-shipping-card.tsx +190 -0
  58. package/src/blocks/e-commerce/checkout-forms/checkout-form-2.tsx +312 -0
  59. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-information-step.tsx +91 -0
  60. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-order-summary.tsx +110 -0
  61. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-payment-step.tsx +137 -0
  62. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-progress.tsx +68 -0
  63. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-review-step.tsx +79 -0
  64. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-shipping-step.tsx +172 -0
  65. package/src/blocks/e-commerce/checkout-forms/checkout-form-3.tsx +392 -0
  66. package/src/blocks/e-commerce/checkout-forms/checkout-form-types.ts +7 -0
  67. package/src/blocks/e-commerce/navigation/category-icon.test.tsx +54 -0
  68. package/src/blocks/e-commerce/navigation/category-icon.tsx +94 -0
  69. package/src/blocks/e-commerce/navigation/mega-menu.test.tsx +139 -0
  70. package/src/blocks/e-commerce/navigation/mega-menu.tsx +271 -0
  71. package/src/blocks/e-commerce/navigation/mobile-category-drawer.test.tsx +152 -0
  72. package/src/blocks/e-commerce/navigation/mobile-category-drawer.tsx +276 -0
  73. package/src/blocks/e-commerce/order-history/order-history-1.tsx +159 -0
  74. package/src/blocks/e-commerce/order-history/order-history-2.tsx +222 -0
  75. package/src/blocks/e-commerce/product-categories/avatar-group.tsx +86 -0
  76. package/src/blocks/e-commerce/product-categories/product-category-1.tsx +126 -0
  77. package/src/blocks/e-commerce/product-categories/product-category-2.tsx +100 -0
  78. package/src/blocks/e-commerce/product-categories/product-category-4.tsx +129 -0
  79. package/src/blocks/e-commerce/product-categories/product-category-5.tsx +218 -0
  80. package/src/blocks/e-commerce/product-list/product-card-1.tsx +131 -0
  81. package/src/blocks/e-commerce/product-list/product-card-2.tsx +71 -0
  82. package/src/blocks/e-commerce/product-list/product-card-3-reaction-toggle.tsx +166 -0
  83. package/src/blocks/e-commerce/product-list/product-card-3.tsx +215 -0
  84. package/src/blocks/e-commerce/product-list/product-card-5-checkbox-check.tsx +98 -0
  85. package/src/blocks/e-commerce/product-list/product-card-5-reaction-toggle.tsx +171 -0
  86. package/src/blocks/e-commerce/product-list/product-card-5.tsx +367 -0
  87. package/src/blocks/e-commerce/product-overview/product-overview-1.tsx +201 -0
  88. package/src/blocks/e-commerce/product-overview/product-overview-2.tsx +272 -0
  89. package/src/blocks/e-commerce/product-overview/product-overview-5-gallery.tsx +59 -0
  90. package/src/blocks/e-commerce/product-overview/product-overview-5-lightbox-tab.tsx +104 -0
  91. package/src/blocks/e-commerce/product-overview/product-overview-5-reviews-tab.tsx +82 -0
  92. package/src/blocks/e-commerce/product-overview/product-overview-5-tabs.tsx +69 -0
  93. package/src/blocks/e-commerce/product-overview/product-overview-5.tsx +274 -0
  94. package/src/blocks/e-commerce/product-overview/product-quick-view-card.tsx +135 -0
  95. package/src/blocks/e-commerce/product-overview/product-quick-view-dialog.tsx +393 -0
  96. package/src/blocks/e-commerce/product-overview/product-quick-view.tsx +136 -0
  97. package/src/blocks/e-commerce/reviews/review-rating-1-review-form.tsx +133 -0
  98. package/src/blocks/e-commerce/reviews/review-rating-1.tsx +178 -0
  99. package/src/blocks/e-commerce/reviews/review-rating-2.tsx +246 -0
  100. package/src/blocks/e-commerce/shopping-carts/shopping-cart-1-spinning-counter.tsx +142 -0
  101. package/src/blocks/e-commerce/shopping-carts/shopping-cart-1.tsx +291 -0
  102. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-item-row.tsx +110 -0
  103. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-recommendations.tsx +51 -0
  104. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-spinning-counter.tsx +146 -0
  105. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2.tsx +295 -0
  106. package/src/blocks/e-commerce/shopping-carts/shopping-cart-3-spinning-counter.tsx +142 -0
  107. package/src/blocks/e-commerce/shopping-carts/shopping-cart-3.tsx +342 -0
  108. package/src/blocks/e-commerce/shopping-carts/shopping-cart-4-spinning-counter.tsx +146 -0
  109. package/src/blocks/e-commerce/shopping-carts/shopping-cart-4.tsx +391 -0
  110. package/src/blocks/e-commerce/store-containers/store-container-1.tsx +29 -0
  111. package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.tsx +244 -0
  112. package/src/blocks/e-commerce/storefront-hero/storefront-hero-2.tsx +283 -0
  113. package/src/blocks/e-commerce/storefront-hero/storefront-hero-5.tsx +318 -0
  114. package/src/blocks/e-commerce/storefront-hero/storefront-hero-6.tsx +205 -0
  115. package/src/blocks/marketing/banners/avatar-group.tsx +69 -0
  116. package/src/blocks/marketing/banners/banner-1.tsx +188 -0
  117. package/src/blocks/marketing/cta/cta-section-1.tsx +112 -0
  118. package/src/blocks/marketing/faq/faq-block-1.tsx +45 -0
  119. package/src/blocks/marketing/features/feature-1.tsx +97 -0
  120. package/src/blocks/marketing/features/feature-2.tsx +82 -0
  121. package/src/blocks/marketing/footers/footer-section-1.tsx +141 -0
  122. package/src/blocks/marketing/footers/footer-section-3.tsx +192 -0
  123. package/src/blocks/marketing/footers/footer-section-4.tsx +355 -0
  124. package/src/blocks/marketing/hero-sections/avatar-group.tsx +86 -0
  125. package/src/blocks/marketing/hero-sections/hero-section-1.tsx +135 -0
  126. package/src/blocks/marketing/logo-clouds/logo-cloud-1-logo-icon.tsx +52 -0
  127. package/src/blocks/marketing/logo-clouds/logo-cloud-1.tsx +56 -0
  128. package/src/blocks/marketing/navbars/navbar-1.tsx +267 -0
  129. package/src/blocks/marketing/navbars/navbar-2.tsx +351 -0
  130. package/src/blocks/marketing/navbars/navbar-3.tsx +303 -0
  131. package/src/blocks/marketing/navbars/navbar-4-account-menu.tsx +118 -0
  132. package/src/blocks/marketing/navbars/navbar-4-cart-sheet.tsx +248 -0
  133. package/src/blocks/marketing/navbars/navbar-4-like-button.tsx +177 -0
  134. package/src/blocks/marketing/navbars/navbar-4-mobile-nav.tsx +104 -0
  135. package/src/blocks/marketing/navbars/navbar-4-product-grid.tsx +219 -0
  136. package/src/blocks/marketing/navbars/navbar-4-search-bar.tsx +52 -0
  137. package/src/blocks/marketing/navbars/navbar-4.tsx +411 -0
  138. package/src/blocks/marketing/pricing/pricing-section-1.tsx +94 -0
  139. package/src/blocks/marketing/statistics/statistic-1.tsx +93 -0
  140. package/src/blocks/marketing/testimonials/testimonial-1.tsx +141 -0
  141. package/src/blocks/marketing/testimonials/testimonial-2.tsx +108 -0
  142. package/src/blocks/shared/reaction-toggle.tsx +145 -0
  143. package/src/hooks/use-mobile.ts +19 -0
  144. package/src/index.ts +200 -0
  145. package/src/lib/contracts.test.ts +32 -0
  146. package/src/lib/contracts.ts +235 -0
  147. package/src/lib/types/menu.ts +43 -0
  148. package/src/lib/types.ts +4 -0
  149. package/src/lib/utils.ts +10 -0
  150. package/src/ui/accordion.tsx +65 -0
  151. package/src/ui/avatar.tsx +108 -0
  152. package/src/ui/badge.tsx +47 -0
  153. package/src/ui/breadcrumb.tsx +108 -0
  154. package/src/ui/button-group.tsx +83 -0
  155. package/src/ui/button.tsx +63 -0
  156. package/src/ui/card.tsx +91 -0
  157. package/src/ui/carousel.tsx +241 -0
  158. package/src/ui/dialog.tsx +158 -0
  159. package/src/ui/drawer.tsx +134 -0
  160. package/src/ui/dropdown-menu.tsx +256 -0
  161. package/src/ui/empty.tsx +103 -0
  162. package/src/ui/field.tsx +248 -0
  163. package/src/ui/input-group.tsx +170 -0
  164. package/src/ui/input-otp.tsx +76 -0
  165. package/src/ui/input.tsx +20 -0
  166. package/src/ui/label.tsx +23 -0
  167. package/src/ui/navigation-menu.tsx +167 -0
  168. package/src/ui/pagination.tsx +127 -0
  169. package/src/ui/progress.tsx +30 -0
  170. package/src/ui/radio-group.tsx +44 -0
  171. package/src/ui/select.tsx +189 -0
  172. package/src/ui/separator.tsx +27 -0
  173. package/src/ui/sheet.tsx +142 -0
  174. package/src/ui/sidebar.tsx +726 -0
  175. package/src/ui/skeleton.tsx +13 -0
  176. package/src/ui/table.tsx +115 -0
  177. package/src/ui/tabs.tsx +90 -0
  178. package/src/ui/textarea.tsx +17 -0
  179. package/src/ui/toggle-group.tsx +83 -0
  180. package/src/ui/toggle.tsx +46 -0
  181. package/src/ui/tooltip.tsx +56 -0
  182. package/tsconfig.json +20 -0
  183. package/tsup.config.ts +16 -0
  184. package/vitest.config.ts +9 -0
@@ -0,0 +1,355 @@
1
+ import type { ReactElement, ReactNode, SVGProps } from 'react'
2
+ import { Input } from '../../../ui/input'
3
+ import { Button } from '../../../ui/button'
4
+ import { ArrowRight, Mail, MapPin, Phone } from 'lucide-react'
5
+
6
+ export type FooterSection4SocialIcon = 'facebook' | 'twitter' | 'instagram'
7
+
8
+ export interface FooterSection4Link {
9
+ label: string
10
+ href?: string
11
+ }
12
+
13
+ export interface FooterSection4LinkColumn {
14
+ title: string
15
+ links: FooterSection4Link[]
16
+ }
17
+
18
+ export interface FooterSection4Contact {
19
+ /** Título de la columna de contacto. Requerido. */
20
+ heading: string
21
+ /** Dirección del negocio. Por defecto undefined. */
22
+ address?: string
23
+ /** href del enlace de dirección. Por defecto undefined. */
24
+ addressHref?: string
25
+ /** Teléfono de contacto. Por defecto undefined. */
26
+ phone?: string
27
+ /** href del enlace de teléfono (p. ej. tel:). Por defecto undefined. */
28
+ phoneHref?: string
29
+ /** Email de contacto. Por defecto undefined. */
30
+ email?: string
31
+ /** href del enlace de email (p. ej. mailto:). Por defecto undefined. */
32
+ emailHref?: string
33
+ }
34
+
35
+ export interface FooterSection4SocialLink {
36
+ label: string
37
+ href?: string
38
+ icon: FooterSection4SocialIcon
39
+ }
40
+
41
+ export interface FooterSection4Props {
42
+ /** Nombre de la marca mostrado junto al logo. Requerido. */
43
+ brandName: string
44
+ /** Descripción corta de la marca bajo el logo. Requerida. */
45
+ tagline: string
46
+ /** Placeholder del input de email del newsletter. Por defecto 'Tu correo electrónico'. */
47
+ newsletterPlaceholder?: string
48
+ /** Texto del botón de suscripción. Por defecto 'Suscribirse'. */
49
+ newsletterSubmitLabel?: string
50
+ /** Datos de la columna de contacto (dirección, teléfono, email). Requerido. */
51
+ contact: FooterSection4Contact
52
+ /** Columnas de enlaces (Quick Links, Resources, Legal...). Requerido. */
53
+ linkColumns: FooterSection4LinkColumn[]
54
+ /** Texto de copyright de la barra inferior (incluye año). Requerido. */
55
+ copyright: string
56
+ /** Redes sociales con su icono serializable por nombre. Por defecto []. */
57
+ socialLinks?: FooterSection4SocialLink[]
58
+ /** Logo personalizado; si se omite se usa el SVG interno del bloque. Por defecto undefined. */
59
+ logo?: ReactNode
60
+ /** Callback al enviar el formulario newsletter. Recibe el email. */
61
+ onNewsletterSubmit?: (email: string) => void
62
+ /** Callback al hacer clic en un enlace de columna o contacto. Recibe el enlace. */
63
+ onLinkClick?: (link: FooterSection4Link) => void
64
+ /** Callback al hacer clic en una red social. Recibe el label. */
65
+ onSocialClick?: (label: string) => void
66
+ }
67
+
68
+ const socialIconPaths: Record<
69
+ FooterSection4SocialIcon,
70
+ (props: SVGProps<SVGSVGElement>) => ReactElement
71
+ > = {
72
+ facebook: props => (
73
+ <svg
74
+ xmlns='http://www.w3.org/2000/svg'
75
+ width='24'
76
+ height='24'
77
+ viewBox='0 0 24 24'
78
+ fill='none'
79
+ stroke='currentColor'
80
+ strokeWidth='2'
81
+ strokeLinecap='round'
82
+ strokeLinejoin='round'
83
+ {...props}
84
+ >
85
+ <path d='M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z' />
86
+ </svg>
87
+ ),
88
+ twitter: props => (
89
+ <svg
90
+ xmlns='http://www.w3.org/2000/svg'
91
+ width='24'
92
+ height='24'
93
+ viewBox='0 0 24 24'
94
+ fill='none'
95
+ stroke='currentColor'
96
+ strokeWidth='2'
97
+ strokeLinecap='round'
98
+ strokeLinejoin='round'
99
+ {...props}
100
+ >
101
+ <path d='M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z' />
102
+ </svg>
103
+ ),
104
+ instagram: props => (
105
+ <svg
106
+ xmlns='http://www.w3.org/2000/svg'
107
+ width='24'
108
+ height='24'
109
+ viewBox='0 0 24 24'
110
+ fill='none'
111
+ stroke='currentColor'
112
+ strokeWidth='2'
113
+ strokeLinecap='round'
114
+ strokeLinejoin='round'
115
+ {...props}
116
+ >
117
+ <rect width='20' height='20' x='2' y='2' rx='5' ry='5' />
118
+ <path d='M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z' />
119
+ <line x1='17.5' y1='6.5' x2='17.51' y2='6.5' />
120
+ </svg>
121
+ ),
122
+ }
123
+
124
+ const DefaultLogo = (
125
+ <svg width='32' height='32' viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'>
126
+ <path
127
+ d='M26 24.75C26.4142 24.75 26.75 24.4142 26.75 24C26.75 23.5858 26.4142 23.25 26 23.25V24.75ZM26 23.25H11V24.75H26V23.25ZM8.75 21V15H7.25V21H8.75ZM11 23.25C9.75736 23.25 8.75 22.2426 8.75 21H7.25C7.25 23.0711 8.92893 24.75 11 24.75V23.25Z'
128
+ fill='currentColor'
129
+ />
130
+ <path
131
+ d='M1.5 3.25C1.08579 3.25 0.75 3.58579 0.75 4C0.75 4.41421 1.08579 4.75 1.5 4.75V3.25ZM1.5 4.75H6V3.25H1.5V4.75ZM7.25 6V21H8.75V6H7.25ZM6 4.75C6.69036 4.75 7.25 5.30964 7.25 6H8.75C8.75 4.48122 7.51878 3.25 6 3.25V4.75Z'
132
+ fill='currentColor'
133
+ />
134
+ <path
135
+ d='M22 21.75C22.4142 21.75 22.75 21.4142 22.75 21C22.75 20.5858 22.4142 20.25 22 20.25V21.75ZM22 20.25H11V21.75H22V20.25ZM8.75 18V12H7.25V18H8.75ZM11 20.25C9.75736 20.25 8.75 19.2426 8.75 18H7.25C7.25 20.0711 8.92893 21.75 11 21.75V20.25Z'
136
+ fill='currentColor'
137
+ />
138
+ <path
139
+ d='M27.2057 19.754C27.0654 20.1438 26.6357 20.346 26.246 20.2057C25.8562 20.0654 25.654 19.6357 25.7943 19.246L27.2057 19.754ZM30.0361 9.67744L29.3305 9.4234L29.3305 9.4234L30.0361 9.67744ZM25.7943 19.246L29.3305 9.4234L30.7418 9.93148L27.2057 19.754L25.7943 19.246ZM28.1543 7.75L8 7.75V6.25L28.1543 6.25V7.75ZM29.3305 9.4234C29.6237 8.60882 29.0201 7.75 28.1543 7.75V6.25C30.059 6.25 31.3869 8.13941 30.7418 9.93148L29.3305 9.4234Z'
140
+ fill='currentColor'
141
+ />
142
+ <path
143
+ d='M13.5 21.75C13.0858 21.75 12.75 21.4142 12.75 21C12.75 20.5858 13.0858 20.25 13.5 20.25V21.75ZM26.7111 19.009L27.4174 19.2613L27.4174 19.2613L26.7111 19.009ZM13.5 20.25H23.8858V21.75H13.5V20.25ZM26.0048 18.7568L27.7937 13.7477L29.2063 14.2523L27.4174 19.2613L26.0048 18.7568ZM23.8858 20.25C24.8367 20.25 25.6849 19.6522 26.0048 18.7568L27.4174 19.2613C26.8843 20.7537 25.4706 21.75 23.8858 21.75V20.25Z'
144
+ fill='currentColor'
145
+ />
146
+ <path d='M21.1694 10.5806L14.5651 17.1849' stroke='currentColor' />
147
+ <path d='M22.1694 14.5806L18.5632 18.1868' stroke='currentColor' />
148
+ <circle cx='13.1' cy='26.1' r='1.7' stroke='currentColor' />
149
+ <circle cx='22.1' cy='26.1' r='1.7' stroke='currentColor' />
150
+ </svg>
151
+ )
152
+
153
+ const contactItems = (contact: FooterSection4Contact, onLinkClick?: FooterSection4Props['onLinkClick']) => {
154
+ const items: { key: string; icon: ReactElement; href?: string; text?: string }[] = []
155
+ if (contact.address) {
156
+ items.push({
157
+ key: 'address',
158
+ icon: <MapPin className='mt-0.5 size-4 shrink-0' aria-hidden />,
159
+ href: contact.addressHref,
160
+ text: contact.address,
161
+ })
162
+ }
163
+ if (contact.phone) {
164
+ items.push({
165
+ key: 'phone',
166
+ icon: <Phone className='size-4 shrink-0' aria-hidden />,
167
+ href: contact.phoneHref,
168
+ text: contact.phone,
169
+ })
170
+ }
171
+ if (contact.email) {
172
+ items.push({
173
+ key: 'email',
174
+ icon: <Mail className='size-4 shrink-0' aria-hidden />,
175
+ href: contact.emailHref,
176
+ text: contact.email,
177
+ })
178
+ }
179
+ return items
180
+ }
181
+
182
+ export function FooterSection4({
183
+ brandName,
184
+ tagline,
185
+ newsletterPlaceholder = 'Tu correo electrónico',
186
+ newsletterSubmitLabel = 'Suscribirse',
187
+ contact,
188
+ linkColumns,
189
+ copyright,
190
+ socialLinks = [],
191
+ logo,
192
+ onNewsletterSubmit,
193
+ onLinkClick,
194
+ onSocialClick,
195
+ }: FooterSection4Props) {
196
+ const logoMark = logo ?? DefaultLogo
197
+ return (
198
+ <footer className='from-background to-muted/50 w-full bg-linear-to-b py-16'>
199
+ <div className='mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8'>
200
+ {/* Top Section with Logo and Newsletter */}
201
+ <section className='mb-16 flex flex-col items-start justify-between gap-8 md:flex-row md:items-center'>
202
+ <div className='max-w-sm flex flex-col gap-4'>
203
+ <div className='flex items-center gap-2'>
204
+ {logoMark}
205
+ <h2 className='text-2xl font-bold text-balance'>{brandName}</h2>
206
+ </div>
207
+ <p className='text-muted-foreground text-sm leading-relaxed text-balance'>{tagline}</p>
208
+ </div>
209
+
210
+ <div className='w-full md:w-auto'>
211
+ <form
212
+ className='relative'
213
+ onSubmit={e => {
214
+ e.preventDefault()
215
+ if (onNewsletterSubmit) {
216
+ const email = new FormData(e.currentTarget).get('email')
217
+ if (typeof email === 'string') {
218
+ onNewsletterSubmit(email)
219
+ }
220
+ }
221
+ }}
222
+ >
223
+ <Input
224
+ type='email'
225
+ name='email'
226
+ placeholder={newsletterPlaceholder}
227
+ className='border-muted bg-background/50 h-12 w-full ps-4 pe-32 backdrop-blur-xs md:w-[400px]'
228
+ aria-label={newsletterPlaceholder}
229
+ />
230
+ <Button
231
+ type='submit'
232
+ className='bg-primary hover:bg-primary/90 absolute end-1 top-1 h-10 cursor-pointer px-4'
233
+ aria-label={newsletterSubmitLabel}
234
+ >
235
+ {newsletterSubmitLabel}
236
+ <ArrowRight />
237
+ </Button>
238
+ </form>
239
+ </div>
240
+ </section>
241
+
242
+ {/* Middle Section with Contact Info and Links */}
243
+ <div className='mb-16 grid grid-cols-1 gap-12 md:grid-cols-2 lg:grid-cols-5'>
244
+ {/* Contact Information */}
245
+ <address className='not-italic lg:col-span-2'>
246
+ <h3 className='mb-6 text-lg font-semibold text-balance'>{contact.heading}</h3>
247
+ <ul className='flex flex-col gap-4'>
248
+ {contactItems(contact, onLinkClick).map(item => (
249
+ <li key={item.key} className='text-muted-foreground flex items-start gap-3 text-sm'>
250
+ {item.icon}
251
+ {item.href ? (
252
+ <a
253
+ href={item.href}
254
+ onClick={e => {
255
+ if (!onLinkClick) {
256
+ e.preventDefault()
257
+ } else {
258
+ onLinkClick({ label: item.text ?? '' })
259
+ }
260
+ }}
261
+ className='hover:text-foreground transition-colors'
262
+ >
263
+ {item.text}
264
+ </a>
265
+ ) : (
266
+ <span>{item.text}</span>
267
+ )}
268
+ </li>
269
+ ))}
270
+ </ul>
271
+ </address>
272
+
273
+ {/* Link columns */}
274
+ {linkColumns.map(column => (
275
+ <nav key={column.title} aria-label={column.title}>
276
+ <h3 className='mb-6 text-lg font-semibold text-balance'>{column.title}</h3>
277
+ <ul className='grid grid-cols-1 gap-3'>
278
+ {column.links.map(link => (
279
+ <li key={link.label}>
280
+ {link.href ? (
281
+ <a
282
+ href={link.href}
283
+ onClick={e => {
284
+ if (!onLinkClick) {
285
+ e.preventDefault()
286
+ } else {
287
+ onLinkClick(link)
288
+ }
289
+ }}
290
+ className='group text-muted-foreground hover:text-foreground focus-visible:ring-ring relative inline-block rounded-sm text-sm transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden'
291
+ >
292
+ {link.label}
293
+ </a>
294
+ ) : (
295
+ <span className='text-muted-foreground relative inline-block text-sm'>
296
+ {link.label}
297
+ </span>
298
+ )}
299
+ </li>
300
+ ))}
301
+ </ul>
302
+ </nav>
303
+ ))}
304
+ </div>
305
+
306
+ {/* Bottom Section */}
307
+ <div className='border-muted-foreground/20 flex flex-col items-center justify-between border-t pt-8 md:flex-row'>
308
+ <p className='text-muted-foreground mb-4 text-sm md:mb-0'>{copyright}</p>
309
+
310
+ <nav aria-label='Social media'>
311
+ <ul className='flex gap-4'>
312
+ {socialLinks.map(({ label, href, icon }) => {
313
+ const Icon = socialIconPaths[icon]
314
+ const element = (
315
+ <>
316
+ <Icon className='size-5' aria-hidden />
317
+ <span className='sr-only'>{label}</span>
318
+ </>
319
+ )
320
+ const classes =
321
+ 'bg-accent text-muted-foreground hover:text-foreground focus-visible:ring-ring inline-flex size-10 items-center justify-center rounded-full p-2 transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden'
322
+ return (
323
+ <li key={label}>
324
+ {href ? (
325
+ <a
326
+ href={href}
327
+ onClick={e => {
328
+ if (!onSocialClick) {
329
+ e.preventDefault()
330
+ } else {
331
+ onSocialClick(label)
332
+ }
333
+ }}
334
+ className={classes}
335
+ aria-label={`Seguir en ${label}`}
336
+ >
337
+ {element}
338
+ </a>
339
+ ) : (
340
+ <span className={classes} aria-hidden='true'>
341
+ {element}
342
+ </span>
343
+ )}
344
+ </li>
345
+ )
346
+ })}
347
+ </ul>
348
+ </nav>
349
+ </div>
350
+ </div>
351
+ </footer>
352
+ )
353
+ }
354
+
355
+ export default FooterSection4
@@ -0,0 +1,86 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { useEffect, useState } from 'react'
5
+
6
+ import { cn } from '../../../lib/utils'
7
+
8
+ /**
9
+ * @title Avatar group
10
+ * @description Overlapping avatar stack whose neighbours lift with a distance falloff on hover.
11
+ *
12
+ * Adapted from Transitions.dev "Avatar group hover".
13
+ *
14
+ * A dock, not a switch. Lifting only the hovered item makes the row twitch — one element jumps
15
+ * while the rest sit rigid — so the response falls off with distance: the neighbour rises about
16
+ * half as far, the next barely at all. The falloff is what makes the row read as one soft object.
17
+ *
18
+ * The return is deliberately springier than the approach: a strong overshoot curve on the way back
19
+ * reads as elastic rather than mechanical.
20
+ */
21
+ const LIFT = -4
22
+ const FALLOFF = 0.45
23
+ const ACTIVE_SCALE = 1.05
24
+ const DURATION = 320
25
+ const EASE_IN = 'cubic-bezier(0.22, 1, 0.36, 1)'
26
+ const EASE_OUT = 'cubic-bezier(0.34, 3.85, 0.64, 1)'
27
+
28
+ export function AvatarGroup({
29
+ children,
30
+ className,
31
+ overlap = '-ml-2',
32
+ }: {
33
+ children: React.ReactNode
34
+ className?: string
35
+ /** Negative margin applied to every item but the first. Set to `''` for a non-overlapping row. */
36
+ overlap?: string
37
+ }) {
38
+ const items = React.Children.toArray(children)
39
+ const [activeIndex, setActiveIndex] = useState<number | null>(null)
40
+ const [reducedMotion, setReducedMotion] = useState(false)
41
+
42
+ useEffect(() => {
43
+ const query = window.matchMedia('(prefers-reduced-motion: reduce)')
44
+ const sync = () => setReducedMotion(query.matches)
45
+
46
+ sync()
47
+ query.addEventListener('change', sync)
48
+ return () => query.removeEventListener('change', sync)
49
+ }, [])
50
+
51
+ const itemStyle = (index: number): React.CSSProperties | undefined => {
52
+ if (reducedMotion) {
53
+ return undefined
54
+ }
55
+
56
+ const isActive = index === activeIndex
57
+ const shift = activeIndex === null ? 0 : LIFT * FALLOFF ** Math.abs(index - activeIndex)
58
+
59
+ return {
60
+ // translateY before scale, so scaling never amplifies the lift distance.
61
+ transform: `translateY(${shift.toFixed(3)}px) scale(${isActive ? ACTIVE_SCALE : 1})`,
62
+ transition: `transform ${DURATION}ms ${activeIndex === null ? EASE_OUT : EASE_IN}`,
63
+ willChange: 'transform',
64
+ // A lifted item has to sit above the neighbour overlapping it, or the lift happens underneath.
65
+ zIndex: isActive ? 1 : undefined,
66
+ }
67
+ }
68
+
69
+ return (
70
+ <div className={cn('flex', className)} onMouseLeave={() => setActiveIndex(null)}>
71
+ {items.map((child, index) => (
72
+ <div
73
+ // eslint-disable-next-line react/no-array-index-key -- position is the identity here
74
+ key={index}
75
+ className={cn('relative', index > 0 && overlap)}
76
+ style={itemStyle(index)}
77
+ onMouseEnter={() => setActiveIndex(index)}
78
+ >
79
+ {child}
80
+ </div>
81
+ ))}
82
+ </div>
83
+ )
84
+ }
85
+
86
+ export default AvatarGroup
@@ -0,0 +1,135 @@
1
+ import { Button } from '../../../ui/button'
2
+ import { Avatar, AvatarFallback, AvatarImage } from '../../../ui/avatar'
3
+ import { Badge } from '../../../ui/badge'
4
+ import { AvatarGroup } from './avatar-group'
5
+ import { Star } from 'lucide-react'
6
+ import type { CTA } from '../../../lib/types'
7
+
8
+ interface AvatarItem {
9
+ id: string | number
10
+ avatarUrl: string
11
+ fallback: string
12
+ alt?: string
13
+ }
14
+
15
+ interface Rating {
16
+ value: number
17
+ count: number
18
+ }
19
+
20
+ interface HeroSection1Props {
21
+ title: string
22
+ description: string
23
+ badge?: string
24
+ cta?: CTA
25
+ reviewers?: AvatarItem[]
26
+ rating?: Rating
27
+ trustedBy?: string
28
+ image?: { src: string; alt: string }
29
+ }
30
+
31
+ export function HeroSection1({
32
+ title,
33
+ description,
34
+ badge,
35
+ cta,
36
+ reviewers,
37
+ rating,
38
+ trustedBy,
39
+ image,
40
+ }: HeroSection1Props) {
41
+ return (
42
+ <section className='w-full py-12 lg:py-20'>
43
+ <div className='mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8'>
44
+ <div className='grid items-center gap-8 lg:grid-cols-2 lg:gap-12'>
45
+ <div className='flex flex-col gap-8 text-center lg:text-start'>
46
+ <header className='flex flex-col gap-4'>
47
+ {badge && (
48
+ <Badge variant='outline' className='h-auto font-semibold rounded-md text-muted-foreground px-3.5 py-1 self-center lg:self-start'>
49
+ {badge}
50
+ </Badge>
51
+ )}
52
+ <h1 className='text-4xl font-bold tracking-tight text-balance md:text-5xl'>{title}</h1>
53
+ <p className='text-muted-foreground text-balance md:text-lg'>
54
+ {description}
55
+ </p>
56
+ </header>
57
+
58
+ {cta &&
59
+ (cta.href ? (
60
+ <Button
61
+ asChild
62
+ size='lg'
63
+ className='h-10 px-8 cursor-pointer text-base w-fit self-center lg:self-start'
64
+ >
65
+ <a href={cta.href}>{cta.label}</a>
66
+ </Button>
67
+ ) : (
68
+ <Button
69
+ size='lg'
70
+ className='h-10 px-8 cursor-pointer text-base w-fit self-center lg:self-start'
71
+ >
72
+ {cta.label}
73
+ </Button>
74
+ ))}
75
+
76
+ {(reviewers || rating || trustedBy) && (
77
+ <div className='flex flex-col items-center gap-4 sm:flex-row sm:justify-center lg:justify-start'>
78
+ {reviewers && (
79
+ <AvatarGroup overlap='-ml-3'>
80
+ {reviewers.map(reviewer => (
81
+ <Avatar
82
+ key={reviewer.id}
83
+ className='ring-background bg-muted size-10 ring-2'
84
+ >
85
+ <AvatarImage
86
+ src={reviewer.avatarUrl || undefined}
87
+ alt={reviewer.alt ?? `User ${reviewer.id}`}
88
+ />
89
+ <AvatarFallback className='text-xs'>{reviewer.fallback}</AvatarFallback>
90
+ </Avatar>
91
+ ))}
92
+ <div className='border-background bg-accent relative -ms-1.5 flex size-10 items-center justify-center rounded-full border-2'>
93
+ <span className='text-xs font-medium'>+{reviewers.length}</span>
94
+ </div>
95
+ </AvatarGroup>
96
+ )}
97
+
98
+ {(rating || trustedBy) && (
99
+ <div className='flex flex-col gap-0.5'>
100
+ {rating && (
101
+ <div className='flex items-center justify-center gap-1 lg:justify-start'>
102
+ {Array.from({ length: 5 }).map((_, i) => (
103
+ <Star
104
+ key={i}
105
+ className={`size-4 ${i < Math.round(rating.value) ? 'fill-foreground' : ''}`}
106
+ />
107
+ ))}
108
+ <span className='ms-2 text-sm font-medium'>({rating.count})</span>
109
+ </div>
110
+ )}
111
+ {trustedBy && <p className='text-muted-foreground text-sm'>{trustedBy}</p>}
112
+ </div>
113
+ )}
114
+ </div>
115
+ )}
116
+ </div>
117
+
118
+ {image && (
119
+ <div className='aspect-[4/3] overflow-hidden rounded-xl border'>
120
+ <img
121
+ src={image.src || undefined}
122
+ alt={image.alt}
123
+ className='size-full object-cover'
124
+ width={800}
125
+ height={600}
126
+ />
127
+ </div>
128
+ )}
129
+ </div>
130
+ </div>
131
+ </section>
132
+ )
133
+ }
134
+
135
+ export default HeroSection1
@@ -0,0 +1,52 @@
1
+ import { Skeleton } from '../../../ui/skeleton'
2
+ import { cn } from '../../../lib/utils'
3
+
4
+ const iconMap = {
5
+ nextdotjs:
6
+ 'M18.665 21.978C16.758 23.255 14.465 24 12 24 5.377 24 0 18.623 0 12S5.377 0 12 0s12 5.377 12 12c0 3.583-1.574 6.801-4.067 9.001L9.219 7.2H7.2v9.596h1.615V9.251l9.85 12.727Zm-3.332-8.533 1.6 2.061V7.2h-1.6v6.245Z',
7
+ typescript:
8
+ 'M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111a6.38 6.38 0 0 1 1.306.34v2.458a3.95 3.95 0 0 0-.643-.361 5.093 5.093 0 0 0-.717-.26 5.453 5.453 0 0 0-1.426-.2c-.3 0-.573.028-.819.086a2.1 2.1 0 0 0-.623.242c-.17.104-.3.229-.393.374a.888.888 0 0 0-.14.49c0 .196.053.373.156.529.104.156.252.304.443.444s.423.276.696.41c.273.135.582.274.926.416.47.197.892.407 1.266.628.374.222.695.473.963.753.268.279.472.598.614.957.142.359.214.776.214 1.253 0 .657-.125 1.21-.373 1.656a3.033 3.033 0 0 1-1.012 1.085 4.38 4.38 0 0 1-1.487.596c-.566.12-1.163.18-1.79.18a9.916 9.916 0 0 1-1.84-.164 5.544 5.544 0 0 1-1.512-.493v-2.63a5.033 5.033 0 0 0 3.237 1.2c.333 0 .624-.03.872-.09.249-.06.456-.144.623-.25.166-.108.29-.234.373-.38a1.023 1.023 0 0 0-.074-1.089 2.12 2.12 0 0 0-.537-.5 5.597 5.597 0 0 0-.807-.444 27.72 27.72 0 0 0-1.007-.436c-.918-.383-1.602-.852-2.053-1.405-.45-.553-.676-1.222-.676-2.005 0-.614.123-1.141.369-1.582.246-.441.58-.804 1.004-1.089a4.494 4.494 0 0 1 1.47-.629 7.536 7.536 0 0 1 1.77-.201zm-15.113.188h9.563v2.166H9.506v9.646H6.789v-9.646H3.375z',
9
+ tailwindcss:
10
+ 'M12.001,4.8c-3.2,0-5.2,1.6-6,4.8c1.2-1.6,2.6-2.2,4.2-1.8c0.913,0.228,1.565,0.89,2.288,1.624 C13.666,10.618,15.027,12,18.001,12c3.2,0,5.2-1.6,6-4.8c-1.2,1.6-2.6,2.2-4.2,1.8c-0.913-0.228-1.565-0.89-2.288-1.624 C16.337,6.182,14.976,4.8,12.001,4.8z M6.001,12c-3.2,0-5.2,1.6-6,4.8c1.2-1.6,2.6-2.2,4.2-1.8c0.913,0.228,1.565,0.89,2.288,1.624 c1.177,1.194,2.538,2.576,5.512,2.576c3.2,0,5.2-1.6,6-4.8c-1.2,1.6-2.6,2.2-4.2,1.8c-0.913-0.228-1.565-0.89-2.288-1.624 C10.337,13.382,8.976,12,6.001,12z',
11
+ react:
12
+ 'M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38-.318-.184-.688-.277-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44-.96-.236-2.006-.417-3.107-.534-.66-.905-1.345-1.727-2.035-2.447 1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442-1.107.117-2.154.298-3.113.538-.112-.49-.195-.964-.254-1.42-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87-.728.063-1.466.098-2.21.098-.74 0-1.477-.035-2.202-.093-.406-.582-.802-1.204-1.183-1.86-.372-.64-.71-1.29-1.018-1.946.303-.657.646-1.313 1.013-1.954.38-.66.773-1.286 1.18-1.868.728-.064 1.466-.098 2.21-.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933-.2-.39-.41-.783-.64-1.174-.225-.392-.465-.774-.705-1.146zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493-.28-.958-.646-1.956-1.1-2.98.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98-.45 1.017-.812 2.01-1.086 2.964-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39.24-.375.48-.762.705-1.158.225-.39.435-.788.636-1.18zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143-.695-.102-1.365-.23-2.006-.386.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295-.22-.005-.406-.05-.553-.132-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z',
13
+ prisma:
14
+ 'M21.8068 18.2848L13.5528.7565c-.207-.4382-.639-.7273-1.1286-.7541-.5023-.0293-.9523.213-1.2062.6253L2.266 15.1271c-.2773.4518-.2718 1.0091.0158 1.4555l4.3759 6.7786c.2608.4046.7127.6388 1.1823.6388.1332 0 .267-.0188.3987-.0577l12.7019-3.7568c.3891-.1151.7072-.3904.8737-.7553s.1633-.7828-.0075-1.1454zm-1.8481.7519L9.1814 22.2242c-.3292.0975-.6448-.1873-.5756-.5194l3.8501-18.4386c.072-.3448.5486-.3996.699-.0803l7.1288 15.138c.1344.2856-.019.6224-.325.7128z',
15
+ github:
16
+ 'M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12',
17
+ vercel: 'm12 1.608 12 20.784H0Z',
18
+ stripe:
19
+ 'M13.976 9.15c-2.172-.806-3.356-1.426-3.356-2.409 0-.831.683-1.305 1.901-1.305 2.227 0 4.515.858 6.09 1.631l.89-5.494C18.252.975 15.697 0 12.165 0 9.667 0 7.589.654 6.104 1.872 4.56 3.147 3.757 4.992 3.757 7.218c0 4.039 2.467 5.76 6.476 7.219 2.585.92 3.445 1.574 3.445 2.583 0 .98-.84 1.545-2.354 1.545-1.875 0-4.965-.921-6.99-2.109l-.9 5.555C5.175 22.99 8.385 24 11.714 24c2.641 0 4.843-.624 6.328-1.813 1.664-1.305 2.525-3.236 2.525-5.732 0-4.128-2.524-5.851-6.594-7.305h.003z',
20
+ figma:
21
+ 'M15.852 8.981h-4.588V0h4.588c2.476 0 4.49 2.014 4.49 4.49s-2.014 4.491-4.49 4.491zM12.735 7.51h3.117c1.665 0 3.019-1.355 3.019-3.019s-1.355-3.019-3.019-3.019h-3.117V7.51zm0 1.471H8.148c-2.476 0-4.49-2.014-4.49-4.49S5.672 0 8.148 0h4.588v8.981zm-4.587-7.51c-1.665 0-3.019 1.355-3.019 3.019s1.354 3.02 3.019 3.02h3.117V1.471H8.148zm4.587 15.019H8.148c-2.476 0-4.49-2.014-4.49-4.49s2.014-4.49 4.49-4.49h4.588v8.98zM8.148 8.981c-1.665 0-3.019 1.355-3.019 3.019s1.355 3.019 3.019 3.019h3.117V8.981H8.148zM8.172 24c-2.489 0-4.515-2.014-4.515-4.49s2.014-4.49 4.49-4.49h4.588v4.441c0 2.503-2.047 4.539-4.563 4.539zm-.024-7.51a3.023 3.023 0 0 0-3.019 3.019c0 1.665 1.365 3.019 3.044 3.019 1.705 0 3.093-1.376 3.093-3.068v-2.97H8.148zm7.704 0h-.098c-2.476 0-4.49-2.014-4.49-4.49s2.014-4.49 4.49-4.49h.098c2.476 0 4.49 2.014 4.49 4.49s-2.014 4.49-4.49 4.49zm-.097-7.509c-1.665 0-3.019 1.355-3.019 3.019s1.355 3.019 3.019 3.019h.098c1.665 0 3.019-1.355 3.019-3.019s-1.355-3.019-3.019-3.019h-.098z',
22
+ } as const
23
+
24
+ export type LogoCloud1LogoSlug = keyof typeof iconMap
25
+
26
+ export function LogoCloud1LogoIcon({
27
+ iconSlug,
28
+ size = 24,
29
+ className,
30
+ }: {
31
+ iconSlug: string
32
+ size?: number
33
+ className?: string
34
+ }) {
35
+ const iconPath = iconMap[iconSlug as LogoCloud1LogoSlug]
36
+
37
+ if (!iconPath) {
38
+ return <Skeleton className={cn('rounded-sm', className)} style={{ width: size, height: size }} />
39
+ }
40
+
41
+ return (
42
+ <svg
43
+ role='img'
44
+ viewBox='0 0 24 24'
45
+ className={cn('fill-foreground', className)}
46
+ style={{ width: size, height: size }}
47
+ aria-hidden='true'
48
+ >
49
+ <path d={iconPath} />
50
+ </svg>
51
+ )
52
+ }