blocks-dusted 0.1.11 → 0.1.13

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 (161) hide show
  1. package/package.json +5 -2
  2. package/src/cli.js +39 -3
  3. package/src/commands/shop.js +59 -0
  4. package/src/commands/warehouse.js +73 -0
  5. package/src/installers/patchRenderBlocks.js +1 -1
  6. package/src/registry/validateTemplateManifest.js +2 -1
  7. package/templates/blocks/BannerDD790432/Component.tsx +63 -0
  8. package/templates/blocks/BannerDD790432/config.ts +54 -0
  9. package/templates/blocks/BannerDD790432/manifest.json +80 -0
  10. package/templates/blocks/Button47/Component.tsx +124 -0
  11. package/templates/blocks/Button47/config.ts +75 -0
  12. package/templates/blocks/Button47/link.ts +22 -0
  13. package/templates/blocks/Button47/manifest.json +105 -0
  14. package/templates/blocks/Button47/styles.module.scss +75 -0
  15. package/templates/blocks/DD-Accordion/Component.tsx +55 -36
  16. package/templates/blocks/DD-Accordion/config.ts +71 -59
  17. package/templates/blocks/DD-AnimText/Component.tsx +50 -32
  18. package/templates/blocks/DD-AnimText/config.ts +296 -295
  19. package/templates/blocks/DD-AsideNav/Component.tsx +323 -319
  20. package/templates/blocks/DD-Banner/Component.tsx +54 -51
  21. package/templates/blocks/DD-BookCall/Component.tsx +10 -9
  22. package/templates/blocks/DD-ButtonPop/Component.tsx +118 -117
  23. package/templates/blocks/DD-Card/Component.tsx +4 -4
  24. package/templates/blocks/DD-CardTemplate01/Component.tsx +5 -5
  25. package/templates/blocks/DD-CardsWithVariantV3/Component.tsx +472 -0
  26. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/index.tsx +203 -0
  27. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/styles.ts +87 -0
  28. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/useInjectStyle.ts +39 -0
  29. package/templates/blocks/DD-CardsWithVariantV3/config.ts +326 -0
  30. package/templates/blocks/DD-CardsWithVariantV3/manifest.json +110 -0
  31. package/templates/blocks/DD-Carousel-A/Component.tsx +251 -249
  32. package/templates/blocks/DD-Carousel-B/Component.tsx +405 -403
  33. package/templates/blocks/DD-Chip/Component.tsx +14 -14
  34. package/templates/blocks/DD-ComparisonTable/Component.tsx +255 -256
  35. package/templates/blocks/DD-Contact/Component.tsx +433 -434
  36. package/templates/blocks/DD-Counter/Component.tsx +18 -13
  37. package/templates/blocks/DD-DeliCard/Component.tsx +303 -0
  38. package/templates/blocks/DD-DeliCard/config.ts +186 -0
  39. package/templates/blocks/DD-DeliCard/manifest.json +94 -0
  40. package/templates/blocks/DD-FeatCat/Component.tsx +302 -302
  41. package/templates/blocks/DD-FeatProd/CircularGallery.tsx +1179 -0
  42. package/templates/blocks/DD-FeatProd/Component.tsx +484 -0
  43. package/templates/blocks/DD-FeatProd/config.ts +173 -0
  44. package/templates/blocks/DD-FeatProd/manifest.json +93 -0
  45. package/templates/blocks/DD-FeatServ/Component.tsx +607 -0
  46. package/templates/blocks/DD-FeatServ/config.ts +213 -0
  47. package/templates/blocks/DD-FeatServ/manifest.json +113 -0
  48. package/templates/blocks/DD-FeatStrip/Component.tsx +584 -585
  49. package/templates/blocks/DD-FluidBtn/ButtonsEyeCatching.tsx +627 -628
  50. package/templates/blocks/DD-FluidBtn/Component.tsx +46 -43
  51. package/templates/blocks/DD-GooeyInfo/Component.tsx +150 -149
  52. package/templates/blocks/DD-GooeyInfo/config.ts +136 -135
  53. package/templates/blocks/DD-Hero/Component.tsx +16 -40
  54. package/templates/blocks/DD-HeroPlas/Component.tsx +50 -0
  55. package/templates/blocks/DD-HeroPlas/ThreeScene.tsx +109 -0
  56. package/templates/blocks/DD-HeroPlas/config.ts +33 -0
  57. package/templates/blocks/DD-HeroPlas/manifest.json +88 -0
  58. package/templates/blocks/DD-HorizontalScroll/Component.tsx +4 -5
  59. package/templates/blocks/DD-Icon/Component.tsx +46 -81
  60. package/templates/blocks/DD-IconButton/Component.tsx +13 -20
  61. package/templates/blocks/DD-IdTag/Component.tsx +22 -21
  62. package/templates/blocks/DD-IframeMedia/Component.tsx +14 -10
  63. package/templates/blocks/DD-LineAnim/Component.tsx +42 -36
  64. package/templates/blocks/DD-LogoMarq/Component.tsx +38 -48
  65. package/templates/blocks/DD-Lottie/Component.tsx +11 -3
  66. package/templates/blocks/DD-MasonaryMedia/Component.tsx +544 -547
  67. package/templates/blocks/DD-MasonaryMedia/config.ts +389 -389
  68. package/templates/blocks/DD-Masonry/Component.tsx +654 -644
  69. package/templates/blocks/DD-Masonry/config.ts +475 -475
  70. package/templates/blocks/DD-MaterialSwatches/Component.tsx +85 -0
  71. package/templates/blocks/DD-MaterialSwatches/config.ts +232 -0
  72. package/templates/blocks/DD-MaterialSwatches/manifest.json +84 -0
  73. package/templates/blocks/DD-MaterialSwatches/material.ts +107 -0
  74. package/templates/blocks/DD-NineCanvas/Component.tsx +129 -0
  75. package/templates/blocks/DD-NineCanvas/RenderBlocks.tsx +244 -0
  76. package/templates/blocks/DD-NineCanvas/allowedCanvasBlocks.ts +6 -0
  77. package/templates/blocks/DD-NineCanvas/blockComponents.tsx +8 -0
  78. package/templates/blocks/DD-NineCanvas/canvas/CanvasGrid.tsx +316 -0
  79. package/templates/blocks/DD-NineCanvas/canvas/RadialNav.tsx +432 -0
  80. package/templates/blocks/DD-NineCanvas/canvas/canvas-panels.css +889 -0
  81. package/templates/blocks/DD-NineCanvas/canvas/icons/custom-icons.tsx +364 -0
  82. package/templates/blocks/DD-NineCanvas/config.ts +61 -0
  83. package/templates/blocks/DD-NineCanvas/manifest.json +165 -0
  84. package/templates/blocks/DD-NineCanvas/styles.scss +152 -0
  85. package/templates/blocks/DD-NineCanvas/tooltip/get-strict-context.tsx +26 -0
  86. package/templates/blocks/DD-NineCanvas/tooltip/primitives.tsx +178 -0
  87. package/templates/blocks/DD-NineCanvas/tooltip/tooltip.tsx +71 -0
  88. package/templates/blocks/DD-NineCanvas/tooltip/use-controlled-state.tsx +33 -0
  89. package/templates/blocks/DD-NineCanvas/utilities/FadeOnScroll.tsx +68 -0
  90. package/templates/blocks/DD-NineCanvas/utilities/ParallaxWrapper.tsx +157 -0
  91. package/templates/blocks/DD-NineCanvas/utilities/renderCustomCSS.ts +5 -0
  92. package/templates/blocks/DD-NineCanvas/utilities/webglCleanup.ts +27 -0
  93. package/templates/blocks/DD-PayloadAIAssistant/Component.tsx +78 -0
  94. package/templates/blocks/DD-PayloadAIAssistant/DustedBot.tsx +590 -0
  95. package/templates/blocks/DD-PayloadAIAssistant/config.ts +37 -0
  96. package/templates/blocks/DD-PayloadAIAssistant/demo.css +125 -0
  97. package/templates/blocks/DD-PayloadAIAssistant/manifest.json +93 -0
  98. package/templates/blocks/DD-PayloadAIAssistant/source-defaults.ts +17 -0
  99. package/templates/blocks/DD-PayloadAIAssistant/styles.ts +306 -0
  100. package/templates/blocks/DD-Pricing/Component.tsx +368 -372
  101. package/templates/blocks/DD-Pricing/config.ts +215 -215
  102. package/templates/blocks/DD-Process/Component.tsx +147 -149
  103. package/templates/blocks/DD-Progress/Component.tsx +156 -141
  104. package/templates/blocks/DD-ProjHeader/Component.tsx +93 -91
  105. package/templates/blocks/DD-ProjSlider/Component.tsx +1 -1
  106. package/templates/blocks/DD-ProjSlider/config.ts +2 -2
  107. package/templates/blocks/DD-Section/Component.tsx +265 -266
  108. package/templates/blocks/DD-Separator/Component.tsx +66 -66
  109. package/templates/blocks/DD-Services/Component.tsx +176 -176
  110. package/templates/blocks/DD-ShowcaseGrid/Component.tsx +306 -307
  111. package/templates/blocks/DD-Slider-A/Component.tsx +237 -237
  112. package/templates/blocks/DD-StackCards/Component.tsx +474 -473
  113. package/templates/blocks/DD-Team/Component.tsx +247 -247
  114. package/templates/blocks/DD-TeamCards/Component.tsx +251 -251
  115. package/templates/blocks/DD-TechStack/Component.tsx +161 -168
  116. package/templates/blocks/DD-TestMarq/Component.tsx +158 -132
  117. package/templates/blocks/DD-Testim/Component.tsx +247 -247
  118. package/templates/blocks/DD-Testim2/Component.tsx +265 -256
  119. package/templates/blocks/DD-Testimonials/Component.tsx +139 -147
  120. package/templates/blocks/DD-TextMarq/Component.tsx +157 -157
  121. package/templates/blocks/DD-Thanks/Component.tsx +501 -500
  122. package/templates/blocks/DD-Work/Component.tsx +315 -315
  123. package/templates/blocks/DD-Work-B/Component.tsx +294 -294
  124. package/templates/blocks/DDHG-CTASection2/Component.tsx +183 -184
  125. package/templates/blocks/DDHG-HighlightArc/Component.tsx +246 -243
  126. package/templates/blocks/DDHG-LogoMarquee/Component.tsx +180 -93
  127. package/templates/blocks/DDHG-LogoMarquee/config.ts +154 -66
  128. package/templates/blocks/DDHG-NavCodeGrid/Component.tsx +225 -213
  129. package/templates/blocks/DDHG-ProcessSec/Component.tsx +38 -39
  130. package/templates/blocks/DDHG-ServiceDetails/Component.tsx +11 -11
  131. package/templates/blocks/DDHG-ServiceDetails/ServiceDetailCard.tsx +30 -47
  132. package/templates/blocks/DDHG-ServiceHero/Component.tsx +10 -10
  133. package/templates/blocks/DDHG-ServiceOverview/Component.tsx +23 -23
  134. package/templates/blocks/DDHG-StatsFAQ/Component.tsx +40 -40
  135. package/templates/blocks/DDHG-TeamCards/Component.tsx +140 -131
  136. package/templates/blocks/Form/Checkbox/index.tsx +45 -0
  137. package/templates/blocks/Form/Component.tsx +163 -0
  138. package/templates/blocks/Form/Country/index.tsx +65 -0
  139. package/templates/blocks/Form/Country/options.ts +982 -0
  140. package/templates/blocks/Form/Email/index.tsx +38 -0
  141. package/templates/blocks/Form/Error/index.tsx +15 -0
  142. package/templates/blocks/Form/Message/index.tsx +13 -0
  143. package/templates/blocks/Form/Number/index.tsx +36 -0
  144. package/templates/blocks/Form/Select/index.tsx +63 -0
  145. package/templates/blocks/Form/State/index.tsx +64 -0
  146. package/templates/blocks/Form/State/options.ts +52 -0
  147. package/templates/blocks/Form/Text/index.tsx +32 -0
  148. package/templates/blocks/Form/Textarea/index.tsx +40 -0
  149. package/templates/blocks/Form/Width/index.tsx +13 -0
  150. package/templates/blocks/Form/config.ts +51 -0
  151. package/templates/blocks/Form/fields.tsx +21 -0
  152. package/templates/blocks/Form/manifest.json +168 -0
  153. package/templates/blocks/MediaBlock/Component.tsx +67 -0
  154. package/templates/blocks/MediaBlock/config.ts +14 -0
  155. package/templates/blocks/MediaBlock/manifest.json +92 -0
  156. package/templates/blocks/TransitionsDustedShowcase/Component.tsx +9 -0
  157. package/templates/blocks/TransitionsDustedShowcase/config.ts +13 -0
  158. package/templates/blocks/TransitionsDustedShowcase/manifest.json +80 -0
  159. package/templates/blocks/rawHtml/Component.tsx +25 -0
  160. package/templates/blocks/rawHtml/config.ts +56 -0
  161. package/templates/blocks/rawHtml/manifest.json +80 -0
@@ -1,500 +1,501 @@
1
- 'use client'
2
-
3
- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
4
- import Link from 'next/link'
5
- import {
6
- Check,
7
- DownloadCloud,
8
- Home,
9
- LayoutGrid,
10
- MailCheck,
11
- ShieldCheck,
12
- Sparkles
13
- } from 'lucide-react'
14
- import { cn } from '@/utilities/ui'
15
-
16
- type InfoPill = {
17
- id?: string | null
18
- label?: string | null
19
- icon?: 'mail' | 'download' | 'shield' | null
20
- }
21
-
22
- type CtaLink = {
23
- label?: string | null
24
- href?: string | null
25
- variant?: 'primary' | 'secondary' | null
26
- icon?: 'home' | 'grid' | 'download' | null
27
- }
28
-
29
- type ThanksConfettiProps = {
30
- sectionId?: string | null
31
- customCSS?: string | null
32
- headline?: string | null
33
- description?: string | null
34
- subDescription?: string | null
35
- replayLabel?: string | null
36
- brandLabel?: string | null
37
- showBrandLabel?: boolean | null
38
- showReplayButton?: boolean | null
39
- autoPlayConfetti?: boolean | null
40
- confettiDuration?: number | null
41
- infoPills?: InfoPill[] | null
42
- ctaLinks?: CtaLink[] | null
43
- className?: string
44
- disableInnerContainer?: boolean
45
- }
46
-
47
- type ConfettiParticle = {
48
- x: number
49
- y: number
50
- vx: number
51
- vy: number
52
- size: number
53
- rotation: number
54
- rotationSpeed: number
55
- color: string
56
- shape: 'circle' | 'square'
57
- life: number
58
- maxLife: number
59
- }
60
-
61
- const CONFETTI_COLORS = ['#d6d3d1', '#a8a29e', '#10b981', '#6ee7b7', '#ffffff', '#f5f5f4']
62
-
63
- const defaultInfoPills: Required<Pick<InfoPill, 'label' | 'icon'>>[] = [
64
- { icon: 'mail', label: 'Confirmation sent' },
65
- { icon: 'download', label: 'Files ready' },
66
- { icon: 'shield', label: 'License activated' }
67
- ]
68
-
69
- const defaultCtaLinks: Required<Pick<CtaLink, 'label' | 'href' | 'variant' | 'icon'>>[] = [
70
- { label: 'Back to Home', href: '/', variant: 'primary', icon: 'home' },
71
- { label: 'Continue Browsing', href: '/components', variant: 'secondary', icon: 'grid' }
72
- ]
73
-
74
- function PillIcon({ icon }: { icon?: InfoPill['icon'] }) {
75
- if (icon === 'download') return <DownloadCloud className="h-3.5 w-3.5" />
76
- if (icon === 'shield') return <ShieldCheck className="h-3.5 w-3.5" />
77
-
78
- return <MailCheck className="h-3.5 w-3.5" />
79
- }
80
-
81
- function CtaIcon({ icon }: { icon?: CtaLink['icon'] }) {
82
- if (icon === 'grid') return <LayoutGrid className="h-4 w-4" />
83
- if (icon === 'download') return <DownloadCloud className="h-4 w-4" />
84
-
85
- return <Home className="h-4 w-4" />
86
- }
87
-
88
- function getSafeDuration(duration?: number | null) {
89
- if (typeof duration !== 'number' || !Number.isFinite(duration)) return 3000
90
- return Math.min(Math.max(duration, 1000), 8000)
91
- }
92
-
93
- function createParticles({
94
- amount,
95
- originX,
96
- originY,
97
- angle,
98
- spread,
99
- velocity,
100
- width,
101
- height
102
- }: {
103
- amount: number
104
- originX: number
105
- originY: number
106
- angle?: number
107
- spread: number
108
- velocity: number
109
- width: number
110
- height: number
111
- }): ConfettiParticle[] {
112
- return Array.from({ length: amount }, () => {
113
- const baseAngle = typeof angle === 'number' ? angle : Math.random() * 360
114
- const randomAngle = (baseAngle - spread / 2 + Math.random() * spread) * (Math.PI / 180)
115
- const speed = velocity * (0.45 + Math.random() * 0.75)
116
-
117
- return {
118
- x: originX * width,
119
- y: originY * height,
120
- vx: Math.cos(randomAngle) * speed,
121
- vy: Math.sin(randomAngle) * speed,
122
- size: 4 + Math.random() * 7,
123
- rotation: Math.random() * Math.PI,
124
- rotationSpeed: -0.22 + Math.random() * 0.44,
125
- color: CONFETTI_COLORS[Math.floor(Math.random() * CONFETTI_COLORS.length)] ?? '#ffffff',
126
- shape: Math.random() > 0.5 ? 'circle' : 'square',
127
- life: 0,
128
- maxLife: 90 + Math.random() * 70
129
- }
130
- })
131
- }
132
-
133
- export function DDThanks({
134
- sectionId,
135
- headline = 'Thank you! 🎉',
136
- description = 'Your order has been confirmed and is on its way.',
137
- subDescription = "We've sent a confirmation email with your download links and license keys. Check your inbox — and maybe your spam folder too.",
138
- replayLabel = 'Replay confetti',
139
- brandLabel = 'DesignsDusted',
140
- showBrandLabel = true,
141
- showReplayButton = true,
142
- autoPlayConfetti = true,
143
- confettiDuration = 3000,
144
- infoPills,
145
- ctaLinks,
146
- className
147
- }: ThanksConfettiProps) {
148
- const hasRun = useRef(false)
149
- const canvasRef = useRef<HTMLCanvasElement | null>(null)
150
- const animationRef = useRef<number | null>(null)
151
- const particlesRef = useRef<ConfettiParticle[]>([])
152
- const [visible, setVisible] = useState(false)
153
-
154
- const pills = useMemo(() => {
155
- const source = infoPills?.length ? infoPills : defaultInfoPills
156
- return source.filter((pill) => pill.label)
157
- }, [infoPills])
158
-
159
- const links = useMemo(() => {
160
- const source = ctaLinks?.length ? ctaLinks : defaultCtaLinks
161
- return source.filter((link) => link.label && link.href)
162
- }, [ctaLinks])
163
-
164
- const stopAnimation = useCallback(() => {
165
- if (animationRef.current) {
166
- cancelAnimationFrame(animationRef.current)
167
- animationRef.current = null
168
- }
169
- }, [])
170
-
171
- const fireConfetti = useCallback(() => {
172
- const canvas = canvasRef.current
173
- const ctx = canvas?.getContext('2d')
174
- if (!canvas || !ctx) return
175
-
176
- const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
177
- if (reducedMotion) return
178
-
179
- const pixelRatio = Math.min(window.devicePixelRatio || 1, 2)
180
- const width = window.innerWidth
181
- const height = window.innerHeight
182
-
183
- canvas.width = width * pixelRatio
184
- canvas.height = height * pixelRatio
185
- canvas.style.width = `${width}px`
186
- canvas.style.height = `${height}px`
187
- ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0)
188
-
189
- const duration = getSafeDuration(confettiDuration)
190
- const end = performance.now() + duration
191
-
192
- particlesRef.current = [
193
- ...particlesRef.current,
194
- ...createParticles({
195
- amount: 120,
196
- originX: 0.5,
197
- originY: 0.5,
198
- spread: 100,
199
- velocity: 20,
200
- width,
201
- height
202
- }),
203
- ...createParticles({
204
- amount: 60,
205
- originX: 0,
206
- originY: 0.6,
207
- angle: -35,
208
- spread: 70,
209
- velocity: 18,
210
- width,
211
- height
212
- }),
213
- ...createParticles({
214
- amount: 60,
215
- originX: 1,
216
- originY: 0.6,
217
- angle: 215,
218
- spread: 70,
219
- velocity: 18,
220
- width,
221
- height
222
- })
223
- ]
224
-
225
- stopAnimation()
226
-
227
- const frame = (now: number) => {
228
- ctx.clearRect(0, 0, width, height)
229
-
230
- if (now < end && Math.random() > 0.35) {
231
- particlesRef.current.push(
232
- ...createParticles({
233
- amount: 2,
234
- originX: 0,
235
- originY: 0.3,
236
- angle: -25,
237
- spread: 55,
238
- velocity: 10,
239
- width,
240
- height
241
- }),
242
- ...createParticles({
243
- amount: 2,
244
- originX: 1,
245
- originY: 0.3,
246
- angle: 205,
247
- spread: 55,
248
- velocity: 10,
249
- width,
250
- height
251
- })
252
- )
253
- }
254
-
255
- particlesRef.current = particlesRef.current.filter((particle) => {
256
- particle.life += 1
257
- particle.x += particle.vx
258
- particle.y += particle.vy
259
- particle.vy += 0.28
260
- particle.vx *= 0.992
261
- particle.rotation += particle.rotationSpeed
262
-
263
- const opacity = Math.max(0, 1 - particle.life / particle.maxLife)
264
- if (opacity <= 0 || particle.y > height + 40) return false
265
-
266
- ctx.save()
267
- ctx.globalAlpha = opacity
268
- ctx.translate(particle.x, particle.y)
269
- ctx.rotate(particle.rotation)
270
- ctx.fillStyle = particle.color
271
-
272
- if (particle.shape === 'circle') {
273
- ctx.beginPath()
274
- ctx.arc(0, 0, particle.size * 0.5, 0, Math.PI * 2)
275
- ctx.fill()
276
- } else {
277
- ctx.fillRect(-particle.size / 2, -particle.size / 2, particle.size, particle.size)
278
- }
279
-
280
- ctx.restore()
281
- return true
282
- })
283
-
284
- if (particlesRef.current.length || now < end) {
285
- animationRef.current = requestAnimationFrame(frame)
286
- } else {
287
- ctx.clearRect(0, 0, width, height)
288
- animationRef.current = null
289
- }
290
- }
291
-
292
- animationRef.current = requestAnimationFrame(frame)
293
- }, [confettiDuration, stopAnimation])
294
-
295
- useEffect(() => {
296
- if (hasRun.current) return
297
- hasRun.current = true
298
-
299
- const timer = window.setTimeout(() => {
300
- setVisible(true)
301
- if (autoPlayConfetti !== false) fireConfetti()
302
- }, 200)
303
-
304
- return () => window.clearTimeout(timer)
305
- }, [autoPlayConfetti, fireConfetti])
306
-
307
- useEffect(() => {
308
- return () => {
309
- stopAnimation()
310
- particlesRef.current = []
311
- }
312
- }, [stopAnimation])
313
-
314
- return (
315
- <section
316
- id={sectionId ?? undefined}
317
- className={cn(
318
- 'thanks-confetti-section relative flex min-h-screen w-full flex-col items-center justify-center overflow-hidden bg-stone-950 px-4 py-24 text-center',
319
- className
320
- )}
321
- >
322
- <canvas
323
- ref={canvasRef}
324
- aria-hidden="true"
325
- className="pointer-events-none fixed inset-0 z-[60] h-screen w-screen"
326
- />
327
-
328
- <div className="pointer-events-none absolute inset-0">
329
- <div
330
- className="absolute inset-0 opacity-20"
331
- style={{
332
- backgroundImage:
333
- 'radial-gradient(circle at 20% 50%, rgba(16,185,129,0.12) 0%, transparent 50%), radial-gradient(circle at 80% 20%, rgba(255,255,255,0.05) 0%, transparent 50%)'
334
- }}
335
- />
336
- <div
337
- className="absolute inset-0 opacity-5"
338
- style={{
339
- backgroundImage:
340
- 'linear-gradient(rgba(255,255,255,0.5) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.5) 1px, transparent 1px)',
341
- backgroundSize: '60px 60px'
342
- }}
343
- />
344
- </div>
345
-
346
- <div
347
- className="relative z-10 flex w-full max-w-lg flex-col items-center px-2"
348
- style={{
349
- opacity: visible ? 1 : 0,
350
- transform: visible ? 'translateY(0) scale(1)' : 'translateY(32px) scale(0.96)',
351
- transition:
352
- 'opacity 0.7s cubic-bezier(0.34,1.56,0.64,1), transform 0.7s cubic-bezier(0.34,1.56,0.64,1)'
353
- }}
354
- >
355
- <div className="relative mb-8">
356
- <div
357
- className="flex h-24 w-24 items-center justify-center rounded-full"
358
- style={{
359
- background: 'linear-gradient(135deg, #10b981 0%, #6ee7b7 100%)',
360
- boxShadow: '0 0 60px rgba(16,185,129,0.35), 0 0 120px rgba(16,185,129,0.15)',
361
- opacity: visible ? 1 : 0,
362
- transform: visible ? 'scale(1)' : 'scale(0.5)',
363
- transition:
364
- 'opacity 0.5s ease 0.3s, transform 0.6s cubic-bezier(0.34,1.56,0.64,1) 0.3s'
365
- }}
366
- >
367
- <Check className="h-11 w-11 stroke-[3px] text-white" />
368
- </div>
369
- <div
370
- className="absolute inset-0 rounded-full bg-emerald-500/20"
371
- style={{
372
- animation: visible
373
- ? 'thanks-confetti-ping 2s cubic-bezier(0,0,0.2,1) infinite 0.8s'
374
- : 'none'
375
- }}
376
- />
377
- </div>
378
-
379
- <div
380
- style={{
381
- opacity: visible ? 1 : 0,
382
- transform: visible ? 'translateY(0)' : 'translateY(20px)',
383
- transition: 'opacity 0.6s ease 0.5s, transform 0.6s ease 0.5s'
384
- }}
385
- >
386
- {headline && (
387
- <h1 className="mb-3 text-4xl font-bold tracking-tight text-white md:text-5xl">
388
- {headline}
389
- </h1>
390
- )}
391
-
392
- {description && (
393
- <p className="mb-2 text-lg leading-relaxed text-stone-400">{description}</p>
394
- )}
395
-
396
- {subDescription && (
397
- <p className="mb-10 text-sm leading-relaxed text-stone-500">{subDescription}</p>
398
- )}
399
- </div>
400
-
401
- {pills.length > 0 && (
402
- <div
403
- className="mb-10 flex flex-wrap items-center justify-center gap-3"
404
- style={{
405
- opacity: visible ? 1 : 0,
406
- transform: visible ? 'translateY(0)' : 'translateY(16px)',
407
- transition: 'opacity 0.6s ease 0.7s, transform 0.6s ease 0.7s'
408
- }}
409
- >
410
- {pills.map((item, index) => (
411
- <div
412
- key={
413
- 'id' in item ? (item.id ?? `${item.label}-${index}`) : `${item.label}-${index}`
414
- }
415
- className="flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-4 py-2"
416
- >
417
- <span className="flex h-4 w-4 items-center justify-center text-emerald-400">
418
- <PillIcon icon={item.icon} />
419
- </span>
420
- <span className="whitespace-nowrap text-sm text-stone-400">{item.label}</span>
421
- </div>
422
- ))}
423
- </div>
424
- )}
425
-
426
- {links.length > 0 && (
427
- <div
428
- className="flex w-full flex-col gap-3 sm:flex-row"
429
- style={{
430
- opacity: visible ? 1 : 0,
431
- transform: visible ? 'translateY(0)' : 'translateY(16px)',
432
- transition: 'opacity 0.6s ease 0.9s, transform 0.6s ease 0.9s'
433
- }}
434
- >
435
- {links.map((link, index) => {
436
- const isPrimary =
437
- (link.variant ?? (index === 0 ? 'primary' : 'secondary')) === 'primary'
438
-
439
- return (
440
- <Link
441
- key={`${link.href}-${index}`}
442
- href={link.href ?? '#'}
443
- className={cn(
444
- 'inline-flex flex-1 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg px-6 py-3.5 text-sm font-semibold transition-colors',
445
- isPrimary
446
- ? 'bg-white text-stone-900 hover:bg-stone-100'
447
- : 'border border-white/15 bg-white/10 text-white hover:bg-white/20'
448
- )}
449
- >
450
- <CtaIcon icon={link.icon} />
451
- {link.label}
452
- </Link>
453
- )
454
- })}
455
- </div>
456
- )}
457
-
458
- {showReplayButton !== false && replayLabel && (
459
- <div
460
- className="mt-8"
461
- style={{
462
- opacity: visible ? 1 : 0,
463
- transition: 'opacity 0.6s ease 1.1s'
464
- }}
465
- >
466
- <button
467
- type="button"
468
- onClick={fireConfetti}
469
- className="flex cursor-pointer items-center gap-1.5 text-xs text-stone-600 transition-colors hover:text-stone-400"
470
- >
471
- <Sparkles className="h-3 w-3" />
472
- {replayLabel}
473
- </button>
474
- </div>
475
- )}
476
- </div>
477
-
478
- {showBrandLabel !== false && brandLabel && (
479
- <div
480
- className="absolute bottom-8 left-1/2 z-10 -translate-x-1/2 text-xs font-black uppercase tracking-[0.35em] text-stone-500"
481
- style={{
482
- opacity: visible ? 0.4 : 0,
483
- transition: 'opacity 0.6s ease 1.2s'
484
- }}
485
- >
486
- {brandLabel}
487
- </div>
488
- )}
489
-
490
- <style>{`
491
- @keyframes thanks-confetti-ping {
492
- 75%, 100% {
493
- transform: scale(2);
494
- opacity: 0;
495
- }
496
- }
497
- `}</style>
498
- </section>
499
- )
500
- }
1
+ 'use client'
2
+
3
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
4
+ import Link from 'next/link'
5
+ import {
6
+ Check,
7
+ DownloadCloud,
8
+ Home,
9
+ LayoutGrid,
10
+ MailCheck,
11
+ ShieldCheck,
12
+ Sparkles,
13
+ } from 'lucide-react'
14
+ import { cn } from '@/utilities/ui'
15
+
16
+ type InfoPill = {
17
+ id?: string | null
18
+ label?: string | null
19
+ icon?: 'mail' | 'download' | 'shield' | null
20
+ }
21
+
22
+ type CtaLink = {
23
+ label?: string | null
24
+ href?: string | null
25
+ variant?: 'primary' | 'secondary' | null
26
+ icon?: 'home' | 'grid' | 'download' | null
27
+ }
28
+
29
+ type ThanksConfettiProps = {
30
+ sectionId?: string | null
31
+ customCSS?: string | null
32
+ headline?: string | null
33
+ description?: string | null
34
+ subDescription?: string | null
35
+ replayLabel?: string | null
36
+ brandLabel?: string | null
37
+ showBrandLabel?: boolean | null
38
+ showReplayButton?: boolean | null
39
+ autoPlayConfetti?: boolean | null
40
+ confettiDuration?: number | null
41
+ infoPills?: InfoPill[] | null
42
+ ctaLinks?: CtaLink[] | null
43
+ className?: string
44
+ disableInnerContainer?: boolean
45
+ }
46
+
47
+ type ConfettiParticle = {
48
+ x: number
49
+ y: number
50
+ vx: number
51
+ vy: number
52
+ size: number
53
+ rotation: number
54
+ rotationSpeed: number
55
+ color: string
56
+ shape: 'circle' | 'square'
57
+ life: number
58
+ maxLife: number
59
+ }
60
+
61
+ const CONFETTI_COLORS = ['#d6d3d1', '#a8a29e', '#10b981', '#6ee7b7', '#ffffff', '#f5f5f4']
62
+
63
+ const defaultInfoPills: Required<Pick<InfoPill, 'label' | 'icon'>>[] = [
64
+ { icon: 'mail', label: 'Confirmation sent' },
65
+ { icon: 'download', label: 'Files ready' },
66
+ { icon: 'shield', label: 'License activated' },
67
+ ]
68
+
69
+ const defaultCtaLinks: Required<Pick<CtaLink, 'label' | 'href' | 'variant' | 'icon'>>[] = [
70
+ { label: 'Back to Home', href: '/', variant: 'primary', icon: 'home' },
71
+ { label: 'Continue Browsing', href: '/components', variant: 'secondary', icon: 'grid' },
72
+ ]
73
+
74
+ function PillIcon({ icon }: { icon?: InfoPill['icon'] }) {
75
+ if (icon === 'download') return <DownloadCloud className="h-3.5 w-3.5" />
76
+ if (icon === 'shield') return <ShieldCheck className="h-3.5 w-3.5" />
77
+
78
+ return <MailCheck className="h-3.5 w-3.5" />
79
+ }
80
+
81
+ function CtaIcon({ icon }: { icon?: CtaLink['icon'] }) {
82
+ if (icon === 'grid') return <LayoutGrid className="h-4 w-4" />
83
+ if (icon === 'download') return <DownloadCloud className="h-4 w-4" />
84
+
85
+ return <Home className="h-4 w-4" />
86
+ }
87
+
88
+ function getSafeDuration(duration?: number | null) {
89
+ if (typeof duration !== 'number' || !Number.isFinite(duration)) return 3000
90
+ return Math.min(Math.max(duration, 1000), 8000)
91
+ }
92
+
93
+ function createParticles({
94
+ amount,
95
+ originX,
96
+ originY,
97
+ angle,
98
+ spread,
99
+ velocity,
100
+ width,
101
+ height,
102
+ }: {
103
+ amount: number
104
+ originX: number
105
+ originY: number
106
+ angle?: number
107
+ spread: number
108
+ velocity: number
109
+ width: number
110
+ height: number
111
+ }): ConfettiParticle[] {
112
+ return Array.from({ length: amount }, () => {
113
+ const baseAngle = typeof angle === 'number' ? angle : Math.random() * 360
114
+ const randomAngle = (baseAngle - spread / 2 + Math.random() * spread) * (Math.PI / 180)
115
+ const speed = velocity * (0.45 + Math.random() * 0.75)
116
+
117
+ return {
118
+ x: originX * width,
119
+ y: originY * height,
120
+ vx: Math.cos(randomAngle) * speed,
121
+ vy: Math.sin(randomAngle) * speed,
122
+ size: 4 + Math.random() * 7,
123
+ rotation: Math.random() * Math.PI,
124
+ rotationSpeed: -0.22 + Math.random() * 0.44,
125
+ color: CONFETTI_COLORS[Math.floor(Math.random() * CONFETTI_COLORS.length)] ?? '#ffffff',
126
+ shape: Math.random() > 0.5 ? 'circle' : 'square',
127
+ life: 0,
128
+ maxLife: 90 + Math.random() * 70,
129
+ }
130
+ })
131
+ }
132
+
133
+ export function DDThanks({
134
+ sectionId,
135
+ headline = 'Thank you! 🎉',
136
+ description = 'Your order has been confirmed and is on its way.',
137
+ subDescription = "We've sent a confirmation email with your download links and license keys. Check your inbox — and maybe your spam folder too.",
138
+ replayLabel = 'Replay confetti',
139
+ brandLabel = 'DesignsDusted',
140
+ showBrandLabel = true,
141
+ showReplayButton = true,
142
+ autoPlayConfetti = true,
143
+ confettiDuration = 3000,
144
+ infoPills,
145
+ ctaLinks,
146
+ className,
147
+ }: ThanksConfettiProps) {
148
+ const hasRun = useRef(false)
149
+ const canvasRef = useRef<HTMLCanvasElement | null>(null)
150
+ const animationRef = useRef<number | null>(null)
151
+ const particlesRef = useRef<ConfettiParticle[]>([])
152
+ const [visible, setVisible] = useState(false)
153
+
154
+ const pills = useMemo(() => {
155
+ const source = infoPills?.length ? infoPills : defaultInfoPills
156
+ return source.filter((pill) => pill.label)
157
+ }, [infoPills])
158
+
159
+ const links = useMemo(() => {
160
+ const source = ctaLinks?.length ? ctaLinks : defaultCtaLinks
161
+ return source.filter((link) => link.label && link.href)
162
+ }, [ctaLinks])
163
+
164
+ const stopAnimation = useCallback(() => {
165
+ if (animationRef.current) {
166
+ cancelAnimationFrame(animationRef.current)
167
+ animationRef.current = null
168
+ }
169
+ }, [])
170
+
171
+ const fireConfetti = useCallback(() => {
172
+ const canvas = canvasRef.current
173
+ const ctx = canvas?.getContext('2d')
174
+ if (!canvas || !ctx) return
175
+
176
+ const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
177
+ if (reducedMotion) return
178
+
179
+ const pixelRatio = Math.min(window.devicePixelRatio || 1, 2)
180
+ const width = window.innerWidth
181
+ const height = window.innerHeight
182
+
183
+ canvas.width = width * pixelRatio
184
+ canvas.height = height * pixelRatio
185
+ canvas.style.width = `${width}px`
186
+ canvas.style.height = `${height}px`
187
+ ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0)
188
+
189
+ const duration = getSafeDuration(confettiDuration)
190
+ const end = performance.now() + duration
191
+
192
+ particlesRef.current = [
193
+ ...particlesRef.current,
194
+ ...createParticles({
195
+ amount: 120,
196
+ originX: 0.5,
197
+ originY: 0.5,
198
+ spread: 100,
199
+ velocity: 20,
200
+ width,
201
+ height,
202
+ }),
203
+ ...createParticles({
204
+ amount: 60,
205
+ originX: 0,
206
+ originY: 0.6,
207
+ angle: -35,
208
+ spread: 70,
209
+ velocity: 18,
210
+ width,
211
+ height,
212
+ }),
213
+ ...createParticles({
214
+ amount: 60,
215
+ originX: 1,
216
+ originY: 0.6,
217
+ angle: 215,
218
+ spread: 70,
219
+ velocity: 18,
220
+ width,
221
+ height,
222
+ }),
223
+ ]
224
+
225
+ stopAnimation()
226
+
227
+ const frame = (now: number) => {
228
+ ctx.clearRect(0, 0, width, height)
229
+
230
+ if (now < end && Math.random() > 0.35) {
231
+ particlesRef.current.push(
232
+ ...createParticles({
233
+ amount: 2,
234
+ originX: 0,
235
+ originY: 0.3,
236
+ angle: -25,
237
+ spread: 55,
238
+ velocity: 10,
239
+ width,
240
+ height,
241
+ }),
242
+ ...createParticles({
243
+ amount: 2,
244
+ originX: 1,
245
+ originY: 0.3,
246
+ angle: 205,
247
+ spread: 55,
248
+ velocity: 10,
249
+ width,
250
+ height,
251
+ }),
252
+ )
253
+ }
254
+
255
+ particlesRef.current = particlesRef.current.filter((particle) => {
256
+ particle.life += 1
257
+ particle.x += particle.vx
258
+ particle.y += particle.vy
259
+ particle.vy += 0.28
260
+ particle.vx *= 0.992
261
+ particle.rotation += particle.rotationSpeed
262
+
263
+ const opacity = Math.max(0, 1 - particle.life / particle.maxLife)
264
+ if (opacity <= 0 || particle.y > height + 40) return false
265
+
266
+ ctx.save()
267
+ ctx.globalAlpha = opacity
268
+ ctx.translate(particle.x, particle.y)
269
+ ctx.rotate(particle.rotation)
270
+ ctx.fillStyle = particle.color
271
+
272
+ if (particle.shape === 'circle') {
273
+ ctx.beginPath()
274
+ ctx.arc(0, 0, particle.size * 0.5, 0, Math.PI * 2)
275
+ ctx.fill()
276
+ } else {
277
+ ctx.fillRect(-particle.size / 2, -particle.size / 2, particle.size, particle.size)
278
+ }
279
+
280
+ ctx.restore()
281
+ return true
282
+ })
283
+
284
+ if (particlesRef.current.length || now < end) {
285
+ animationRef.current = requestAnimationFrame(frame)
286
+ } else {
287
+ ctx.clearRect(0, 0, width, height)
288
+ animationRef.current = null
289
+ }
290
+ }
291
+
292
+ animationRef.current = requestAnimationFrame(frame)
293
+ }, [confettiDuration, stopAnimation])
294
+
295
+ useEffect(() => {
296
+ if (hasRun.current) return
297
+ hasRun.current = true
298
+
299
+ const timer = window.setTimeout(() => {
300
+ setVisible(true)
301
+ if (autoPlayConfetti !== false) fireConfetti()
302
+ }, 200)
303
+
304
+ return () => window.clearTimeout(timer)
305
+ }, [autoPlayConfetti, fireConfetti])
306
+
307
+ useEffect(() => {
308
+ return () => {
309
+ stopAnimation()
310
+ particlesRef.current = []
311
+ }
312
+ }, [stopAnimation])
313
+
314
+ return (
315
+ <section
316
+ id={sectionId ?? undefined}
317
+ className={cn(
318
+ 'thanks-confetti-section relative flex min-h-screen w-full flex-col items-center justify-center overflow-hidden bg-stone-transparent dark:bg-stone-950 px-4 py-4 text-center hue-rotate-300 invert-100 dark:invert-0',
319
+ className,
320
+ )}
321
+ >
322
+ <canvas
323
+ ref={canvasRef}
324
+ aria-hidden="true"
325
+ className="pointer-events-none fixed inset-0 z-60 h-screen w-screen"
326
+ />
327
+
328
+ <div className="pointer-events-none absolute inset-0">
329
+ <div
330
+ className="absolute inset-0 opacity-20"
331
+ style={{
332
+ backgroundImage:
333
+ 'radial-gradient(circle at 20% 50%, rgba(16,185,129,0.12) 0%, transparent 50%), radial-gradient(circle at 80% 20%, rgba(255,255,255,0.05) 0%, transparent 50%)',
334
+ }}
335
+ />
336
+ <div
337
+ className="absolute inset-0 opacity-5"
338
+ style={{
339
+ backgroundImage:
340
+ 'linear-gradient(rgba(255,255,255,0.5) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.5) 1px, transparent 1px)',
341
+ backgroundSize: '60px 60px',
342
+ }}
343
+ />
344
+ </div>
345
+
346
+ <div
347
+ className="relative z-10 flex w-full max-w-lg flex-col items-center px-2"
348
+ style={{
349
+ opacity: visible ? 1 : 0,
350
+ transform: visible ? 'translateY(0) scale(1)' : 'translateY(32px) scale(0.96)',
351
+ transition:
352
+ 'opacity 0.7s cubic-bezier(0.34,1.56,0.64,1), transform 0.7s cubic-bezier(0.34,1.56,0.64,1)',
353
+ }}
354
+ >
355
+ <div className="relative mb-8">
356
+ <div
357
+ className="flex h-24 w-24 items-center justify-center rounded-full border border-emerald-500/10"
358
+ style={{
359
+ background: 'linear-gradient(135deg, #10b981 0%, #6ee7b7 100%)',
360
+ boxShadow: '0 0 60px rgba(16,185,129,0.35), 0 0 120px rgba(16,185,129,0.15)',
361
+ opacity: visible ? 1 : 0,
362
+ transform: visible ? 'scale(1)' : 'scale(0.5)',
363
+ transition:
364
+ 'opacity 0.5s ease 0.3s, transform 0.6s cubic-bezier(0.34,1.56,0.64,1) 0.3s',
365
+ }}
366
+ >
367
+ <Check className="h-11 w-11 stroke-[3px] text-emerald-500 dark:text-white" />
368
+ </div>
369
+ <div
370
+ className="absolute inset-0 rounded-full bg-emerald-500/20"
371
+ style={{
372
+ animation: visible
373
+ ? 'thanks-confetti-ping 2s cubic-bezier(0,0,0.2,1) infinite 0.8s'
374
+ : 'none',
375
+ }}
376
+ />
377
+ </div>
378
+
379
+ <div
380
+ style={{
381
+ opacity: visible ? 1 : 0,
382
+ transform: visible ? 'translateY(0)' : 'translateY(20px)',
383
+ transition: 'opacity 0.6s ease 0.5s, transform 0.6s ease 0.5s',
384
+ }}
385
+ >
386
+ {headline && (
387
+ <h1 className="mb-3 text-4xl font-bold tracking-tight text-white md:text-5xl">
388
+ {headline}
389
+ </h1>
390
+ )}
391
+
392
+ {description && (
393
+ <p className="mb-2 text-lg leading-relaxed text-stone-400">{description}</p>
394
+ )}
395
+
396
+ {subDescription && (
397
+ <p className="mb-10 text-sm leading-relaxed text-stone-500">{subDescription}</p>
398
+ )}
399
+ </div>
400
+
401
+ {pills.length > 0 && (
402
+ <div
403
+ className="mb-10 flex flex-wrap items-center justify-center gap-3"
404
+ style={{
405
+ opacity: visible ? 1 : 0,
406
+ transform: visible ? 'translateY(0)' : 'translateY(16px)',
407
+ transition: 'opacity 0.6s ease 0.7s, transform 0.6s ease 0.7s',
408
+ }}
409
+ >
410
+ {pills.map((item, index) => (
411
+ <div
412
+ key={
413
+ 'id' in item ? (item.id ?? `${item.label}-${index}`) : `${item.label}-${index}`
414
+ }
415
+ className="flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-4 py-2"
416
+ >
417
+ <span className="flex h-4 w-4 items-center justify-center text-emerald-400">
418
+ <PillIcon icon={item.icon} />
419
+ </span>
420
+ <span className="whitespace-nowrap text-sm text-stone-400">{item.label}</span>
421
+ </div>
422
+ ))}
423
+ </div>
424
+ )}
425
+
426
+ {links.length > 0 && (
427
+ <div
428
+ className="flex w-full flex-col gap-3 sm:flex-row"
429
+ style={{
430
+ opacity: visible ? 1 : 0,
431
+ transform: visible ? 'translateY(0)' : 'translateY(16px)',
432
+ transition: 'opacity 0.6s ease 0.9s, transform 0.6s ease 0.9s',
433
+ }}
434
+ >
435
+ {links.map((link, index) => {
436
+ const isPrimary =
437
+ (link.variant ?? (index === 0 ? 'primary' : 'secondary')) === 'primary'
438
+
439
+ return (
440
+ <Link
441
+ key={`${link.href}-${index}`}
442
+ href={link.href ?? '#'}
443
+ className={cn(
444
+ 'inline-flex flex-1 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg px-6 py-3.5 text-sm font-semibold transition-colors',
445
+ 'backdrop-blur-lg rounded-2xl squircle px-5 py-2.5 text-sm font-medium transition-all duration-300 0 hover:shadow-2xl hover:-translate-y-0.5 hover:transition-all hover:duration-300',
446
+ isPrimary
447
+ ? 'bg-white text-stone-900 hover:bg-stone-100'
448
+ : 'border border-white/15 bg-white/10 text-white hover:bg-white/20',
449
+ )}
450
+ >
451
+ <CtaIcon icon={link.icon} />
452
+ {link.label}
453
+ </Link>
454
+ )
455
+ })}
456
+ </div>
457
+ )}
458
+
459
+ {showReplayButton !== false && replayLabel && (
460
+ <div
461
+ className="mt-8"
462
+ style={{
463
+ opacity: visible ? 1 : 0,
464
+ transition: 'opacity 0.6s ease 1.1s',
465
+ }}
466
+ >
467
+ <button
468
+ type="button"
469
+ onClick={fireConfetti}
470
+ className="flex cursor-pointer items-center gap-1.5 text-xs text-stone-600 transition-colors hover:text-stone-400"
471
+ >
472
+ <Sparkles className="h-3 w-3" />
473
+ {replayLabel}
474
+ </button>
475
+ </div>
476
+ )}
477
+ </div>
478
+
479
+ {showBrandLabel !== false && brandLabel && (
480
+ <div
481
+ className="absolute bottom-8 left-1/2 z-10 -translate-x-1/2 text-xs font-black uppercase tracking-[0.35em] text-stone-500"
482
+ style={{
483
+ opacity: visible ? 0.4 : 0,
484
+ transition: 'opacity 0.6s ease 1.2s',
485
+ }}
486
+ >
487
+ {brandLabel}
488
+ </div>
489
+ )}
490
+
491
+ <style>{`
492
+ @keyframes thanks-confetti-ping {
493
+ 75%, 100% {
494
+ transform: scale(2);
495
+ opacity: 0;
496
+ }
497
+ }
498
+ `}</style>
499
+ </section>
500
+ )
501
+ }