blocks-dusted 0.1.10 → 0.1.12
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.
- package/README.md +35 -0
- package/package.json +5 -2
- package/src/cli.js +39 -3
- package/src/commands/shop.js +59 -0
- package/src/commands/warehouse.js +73 -0
- package/src/utils/paths.js +13 -13
- package/templates/blocks/DD-Accordion/Component.tsx +229 -0
- package/templates/blocks/DD-Accordion/README.md +3 -0
- package/templates/blocks/DD-Accordion/config.ts +73 -0
- package/templates/blocks/DD-Accordion/manifest.json +75 -0
- package/templates/blocks/DD-AnimText/Component.tsx +788 -0
- package/templates/blocks/DD-AnimText/README.md +3 -0
- package/templates/blocks/DD-AnimText/config.ts +295 -0
- package/templates/blocks/DD-AnimText/manifest.json +85 -0
- package/templates/blocks/DD-AsideNav/Component.tsx +321 -0
- package/templates/blocks/DD-AsideNav/config.ts +204 -0
- package/templates/blocks/DD-AsideNav/manifest.json +80 -0
- package/templates/blocks/DD-Banner/Component.tsx +51 -0
- package/templates/blocks/DD-Banner/config.ts +57 -0
- package/templates/blocks/DD-Banner/manifest.json +72 -0
- package/templates/blocks/DD-BgLightning/Component.tsx +44 -0
- package/templates/blocks/DD-BgLightning/README.md +3 -0
- package/templates/blocks/DD-BgLightning/config.ts +97 -0
- package/templates/blocks/DD-BgLightning/manifest.json +79 -0
- package/templates/blocks/DD-BookCall/Component.tsx +535 -535
- package/templates/blocks/DD-BookCall/config.ts +238 -238
- package/templates/blocks/DD-ButtonPop/Component.tsx +117 -0
- package/templates/blocks/DD-ButtonPop/config.ts +105 -0
- package/templates/blocks/DD-ButtonPop/manifest.json +88 -0
- package/templates/blocks/DD-ButtonPop/svg-icons/Dust2.tsx +167 -0
- package/templates/blocks/DD-Card/Component.tsx +251 -0
- package/templates/blocks/DD-Card/README.md +4 -0
- package/templates/blocks/DD-Card/config.ts +154 -0
- package/templates/blocks/DD-Card/manifest.json +82 -0
- package/templates/blocks/DD-CardTemplate01/Component.tsx +140 -140
- package/templates/blocks/DD-CardTemplate01/config.ts +106 -106
- package/templates/blocks/DD-Carousel-A/Component.tsx +249 -249
- package/templates/blocks/DD-Carousel-B/Component.tsx +403 -403
- package/templates/blocks/DD-Chip/Component.tsx +250 -0
- package/templates/blocks/DD-Chip/EXAMPLES.md +313 -0
- package/templates/blocks/DD-Chip/README.md +202 -0
- package/templates/blocks/DD-Chip/config.ts +369 -0
- package/templates/blocks/DD-Chip/manifest.json +74 -0
- package/templates/blocks/DD-ComparisonTable/Component.tsx +256 -256
- package/templates/blocks/DD-Contact/Component.tsx +435 -434
- package/templates/blocks/DD-Contact/config.ts +22 -22
- package/templates/blocks/DD-Counter/Component.tsx +190 -0
- package/templates/blocks/DD-Counter/README.md +4 -0
- package/templates/blocks/DD-Counter/config.ts +291 -0
- package/templates/blocks/DD-Counter/manifest.json +74 -0
- package/templates/blocks/DD-CustomCSS/Component.tsx +8 -0
- package/templates/blocks/DD-CustomCSS/README.md +4 -0
- package/templates/blocks/DD-CustomCSS/addToLayout.md +5 -0
- package/templates/blocks/DD-CustomCSS/config.ts +22 -0
- package/templates/blocks/DD-CustomCSS/manifest.json +72 -0
- package/templates/blocks/DD-CustomCSS/renderCustomCSS.md +7 -0
- package/templates/blocks/DD-FeatCat/Component.tsx +302 -302
- package/templates/blocks/DD-FeatCat/config.ts +201 -201
- package/templates/blocks/DD-FeatStrip/Component.tsx +585 -585
- package/templates/blocks/DD-FeatStrip/config.ts +134 -134
- package/templates/blocks/DD-FluidBtn/ButtonsEyeCatching.tsx +628 -0
- package/templates/blocks/DD-FluidBtn/Component.tsx +43 -0
- package/templates/blocks/DD-FluidBtn/README.md +6 -0
- package/templates/blocks/DD-FluidBtn/config.ts +102 -0
- package/templates/blocks/DD-FluidBtn/manifest.json +86 -0
- package/templates/blocks/DD-GooeyInfo/Component.tsx +149 -0
- package/templates/blocks/DD-GooeyInfo/config.ts +135 -0
- package/templates/blocks/DD-GooeyInfo/manifest.json +72 -0
- package/templates/blocks/DD-Hero/Component.tsx +297 -297
- package/templates/blocks/DD-Hero/config.ts +267 -267
- package/templates/blocks/DD-HorizontalScroll/Component.tsx +936 -936
- package/templates/blocks/DD-HorizontalScroll/config.ts +231 -231
- package/templates/blocks/DD-Icon/Component.tsx +287 -0
- package/templates/blocks/DD-Icon/README.md +4 -0
- package/templates/blocks/DD-Icon/config.ts +258 -0
- package/templates/blocks/DD-Icon/hooks/revalidateBlockIds.ts +66 -0
- package/templates/blocks/DD-Icon/manifest.json +82 -0
- package/templates/blocks/DD-IconButton/Component.tsx +455 -0
- package/templates/blocks/DD-IconButton/README.md +326 -0
- package/templates/blocks/DD-IconButton/config.ts +239 -0
- package/templates/blocks/DD-IconButton/manifest.json +85 -0
- package/templates/blocks/DD-IdTag/Component.tsx +21 -0
- package/templates/blocks/DD-IdTag/README.md +4 -0
- package/templates/blocks/DD-IdTag/config.ts +41 -0
- package/templates/blocks/DD-IdTag/manifest.json +72 -0
- package/templates/blocks/DD-Iframe/Component.tsx +45 -0
- package/templates/blocks/DD-Iframe/README.md +4 -0
- package/templates/blocks/DD-Iframe/config.ts +47 -0
- package/templates/blocks/DD-Iframe/manifest.json +72 -0
- package/templates/blocks/DD-IframeMedia/Component.tsx +133 -0
- package/templates/blocks/DD-IframeMedia/README.md +4 -0
- package/templates/blocks/DD-IframeMedia/config.ts +79 -0
- package/templates/blocks/DD-IframeMedia/manifest.json +72 -0
- package/templates/blocks/DD-LineAnim/Component.tsx +36 -0
- package/templates/blocks/DD-LineAnim/README.md +4 -0
- package/templates/blocks/DD-LineAnim/config.ts +90 -0
- package/templates/blocks/DD-LineAnim/manifest.json +80 -0
- package/templates/blocks/DD-LinkGrid/Component.tsx +119 -0
- package/templates/blocks/DD-LinkGrid/README.md +4 -0
- package/templates/blocks/DD-LinkGrid/config.ts +51 -0
- package/templates/blocks/DD-LinkGrid/manifest.json +74 -0
- package/templates/blocks/DD-LogoMarq/Component.tsx +132 -0
- package/templates/blocks/DD-LogoMarq/README.md +4 -0
- package/templates/blocks/DD-LogoMarq/config.ts +264 -0
- package/templates/blocks/DD-LogoMarq/manifest.json +82 -0
- package/templates/blocks/DD-Lottie/Component.tsx +459 -0
- package/templates/blocks/DD-Lottie/README.md +4 -0
- package/templates/blocks/DD-Lottie/config.ts +379 -0
- package/templates/blocks/DD-Lottie/index.tsx +28 -0
- package/templates/blocks/DD-Lottie/manifest.json +83 -0
- package/templates/blocks/DD-MagicBento/Component.tsx +777 -0
- package/templates/blocks/DD-MagicBento/README.md +4 -0
- package/templates/blocks/DD-MagicBento/config.ts +264 -0
- package/templates/blocks/DD-MagicBento/manifest.json +74 -0
- package/templates/blocks/DD-MasonaryMedia/Component.tsx +545 -545
- package/templates/blocks/DD-MasonaryMedia/config.ts +2 -2
- package/templates/blocks/DD-MasonaryMedia/vendor.d.ts +2 -2
- package/templates/blocks/DD-Masonry/Component.tsx +645 -0
- package/templates/blocks/DD-Masonry/README.md +4 -0
- package/templates/blocks/DD-Masonry/config.ts +475 -0
- package/templates/blocks/DD-Masonry/manifest.json +82 -0
- package/templates/blocks/DD-Pricing/Component.tsx +2 -5
- package/templates/blocks/DD-Process/Component.tsx +2 -4
- package/templates/blocks/DD-Process/config.ts +150 -150
- package/templates/blocks/DD-Progress/Component.tsx +156 -0
- package/templates/blocks/DD-Progress/README.md +4 -0
- package/templates/blocks/DD-Progress/config.ts +175 -0
- package/templates/blocks/DD-Progress/manifest.json +74 -0
- package/templates/blocks/DD-ProjHeader/Component.tsx +91 -0
- package/templates/blocks/DD-ProjHeader/README.md +4 -0
- package/templates/blocks/DD-ProjHeader/config.ts +96 -0
- package/templates/blocks/DD-ProjHeader/manifest.json +72 -0
- package/templates/blocks/DD-ProjSlider/Component.tsx +123 -0
- package/templates/blocks/DD-ProjSlider/README.md +4 -0
- package/templates/blocks/DD-ProjSlider/config.ts +64 -0
- package/templates/blocks/DD-ProjSlider/manifest.json +72 -0
- package/templates/blocks/DD-ScrollText/Component.tsx +114 -0
- package/templates/blocks/DD-ScrollText/README.md +4 -0
- package/templates/blocks/DD-ScrollText/config.ts +58 -0
- package/templates/blocks/DD-ScrollText/manifest.json +72 -0
- package/templates/blocks/DD-Section/Component.tsx +266 -266
- package/templates/blocks/DD-Separator/Component.tsx +66 -0
- package/templates/blocks/DD-Separator/README.md +4 -0
- package/templates/blocks/DD-Separator/config.ts +58 -0
- package/templates/blocks/DD-Separator/manifest.json +72 -0
- package/templates/blocks/DD-Services/Component.tsx +176 -176
- package/templates/blocks/DD-Services/config.ts +149 -149
- package/templates/blocks/DD-ShowcaseGrid/Component.tsx +307 -307
- package/templates/blocks/DD-Slider-A/Component.tsx +1 -1
- package/templates/blocks/DD-StackCards/Component.tsx +1 -1
- package/templates/blocks/DD-StackCards/config.ts +399 -399
- package/templates/blocks/DD-Team/Component.tsx +247 -247
- package/templates/blocks/DD-Team/config.ts +173 -173
- package/templates/blocks/DD-TeamCards/Component.tsx +251 -0
- package/templates/blocks/DD-TeamCards/README.md +4 -0
- package/templates/blocks/DD-TeamCards/config.ts +302 -0
- package/templates/blocks/DD-TeamCards/manifest.json +74 -0
- package/templates/blocks/DD-TechStack/Component.tsx +174 -174
- package/templates/blocks/DD-TechStack/config.ts +153 -153
- package/templates/blocks/DD-TestMarq/Component.tsx +132 -0
- package/templates/blocks/DD-TestMarq/README.md +4 -0
- package/templates/blocks/DD-TestMarq/RowLabel.tsx +9 -0
- package/templates/blocks/DD-TestMarq/config.ts +88 -0
- package/templates/blocks/DD-TestMarq/manifest.json +72 -0
- package/templates/blocks/DD-Testim/Component.tsx +247 -0
- package/templates/blocks/DD-Testim/README.md +4 -0
- package/templates/blocks/DD-Testim/config.ts +101 -0
- package/templates/blocks/DD-Testim/manifest.json +75 -0
- package/templates/blocks/DD-Testim2/Component.tsx +256 -0
- package/templates/blocks/DD-Testim2/README.md +4 -0
- package/templates/blocks/DD-Testim2/config.ts +100 -0
- package/templates/blocks/DD-Testim2/manifest.json +74 -0
- package/templates/blocks/DD-Testimonials/Component.tsx +139 -147
- package/templates/blocks/DD-Testimonials/config.ts +120 -120
- package/templates/blocks/DD-TextMarq/Component.tsx +157 -0
- package/templates/blocks/DD-TextMarq/README.md +4 -0
- package/templates/blocks/DD-TextMarq/config.ts +237 -0
- package/templates/blocks/DD-TextMarq/manifest.json +82 -0
- package/templates/blocks/DD-Thanks/Component.tsx +501 -0
- package/templates/blocks/DD-Thanks/README.md +4 -0
- package/templates/blocks/DD-Thanks/config.ts +224 -0
- package/templates/blocks/DD-Thanks/manifest.json +72 -0
- package/templates/blocks/DD-VertMarq/Component.tsx +154 -0
- package/templates/blocks/DD-VertMarq/README.md +4 -0
- package/templates/blocks/DD-VertMarq/config.ts +236 -0
- package/templates/blocks/DD-VertMarq/dist/index.css +6 -0
- package/templates/blocks/DD-VertMarq/dist/index.min.css +1 -0
- package/templates/blocks/DD-VertMarq/manifest.json +74 -0
- package/templates/blocks/DD-Work/Component.tsx +318 -315
- package/templates/blocks/DD-Work/config.ts +165 -165
- package/templates/blocks/DD-Work-B/Component.tsx +294 -294
- package/templates/blocks/DDHG-CTASection2/Component.tsx +185 -0
- package/templates/blocks/DDHG-CTASection2/config.ts +140 -0
- package/templates/blocks/DDHG-CTASection2/manifest.json +45 -0
- package/templates/blocks/DDHG-HighlightArc/Component.tsx +243 -0
- package/templates/blocks/DDHG-HighlightArc/config.ts +85 -0
- package/templates/blocks/DDHG-HighlightArc/manifest.json +46 -0
- package/templates/blocks/DDHG-LogoMarquee/Component.tsx +143 -0
- package/templates/blocks/DDHG-LogoMarquee/config.ts +154 -0
- package/templates/blocks/DDHG-LogoMarquee/manifest.json +39 -0
- package/templates/blocks/DDHG-NavCodeGrid/Component.tsx +227 -0
- package/templates/blocks/DDHG-NavCodeGrid/config.ts +64 -0
- package/templates/blocks/DDHG-NavCodeGrid/manifest.json +45 -0
- package/templates/blocks/DDHG-ProcessSec/Component.tsx +574 -0
- package/templates/blocks/DDHG-ProcessSec/config.ts +170 -0
- package/templates/blocks/DDHG-ProcessSec/manifest.json +56 -0
- package/templates/blocks/DDHG-ServiceDetails/Component.tsx +164 -0
- package/templates/blocks/DDHG-ServiceDetails/ServiceDetailCard.tsx +390 -0
- package/templates/blocks/DDHG-ServiceDetails/config.ts +77 -0
- package/templates/blocks/DDHG-ServiceDetails/manifest.json +50 -0
- package/templates/blocks/DDHG-ServiceHero/Component.tsx +234 -0
- package/templates/blocks/DDHG-ServiceHero/config.ts +49 -0
- package/templates/blocks/DDHG-ServiceHero/manifest.json +26 -0
- package/templates/blocks/DDHG-ServiceOverview/Component.tsx +277 -0
- package/templates/blocks/DDHG-ServiceOverview/config.ts +64 -0
- package/templates/blocks/DDHG-ServiceOverview/manifest.json +44 -0
- package/templates/blocks/DDHG-StatsFAQ/Component.tsx +389 -0
- package/templates/blocks/DDHG-StatsFAQ/config.ts +204 -0
- package/templates/blocks/DDHG-StatsFAQ/manifest.json +45 -0
- package/templates/blocks/DDHG-TeamCards/Component.tsx +169 -0
- package/templates/blocks/DDHG-TeamCards/config.ts +48 -0
- package/templates/blocks/DDHG-TeamCards/manifest.json +29 -0
- package/templates/shared/src/components/BgLightning/index.tsx +242 -0
- package/templates/shared/src/components/ui/ShareButton/index.tsx +80 -0
- package/templates/shared/src/components/ui/anims/FadeOnScroll.tsx +48 -0
- package/templates/shared/src/components/ui/anims/FadeUpOnInView.tsx +63 -0
- package/templates/shared/src/components/ui/anims/GlareHover.tsx +111 -0
- package/templates/shared/src/components/ui/anims/LineAnims.tsx +81 -0
- package/templates/shared/src/components/ui/anims/TitleAnims01.tsx +54 -0
- package/templates/shared/src/components/ui/anims/animation-container.tsx +29 -0
- package/templates/shared/src/components/ui/anims/blur-in.tsx +26 -0
- package/templates/shared/src/components/ui/anims/counter.tsx +53 -0
- package/templates/shared/src/components/ui/anims/fade.tsx +83 -0
- package/templates/shared/src/components/ui/anims/gradual-spacing.tsx +39 -0
- package/templates/shared/src/components/ui/anims/pull-up.tsx +93 -0
- package/templates/shared/src/components/ui/anims/reveal-text.tsx +76 -0
- package/templates/shared/src/components/ui/anims/rotate-words.tsx +43 -0
- package/templates/shared/src/components/ui/anims/shiny-text.tsx +58 -0
- package/templates/shared/src/components/ui/anims/shuffle-text.tsx +85 -0
- package/templates/shared/src/components/ui/anims/split-text.tsx +211 -0
- package/templates/shared/src/components/ui/anims/staggered-fade.tsx +133 -0
- package/templates/shared/src/components/ui/anims/text-scroll.tsx +111 -0
- package/templates/shared/src/components/ui/anims/typing-effect.tsx +112 -0
- package/templates/shared/src/components/ui/button-dusted-fluid/index.tsx +1521 -0
- package/templates/shared/src/components/ui/separator.tsx +85 -0
- package/templates/shared/src/hooks/useBodyScrollLock.ts +97 -0
- package/templates/shared/src/hooks/useClickOutside.ts +28 -0
- package/templates/shared/src/utilities/renderCustomCSS.ts +5 -0
- package/templates/shared/src/utilities/smoothScroll.ts +65 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef } from 'react'
|
|
4
|
+
import Image from 'next/image'
|
|
5
|
+
import Link from 'next/link'
|
|
6
|
+
import { Globe, Briefcase, Camera } from 'lucide-react'
|
|
7
|
+
import { cn } from '@/utilities/ui'
|
|
8
|
+
import { getMediaUrl } from '@/utilities/getMediaUrl'
|
|
9
|
+
|
|
10
|
+
type MediaDoc = {
|
|
11
|
+
url?: string | null
|
|
12
|
+
alt?: string | null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type TeamMember = {
|
|
16
|
+
name?: string | null
|
|
17
|
+
role?: string | null
|
|
18
|
+
bio?: string | null
|
|
19
|
+
image?: number | MediaDoc | null
|
|
20
|
+
priority?: boolean | null
|
|
21
|
+
facebookUrl?: string | null
|
|
22
|
+
linkedinUrl?: string | null
|
|
23
|
+
instagramUrl?: string | null
|
|
24
|
+
id?: string | null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type DDHGTeamCardsProps = {
|
|
28
|
+
sectionId?: string | null
|
|
29
|
+
customClassName?: string | null
|
|
30
|
+
customCSS?: string | null
|
|
31
|
+
members?: TeamMember[] | null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getMediaDoc(img?: number | MediaDoc | null): MediaDoc | null {
|
|
35
|
+
if (!img || typeof img === 'number') return null
|
|
36
|
+
return img
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const DDHGTeamCards: React.FC<DDHGTeamCardsProps> = ({
|
|
40
|
+
sectionId,
|
|
41
|
+
customClassName,
|
|
42
|
+
customCSS,
|
|
43
|
+
members,
|
|
44
|
+
}) => {
|
|
45
|
+
const ref = useRef<HTMLDivElement>(null)
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const observer = new IntersectionObserver(
|
|
49
|
+
(entries) => {
|
|
50
|
+
entries.forEach((entry) => {
|
|
51
|
+
if (entry.isIntersecting) {
|
|
52
|
+
entry.target.querySelectorAll<HTMLElement>('.reveal').forEach((el, i) => {
|
|
53
|
+
setTimeout(() => {
|
|
54
|
+
el.style.opacity = '1'
|
|
55
|
+
el.style.transform = 'translateY(0)'
|
|
56
|
+
}, i * 100)
|
|
57
|
+
})
|
|
58
|
+
observer.unobserve(entry.target)
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
},
|
|
62
|
+
{ threshold: 0.1 },
|
|
63
|
+
)
|
|
64
|
+
if (ref.current) observer.observe(ref.current)
|
|
65
|
+
return () => observer.disconnect()
|
|
66
|
+
}, [])
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div id={sectionId ?? undefined} className={cn('px-5 md:px-8 py-8', customClassName)} ref={ref}>
|
|
70
|
+
{customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
|
|
71
|
+
<div className="max-w-7xl mx-auto">
|
|
72
|
+
{members && members.length > 0 && (
|
|
73
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 md:gap-6">
|
|
74
|
+
{members.map((member, i) => {
|
|
75
|
+
const imageDoc = getMediaDoc(member.image)
|
|
76
|
+
const imageUrl = getMediaUrl(imageDoc?.url)
|
|
77
|
+
const socials = [
|
|
78
|
+
{ Icon: Globe, url: member.facebookUrl, label: 'Facebook' },
|
|
79
|
+
{ Icon: Briefcase, url: member.linkedinUrl, label: 'LinkedIn' },
|
|
80
|
+
{ Icon: Camera, url: member.instagramUrl, label: 'Instagram' },
|
|
81
|
+
].filter((s) => !!s.url)
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div
|
|
85
|
+
key={member.id != null ? `id-${member.id}` : `idx-${i}`}
|
|
86
|
+
className="reveal group relative overflow-hidden cursor-pointer rounded-md squircle border-none border-stone-300/0 bg-stone-100/50 transition-all duration-300 hover:shadow-2xl dark:border-stone-700/0 dark:bg-stone-900/50 h-112 md:h-128"
|
|
87
|
+
style={{
|
|
88
|
+
opacity: 0,
|
|
89
|
+
transform: 'translateY(20px)',
|
|
90
|
+
transition: `opacity 0.9s ease ${i * 0.1}s, transform 0.9s ease ${i * 0.1}s`,
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
{imageDoc && imageUrl && (
|
|
94
|
+
<div className="relative w-full h-full overflow-hidden brightness-100 group-hover:brightness-50 transition-all duration-300">
|
|
95
|
+
<Image
|
|
96
|
+
src={imageUrl}
|
|
97
|
+
alt={imageDoc.alt ?? member.name ?? ''}
|
|
98
|
+
fill
|
|
99
|
+
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
|
100
|
+
priority={Boolean(member.priority)}
|
|
101
|
+
className="team-member object-cover object-top brightness-100 group-hover:brightness-50 group-hover:scale-110 transition-transform duration-500 ease-out"
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
)}
|
|
105
|
+
<div
|
|
106
|
+
className="pattern-overlay absolute inset-0 opacity-[0.03] pointer-events-none"
|
|
107
|
+
style={{
|
|
108
|
+
backgroundImage:
|
|
109
|
+
'repeating-linear-gradient(90deg, #000 0px, #000 1px, transparent 1px, transparent 20px)',
|
|
110
|
+
}}
|
|
111
|
+
/>
|
|
112
|
+
<div className="gradient-overlay absolute inset-0 bg-linear-to-t from-black/80 via-black/80 to-transparent group-hover:from-black/90 transition-all duration-300 group-hover:backdrop-brightness-50" />
|
|
113
|
+
<div className="content-card absolute inset-0 flex flex-col justify-end p-3 text-white">
|
|
114
|
+
<div className="z-10 text-center mb-3">
|
|
115
|
+
{member.name && (
|
|
116
|
+
<h3
|
|
117
|
+
className="text-xl md:text-2xl font-bold mb-1 text-white drop-shadow-lg"
|
|
118
|
+
style={{ fontFamily: 'Outfit, sans-serif', fontWeight: 700 }}
|
|
119
|
+
>
|
|
120
|
+
{member.name}
|
|
121
|
+
</h3>
|
|
122
|
+
)}
|
|
123
|
+
{member.role && (
|
|
124
|
+
<p
|
|
125
|
+
className="text-xs md:text-sm font-medium uppercase tracking-wider text-stone-200 dark:text-stone-200 drop-shadow-md"
|
|
126
|
+
style={{ fontFamily: "'Inter', sans-serif", fontWeight: 500 }}
|
|
127
|
+
>
|
|
128
|
+
{member.role}
|
|
129
|
+
</p>
|
|
130
|
+
)}
|
|
131
|
+
</div>
|
|
132
|
+
<div
|
|
133
|
+
className="hover-panel transform translate-y-full group-hover:translate-y-0 transition-transform duration-300 ease-out p-4"
|
|
134
|
+
style={{ background: 'rgba(0,0,0,0.4)', backdropFilter: 'blur(8px)' }}
|
|
135
|
+
>
|
|
136
|
+
{member.bio && (
|
|
137
|
+
<p className="text-sm text-white/70 leading-relaxed mb-4 opacity-0 group-hover:opacity-100 transition-all duration-300 delay-100">
|
|
138
|
+
{member.bio}
|
|
139
|
+
</p>
|
|
140
|
+
)}
|
|
141
|
+
{socials.length > 0 && (
|
|
142
|
+
<div className="socials-map flex gap-3 justify-center opacity-0 group-hover:opacity-100 transition-all duration-300 delay-200">
|
|
143
|
+
{socials.map(({ Icon, url, label }) => (
|
|
144
|
+
<Link
|
|
145
|
+
key={label}
|
|
146
|
+
href={url!}
|
|
147
|
+
target="_blank"
|
|
148
|
+
rel="noopener noreferrer"
|
|
149
|
+
aria-label={`${member.name} on ${label}`}
|
|
150
|
+
className="text-white/70 hover:text-white hover:scale-110 transition-all duration-200 p-2 rounded-full bg-white/10 hover:bg-white/20 backdrop-blur-sm"
|
|
151
|
+
>
|
|
152
|
+
<Icon size={14} />
|
|
153
|
+
</Link>
|
|
154
|
+
))}
|
|
155
|
+
</div>
|
|
156
|
+
)}
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
)
|
|
161
|
+
})}
|
|
162
|
+
</div>
|
|
163
|
+
)}
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export default DDHGTeamCards
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Block } from 'payload'
|
|
2
|
+
|
|
3
|
+
export const DDHGTeamCards: Block = {
|
|
4
|
+
slug: 'DDHG-TeamCards',
|
|
5
|
+
interfaceName: 'DDHGTeamCards',
|
|
6
|
+
labels: { singular: 'Team Cards', plural: 'Team Cards' },
|
|
7
|
+
fields: [
|
|
8
|
+
{
|
|
9
|
+
type: 'tabs',
|
|
10
|
+
tabs: [
|
|
11
|
+
{
|
|
12
|
+
label: 'Team Members',
|
|
13
|
+
fields: [
|
|
14
|
+
{
|
|
15
|
+
name: 'members',
|
|
16
|
+
type: 'array',
|
|
17
|
+
label: 'Team Members',
|
|
18
|
+
fields: [
|
|
19
|
+
{ type: 'row', fields: [
|
|
20
|
+
{ name: 'name', type: 'text', label: 'Name', required: true, admin: { width: '50%' } },
|
|
21
|
+
{ name: 'role', type: 'text', label: 'Role / Title', admin: { width: '50%' } },
|
|
22
|
+
] },
|
|
23
|
+
{ name: 'bio', type: 'textarea', label: 'Bio (shown on hover)' },
|
|
24
|
+
{ name: 'image', type: 'upload', relationTo: 'media', label: 'Photo' },
|
|
25
|
+
{ name: 'priority', type: 'checkbox', label: 'High Priority', defaultValue: false, admin: { description: 'Enable for above-the-fold media.' } },
|
|
26
|
+
{ type: 'row', fields: [
|
|
27
|
+
{ name: 'linkedinUrl', type: 'text', label: 'LinkedIn URL', admin: { width: '33%' } },
|
|
28
|
+
{ name: 'instagramUrl', type: 'text', label: 'Instagram URL', admin: { width: '33%' } },
|
|
29
|
+
{ name: 'facebookUrl', type: 'text', label: 'Facebook URL', admin: { width: '33%' } },
|
|
30
|
+
] },
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
label: 'Advanced',
|
|
37
|
+
fields: [
|
|
38
|
+
{ type: 'row', fields: [
|
|
39
|
+
{ name: 'sectionId', type: 'text', label: 'Section ID', admin: { width: '50%', description: 'Optional anchor ID' } },
|
|
40
|
+
{ name: 'customClassName', type: 'text', label: 'Custom CSS Classes', admin: { width: '50%' } },
|
|
41
|
+
] },
|
|
42
|
+
{ name: 'customCSS', type: 'code', label: 'Custom CSS', admin: { language: 'css', description: 'Injected as a <style> tag. Use with caution.' } },
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "block",
|
|
3
|
+
"name": "DDHG-TeamCards",
|
|
4
|
+
"label": "Team Cards",
|
|
5
|
+
"sourceName": "TeamCards",
|
|
6
|
+
"portableName": "DDHG-TeamCards",
|
|
7
|
+
"slug": "DDHG-TeamCards",
|
|
8
|
+
"version": "0.0.1",
|
|
9
|
+
"source": { "repo": "Hadizainal/HeatherGrey2025", "originalPath": "src/blocks/TeamCards", "originalSlug": "teamCards", "originalConfigExport": "TeamCards", "originalComponentExport": "TeamCardsBlock", "originalInterfaceName": "TeamCardsBlockProps" },
|
|
10
|
+
"files": [
|
|
11
|
+
{ "from": "Component.tsx", "to": "src/blocks/DDHG-TeamCards/Component.tsx" },
|
|
12
|
+
{ "from": "config.ts", "to": "src/blocks/DDHG-TeamCards/config.ts" }
|
|
13
|
+
],
|
|
14
|
+
"dependencies": [{ "name": "lucide-react" }],
|
|
15
|
+
"sharedFiles": [],
|
|
16
|
+
"requiredProjectFiles": [
|
|
17
|
+
{ "path": "src/utilities/ui.ts", "reason": "Component uses the cn utility." },
|
|
18
|
+
{ "path": "src/utilities/getMediaUrl.ts", "reason": "Component resolves media URLs through getMediaUrl." }
|
|
19
|
+
],
|
|
20
|
+
"optionalProjectFiles": [],
|
|
21
|
+
"collectionRegistration": { "enabled": true, "collections": ["Pages", "Posts"] },
|
|
22
|
+
"renderBlocksRegistration": { "enabled": true },
|
|
23
|
+
"renderBlocksBehavior": { "wrapperless": false },
|
|
24
|
+
"pageRouteIntegration": { "required": false, "manualSteps": [] },
|
|
25
|
+
"manualSteps": ["Run payload generate:types after schema changes."],
|
|
26
|
+
"notes": ["Converted from HeatherGrey2025 TeamCards.", "Generated Payload media type dependency replaced with a local structural type.", "Priority loading now follows each member row's priority field.", "Installer must not overwrite existing target files."],
|
|
27
|
+
"exportName": "DDHGTeamCards",
|
|
28
|
+
"interfaceName": "DDHGTeamCards"
|
|
29
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { useRef, useEffect } from 'react'
|
|
4
|
+
|
|
5
|
+
interface BgLightningProps {
|
|
6
|
+
hue?: number
|
|
7
|
+
xOffset?: number
|
|
8
|
+
speed?: number
|
|
9
|
+
intensity?: number
|
|
10
|
+
size?: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const BgLightning: React.FC<BgLightningProps> = ({
|
|
14
|
+
hue = 230,
|
|
15
|
+
xOffset = 0,
|
|
16
|
+
speed = 1,
|
|
17
|
+
intensity = 1,
|
|
18
|
+
size = 1
|
|
19
|
+
}) => {
|
|
20
|
+
const canvasRef = useRef<HTMLCanvasElement>(null)
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const canvas = canvasRef.current
|
|
24
|
+
if (!canvas) return
|
|
25
|
+
|
|
26
|
+
let frameId = 0
|
|
27
|
+
let disposed = false
|
|
28
|
+
|
|
29
|
+
const gl = canvas.getContext('webgl', {
|
|
30
|
+
alpha: true,
|
|
31
|
+
premultipliedAlpha: false
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
if (!gl) {
|
|
35
|
+
console.error('WebGL not supported')
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const resizeCanvas = () => {
|
|
40
|
+
const width = canvas.clientWidth
|
|
41
|
+
const height = canvas.clientHeight
|
|
42
|
+
|
|
43
|
+
if (canvas.width !== width || canvas.height !== height) {
|
|
44
|
+
canvas.width = width
|
|
45
|
+
canvas.height = height
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
resizeCanvas()
|
|
50
|
+
window.addEventListener('resize', resizeCanvas)
|
|
51
|
+
|
|
52
|
+
const vertexShaderSource = `
|
|
53
|
+
attribute vec2 aPosition;
|
|
54
|
+
|
|
55
|
+
void main() {
|
|
56
|
+
gl_Position = vec4(aPosition, 0.0, 1.0);
|
|
57
|
+
}
|
|
58
|
+
`
|
|
59
|
+
|
|
60
|
+
const fragmentShaderSource = `
|
|
61
|
+
precision mediump float;
|
|
62
|
+
|
|
63
|
+
uniform vec2 iResolution;
|
|
64
|
+
uniform float iTime;
|
|
65
|
+
uniform float uHue;
|
|
66
|
+
uniform float uXOffset;
|
|
67
|
+
uniform float uSpeed;
|
|
68
|
+
uniform float uIntensity;
|
|
69
|
+
uniform float uSize;
|
|
70
|
+
|
|
71
|
+
#define OCTAVE_COUNT 10
|
|
72
|
+
|
|
73
|
+
vec3 hsv2rgb(vec3 c) {
|
|
74
|
+
vec3 rgb = clamp(abs(mod(c.x * 6.0 + vec3(0.0,4.0,2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);
|
|
75
|
+
return c.z * mix(vec3(1.0), rgb, c.y);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
float hash11(float p) {
|
|
79
|
+
p = fract(p * .1031);
|
|
80
|
+
p *= p + 33.33;
|
|
81
|
+
p *= p + p;
|
|
82
|
+
return fract(p);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
float hash12(vec2 p) {
|
|
86
|
+
vec3 p3 = fract(vec3(p.xyx) * .1031);
|
|
87
|
+
p3 += dot(p3, p3.yzx + 33.33);
|
|
88
|
+
return fract((p3.x + p3.y) * p3.z);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
mat2 rotate2d(float theta) {
|
|
92
|
+
float c = cos(theta);
|
|
93
|
+
float s = sin(theta);
|
|
94
|
+
return mat2(c, -s, s, c);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
float noise(vec2 p) {
|
|
98
|
+
vec2 ip = floor(p);
|
|
99
|
+
vec2 fp = fract(p);
|
|
100
|
+
|
|
101
|
+
float a = hash12(ip);
|
|
102
|
+
float b = hash12(ip + vec2(1.0, 0.0));
|
|
103
|
+
float c = hash12(ip + vec2(0.0, 1.0));
|
|
104
|
+
float d = hash12(ip + vec2(1.0, 1.0));
|
|
105
|
+
|
|
106
|
+
vec2 t = smoothstep(0.0, 1.0, fp);
|
|
107
|
+
|
|
108
|
+
return mix(mix(a, b, t.x), mix(c, d, t.x), t.y);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
float fbm(vec2 p) {
|
|
112
|
+
float value = 0.0;
|
|
113
|
+
float amplitude = 0.5;
|
|
114
|
+
|
|
115
|
+
for (int i = 0; i < OCTAVE_COUNT; ++i) {
|
|
116
|
+
value += amplitude * noise(p);
|
|
117
|
+
p *= rotate2d(0.45);
|
|
118
|
+
p *= 2.0;
|
|
119
|
+
amplitude *= 0.5;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
126
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
127
|
+
uv = 2.0 * uv - 1.0;
|
|
128
|
+
uv.x *= iResolution.x / iResolution.y;
|
|
129
|
+
uv.x += uXOffset;
|
|
130
|
+
|
|
131
|
+
uv += 2.0 * fbm(uv * uSize + 0.8 * iTime * uSpeed) - 1.0;
|
|
132
|
+
|
|
133
|
+
float dist = max(abs(uv.x), 0.001);
|
|
134
|
+
vec3 baseColor = hsv2rgb(vec3(uHue / 360.0, 0.7, 0.8));
|
|
135
|
+
vec3 col = baseColor * pow(mix(0.0, 0.07, hash11(iTime * uSpeed)) / dist, 1.0) * uIntensity;
|
|
136
|
+
|
|
137
|
+
float a = clamp(max(col.r, max(col.g, col.b)), 0.0, 1.0);
|
|
138
|
+
fragColor = vec4(col, a);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
void main() {
|
|
142
|
+
mainImage(gl_FragColor, gl_FragCoord.xy);
|
|
143
|
+
}
|
|
144
|
+
`
|
|
145
|
+
|
|
146
|
+
const compileShader = (source: string, type: number) => {
|
|
147
|
+
const shader = gl.createShader(type)
|
|
148
|
+
if (!shader) return null
|
|
149
|
+
|
|
150
|
+
gl.shaderSource(shader, source)
|
|
151
|
+
gl.compileShader(shader)
|
|
152
|
+
|
|
153
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
154
|
+
console.error('Shader compile error:', gl.getShaderInfoLog(shader))
|
|
155
|
+
gl.deleteShader(shader)
|
|
156
|
+
return null
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return shader
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const vertexShader = compileShader(vertexShaderSource, gl.VERTEX_SHADER)
|
|
163
|
+
const fragmentShader = compileShader(fragmentShaderSource, gl.FRAGMENT_SHADER)
|
|
164
|
+
|
|
165
|
+
if (!vertexShader || !fragmentShader) return
|
|
166
|
+
|
|
167
|
+
const program = gl.createProgram()
|
|
168
|
+
if (!program) return
|
|
169
|
+
|
|
170
|
+
gl.attachShader(program, vertexShader)
|
|
171
|
+
gl.attachShader(program, fragmentShader)
|
|
172
|
+
gl.linkProgram(program)
|
|
173
|
+
|
|
174
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
175
|
+
console.error('Program linking error:', gl.getProgramInfoLog(program))
|
|
176
|
+
gl.deleteProgram(program)
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const vertices = new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1])
|
|
181
|
+
|
|
182
|
+
const vertexBuffer = gl.createBuffer()
|
|
183
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
|
|
184
|
+
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
|
|
185
|
+
|
|
186
|
+
const aPosition = gl.getAttribLocation(program, 'aPosition')
|
|
187
|
+
|
|
188
|
+
if (aPosition >= 0) {
|
|
189
|
+
gl.useProgram(program)
|
|
190
|
+
gl.enableVertexAttribArray(aPosition)
|
|
191
|
+
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const iResolutionLocation = gl.getUniformLocation(program, 'iResolution')
|
|
195
|
+
const iTimeLocation = gl.getUniformLocation(program, 'iTime')
|
|
196
|
+
const uHueLocation = gl.getUniformLocation(program, 'uHue')
|
|
197
|
+
const uXOffsetLocation = gl.getUniformLocation(program, 'uXOffset')
|
|
198
|
+
const uSpeedLocation = gl.getUniformLocation(program, 'uSpeed')
|
|
199
|
+
const uIntensityLocation = gl.getUniformLocation(program, 'uIntensity')
|
|
200
|
+
const uSizeLocation = gl.getUniformLocation(program, 'uSize')
|
|
201
|
+
|
|
202
|
+
const startTime = performance.now()
|
|
203
|
+
|
|
204
|
+
const render = () => {
|
|
205
|
+
if (disposed) return
|
|
206
|
+
|
|
207
|
+
resizeCanvas()
|
|
208
|
+
|
|
209
|
+
gl.useProgram(program)
|
|
210
|
+
gl.viewport(0, 0, canvas.width, canvas.height)
|
|
211
|
+
|
|
212
|
+
if (iResolutionLocation) gl.uniform2f(iResolutionLocation, canvas.width, canvas.height)
|
|
213
|
+
if (iTimeLocation) gl.uniform1f(iTimeLocation, (performance.now() - startTime) / 1000)
|
|
214
|
+
if (uHueLocation) gl.uniform1f(uHueLocation, hue)
|
|
215
|
+
if (uXOffsetLocation) gl.uniform1f(uXOffsetLocation, xOffset)
|
|
216
|
+
if (uSpeedLocation) gl.uniform1f(uSpeedLocation, speed)
|
|
217
|
+
if (uIntensityLocation) gl.uniform1f(uIntensityLocation, intensity)
|
|
218
|
+
if (uSizeLocation) gl.uniform1f(uSizeLocation, size)
|
|
219
|
+
|
|
220
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6)
|
|
221
|
+
|
|
222
|
+
frameId = requestAnimationFrame(render)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
frameId = requestAnimationFrame(render)
|
|
226
|
+
|
|
227
|
+
return () => {
|
|
228
|
+
disposed = true
|
|
229
|
+
cancelAnimationFrame(frameId)
|
|
230
|
+
window.removeEventListener('resize', resizeCanvas)
|
|
231
|
+
|
|
232
|
+
gl.deleteBuffer(vertexBuffer)
|
|
233
|
+
gl.deleteShader(vertexShader)
|
|
234
|
+
gl.deleteShader(fragmentShader)
|
|
235
|
+
gl.deleteProgram(program)
|
|
236
|
+
}
|
|
237
|
+
}, [hue, xOffset, speed, intensity, size])
|
|
238
|
+
|
|
239
|
+
return <canvas ref={canvasRef} className="relative h-full w-full" />
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export default BgLightning
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { useState } from 'react'
|
|
4
|
+
import { LucideIcon } from 'lucide-react'
|
|
5
|
+
|
|
6
|
+
import { cn } from '@/utilities/ui'
|
|
7
|
+
import { Button } from '@/components/ui/button'
|
|
8
|
+
|
|
9
|
+
interface ShareLink {
|
|
10
|
+
icon: LucideIcon
|
|
11
|
+
href?: string
|
|
12
|
+
onClick?: () => void
|
|
13
|
+
label?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface ShareButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
17
|
+
links: ShareLink[]
|
|
18
|
+
children: React.ReactNode
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const ShareButton = ({ className, links, children, ...props }: ShareButtonProps) => {
|
|
22
|
+
const [isHovered, setIsHovered] = useState(false)
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div
|
|
26
|
+
className="relative"
|
|
27
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
28
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
29
|
+
>
|
|
30
|
+
<Button
|
|
31
|
+
className={cn(
|
|
32
|
+
'relative min-w-40 rounded-3xl ',
|
|
33
|
+
'bg-white dark:bg-black',
|
|
34
|
+
'hover:bg-gray-50 dark:hover:bg-gray-950',
|
|
35
|
+
'text-stone-200 dark:text-stone-200',
|
|
36
|
+
'border border-black/10 dark:border-white/10',
|
|
37
|
+
'transition-all duration-300',
|
|
38
|
+
isHovered ? 'opacity-0' : 'opacity-100',
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
<span className="flex items-center gap-2">{children}</span>
|
|
44
|
+
</Button>
|
|
45
|
+
|
|
46
|
+
<div className="absolute left-0 top-0 flex h-10">
|
|
47
|
+
{links.map((link, index) => {
|
|
48
|
+
const Icon = link.icon
|
|
49
|
+
return (
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
key={index}
|
|
53
|
+
onClick={link.onClick}
|
|
54
|
+
className={cn(
|
|
55
|
+
'h-10',
|
|
56
|
+
'w-10',
|
|
57
|
+
'flex items-center justify-center',
|
|
58
|
+
'bg-black dark:bg-white',
|
|
59
|
+
'text-white dark:text-black',
|
|
60
|
+
'transition-all duration-300',
|
|
61
|
+
index === 0 && 'rounded-l-3xl',
|
|
62
|
+
index === links.length - 1 && 'rounded-r-3xl',
|
|
63
|
+
'border-r border-white/10 last:border-r-0 dark:border-black/10',
|
|
64
|
+
'hover:bg-gray-900 dark:hover:bg-gray-100',
|
|
65
|
+
'',
|
|
66
|
+
isHovered ? 'translate-x-0 opacity-100' : '-translate-x-full opacity-0',
|
|
67
|
+
index === 0 && 'transition-all duration-200',
|
|
68
|
+
index === 1 && 'delay-\\[50ms\\] transition-all duration-200',
|
|
69
|
+
index === 2 && 'transition-all delay-100 duration-200',
|
|
70
|
+
index === 3 && 'transition-all delay-150 duration-200'
|
|
71
|
+
)}
|
|
72
|
+
>
|
|
73
|
+
<Icon className="size-4" />
|
|
74
|
+
</button>
|
|
75
|
+
)
|
|
76
|
+
})}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { useEffect, useRef, useState } from 'react'
|
|
4
|
+
import { motion, useScroll, useTransform } from 'framer-motion'
|
|
5
|
+
|
|
6
|
+
interface FadeOnScrollProps {
|
|
7
|
+
children: React.ReactNode
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const FadeOnScroll: React.FC<FadeOnScrollProps> = ({ children }) => {
|
|
11
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
12
|
+
const [targetRef, setTargetRef] = useState<HTMLElement | null>(null)
|
|
13
|
+
|
|
14
|
+
// Bind scroll tracking only when ready
|
|
15
|
+
const scrollSetupRef = useRef(false)
|
|
16
|
+
const { scrollYProgress } = useScroll({
|
|
17
|
+
target: targetRef ? { current: targetRef } : undefined,
|
|
18
|
+
offset: ['start 50%', 'start 0%'],
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const opacity = useTransform(scrollYProgress, [0, 1], [1, 0])
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (typeof window === 'undefined') return
|
|
25
|
+
|
|
26
|
+
const container = containerRef.current
|
|
27
|
+
if (!container) return
|
|
28
|
+
|
|
29
|
+
const parent = container.closest('.renderblock-container')
|
|
30
|
+
if (!parent) return
|
|
31
|
+
|
|
32
|
+
let sibling = parent.nextElementSibling as HTMLElement | null
|
|
33
|
+
while (sibling && !sibling.classList.contains('renderblock-container')) {
|
|
34
|
+
sibling = sibling.nextElementSibling as HTMLElement | null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (sibling && !scrollSetupRef.current) {
|
|
38
|
+
setTargetRef(sibling)
|
|
39
|
+
scrollSetupRef.current = true
|
|
40
|
+
}
|
|
41
|
+
}, [])
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<motion.div ref={containerRef} style={{ opacity }}>
|
|
45
|
+
{children}
|
|
46
|
+
</motion.div>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React, { useRef, useEffect, useState, ReactNode } from 'react'
|
|
3
|
+
import { motion } from 'framer-motion'
|
|
4
|
+
import { cn } from '@/utilities/ui'
|
|
5
|
+
|
|
6
|
+
interface FadeUpOnInViewProps {
|
|
7
|
+
children: ReactNode
|
|
8
|
+
delay?: number
|
|
9
|
+
className?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const FadeUpOnInView: React.FC<FadeUpOnInViewProps> = ({ children, delay, className }) => {
|
|
13
|
+
const ref = useRef<HTMLDivElement | null>(null)
|
|
14
|
+
const [isVisible, setIsVisible] = useState(false)
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!ref.current) return
|
|
18
|
+
const target = ref.current
|
|
19
|
+
const observer = new IntersectionObserver(
|
|
20
|
+
(entries) => {
|
|
21
|
+
if (entries && entries.length > 0 && entries[0]) {
|
|
22
|
+
setIsVisible(!!entries[0].isIntersecting)
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
root: null,
|
|
27
|
+
rootMargin: '0px',
|
|
28
|
+
threshold: 0.5
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
observer.observe(target)
|
|
32
|
+
return () => {
|
|
33
|
+
observer.disconnect()
|
|
34
|
+
}
|
|
35
|
+
}, [])
|
|
36
|
+
|
|
37
|
+
// Defensive: Only allow number and >= 0 for delay
|
|
38
|
+
const animationDelay = typeof delay === 'number' && !isNaN(delay) && delay >= 0 ? delay : 0
|
|
39
|
+
|
|
40
|
+
// Defensive: Only render children if defined
|
|
41
|
+
const safeChildren = children === undefined || children === null ? null : children
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<motion.div
|
|
45
|
+
className={cn('fadeupInView', className)}
|
|
46
|
+
ref={ref}
|
|
47
|
+
initial={{ opacity: 0, y: 40 }}
|
|
48
|
+
animate={{
|
|
49
|
+
opacity: isVisible ? 1 : 0,
|
|
50
|
+
y: isVisible ? 0 : 40
|
|
51
|
+
}}
|
|
52
|
+
exit={{ opacity: 0, y: 40 }}
|
|
53
|
+
transition={{
|
|
54
|
+
duration: 0.3,
|
|
55
|
+
delay: animationDelay
|
|
56
|
+
}}
|
|
57
|
+
>
|
|
58
|
+
{safeChildren}
|
|
59
|
+
</motion.div>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default FadeUpOnInView
|