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
|
@@ -1,174 +1,174 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import React from 'react'
|
|
4
|
-
import Image from 'next/image'
|
|
5
|
-
import * as LucideIcons from 'lucide-react'
|
|
6
|
-
export type DDTechStackProps = Record<string, any>
|
|
7
|
-
|
|
8
|
-
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
9
|
-
type MediaObj = { url?: string | null; alt?: string | null }
|
|
10
|
-
type TechStackItem = {
|
|
11
|
-
image?: string | MediaObj | null
|
|
12
|
-
icon?: string | null
|
|
13
|
-
label?: string | null
|
|
14
|
-
showLabel?: boolean | null
|
|
15
|
-
}
|
|
16
|
-
type TechStackRow = {
|
|
17
|
-
duration?: number | null
|
|
18
|
-
direction?: 'left' | 'right' | null
|
|
19
|
-
items?: TechStackItem[] | null
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function getMediaUrl(media: string | MediaObj | null | undefined): string | null {
|
|
23
|
-
if (!media) return null
|
|
24
|
-
if (typeof media === 'string') return media
|
|
25
|
-
return media.url ?? null
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function getMediaAlt(media: string | MediaObj | null | undefined): string | null {
|
|
29
|
-
if (!media || typeof media === 'string') return null
|
|
30
|
-
return (media as MediaObj).alt ?? null
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// ─── Dynamic Lucide Icon ──────────────────────────────────────────────────────
|
|
34
|
-
function isLocalImageSrc(src?: string | null): src is string {
|
|
35
|
-
return typeof src === 'string' && src.startsWith('/api/media/file/')
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function DynamicIcon({
|
|
39
|
-
name,
|
|
40
|
-
size = 36,
|
|
41
|
-
...props
|
|
42
|
-
}: { name?: string | null } & Omit<LucideIcons.LucideProps, 'name'>) {
|
|
43
|
-
if (!name) return null
|
|
44
|
-
const Icon = (LucideIcons as unknown as Record<string, React.FC<LucideIcons.LucideProps>>)[name]
|
|
45
|
-
if (!Icon) return null
|
|
46
|
-
return <Icon size={size} {...props} />
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ─── Tech Item Card ───────────────────────────────────────────────────────────
|
|
50
|
-
function TechItem({
|
|
51
|
-
imageUrl,
|
|
52
|
-
imageAlt,
|
|
53
|
-
icon,
|
|
54
|
-
label,
|
|
55
|
-
showLabel,
|
|
56
|
-
}: {
|
|
57
|
-
imageUrl?: string | null
|
|
58
|
-
imageAlt?: string | null
|
|
59
|
-
icon?: string | null
|
|
60
|
-
label?: string | null
|
|
61
|
-
showLabel?: boolean | null
|
|
62
|
-
}) {
|
|
63
|
-
const safeImageUrl = isLocalImageSrc(imageUrl) ? imageUrl : null
|
|
64
|
-
|
|
65
|
-
return (
|
|
66
|
-
<div className="relative group/tech text-center shrink-0">
|
|
67
|
-
<div className="w-20 h-20 bg-linear-to-b from-[#1a1a1f] to-[#0f0f12] rounded-2xl p-4 transition-all duration-300 hover:scale-105 hover:shadow-[0_0_30px_rgba(140,138,132,0.15)] flex items-center justify-center border border-[#2A2A30] invert-[1] dark:invert-[0]">
|
|
68
|
-
{safeImageUrl ? (
|
|
69
|
-
<Image
|
|
70
|
-
src={safeImageUrl}
|
|
71
|
-
alt={imageAlt ?? label ?? 'Tech logo'}
|
|
72
|
-
width={40}
|
|
73
|
-
height={40}
|
|
74
|
-
loading="lazy"
|
|
75
|
-
className="object-contain w-10 h-10"
|
|
76
|
-
/>
|
|
77
|
-
) : (
|
|
78
|
-
<DynamicIcon className="text-[#9A9790]" name={icon} size={36} />
|
|
79
|
-
)}
|
|
80
|
-
</div>
|
|
81
|
-
{showLabel !== false && label && (
|
|
82
|
-
<span className="text-sm whitespace-nowrap text-[#9A9790] mt-2 block">{label}</span>
|
|
83
|
-
)}
|
|
84
|
-
</div>
|
|
85
|
-
)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// ─── Component ────────────────────────────────────────────────────────────────
|
|
89
|
-
export const DDTechStack: React.FC<DDTechStackProps> = ({
|
|
90
|
-
eyebrow,
|
|
91
|
-
heading,
|
|
92
|
-
techRows,
|
|
93
|
-
sectionId,
|
|
94
|
-
customCSS,
|
|
95
|
-
}) => {
|
|
96
|
-
const rows = (techRows ?? []) as TechStackRow[]
|
|
97
|
-
if (!rows.length) return null
|
|
98
|
-
|
|
99
|
-
const rowMaskStyle: React.CSSProperties = {
|
|
100
|
-
maskImage: 'linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%)',
|
|
101
|
-
WebkitMaskImage:
|
|
102
|
-
'linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%)',
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return (
|
|
106
|
-
<section
|
|
107
|
-
id={sectionId ?? undefined}
|
|
108
|
-
className="relative w-full bg-transparent py-12 px-4 md:px-6 max-w-6xl mx-auto"
|
|
109
|
-
>
|
|
110
|
-
{customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
|
|
111
|
-
|
|
112
|
-
{(eyebrow || heading) && (
|
|
113
|
-
<div className="mb-12">
|
|
114
|
-
#BlockName-DDTechStack
|
|
115
|
-
{eyebrow && (
|
|
116
|
-
<p className="text-sm uppercase tracking-widest text-[#9A9790] mb-2">{eyebrow}</p>
|
|
117
|
-
)}
|
|
118
|
-
{heading && (
|
|
119
|
-
<h2
|
|
120
|
-
className="text-3xl font-bold tracking-tighter sm:text-4xl text-stone-900 dark:text-stone-50"
|
|
121
|
-
style={{ fontFamily: "'Space Grotesk', sans-serif" }}
|
|
122
|
-
>
|
|
123
|
-
{heading}
|
|
124
|
-
</h2>
|
|
125
|
-
)}
|
|
126
|
-
</div>
|
|
127
|
-
)}
|
|
128
|
-
|
|
129
|
-
<div className="relative">
|
|
130
|
-
{rows.map((row, rowIndex) => {
|
|
131
|
-
const duration = row.duration ?? 40
|
|
132
|
-
const direction = row.direction ?? 'left'
|
|
133
|
-
const items = row.items ?? []
|
|
134
|
-
|
|
135
|
-
if (items.length === 0) return null
|
|
136
|
-
|
|
137
|
-
// Quadruple items for a seamless infinite loop
|
|
138
|
-
const duplicated = [...items, ...items, ...items, ...items]
|
|
139
|
-
|
|
140
|
-
return (
|
|
141
|
-
<div
|
|
142
|
-
key={rowIndex}
|
|
143
|
-
className="overflow-x-hidden overflow-y-visible py-6 relative"
|
|
144
|
-
style={rowMaskStyle}
|
|
145
|
-
>
|
|
146
|
-
<div
|
|
147
|
-
className="flex shrink-0 justify-around gap-8 flex-row animate-marquee"
|
|
148
|
-
style={{
|
|
149
|
-
animationDuration: `${duration}s`,
|
|
150
|
-
animationDirection: direction === 'right' ? 'reverse' : 'normal',
|
|
151
|
-
}}
|
|
152
|
-
>
|
|
153
|
-
{duplicated.map((item, idx) => {
|
|
154
|
-
const imgUrl = getMediaUrl(item.image as string | MediaObj | null)
|
|
155
|
-
const imgAlt = getMediaAlt(item.image as string | MediaObj | null)
|
|
156
|
-
return (
|
|
157
|
-
<TechItem
|
|
158
|
-
key={idx}
|
|
159
|
-
imageUrl={imgUrl}
|
|
160
|
-
imageAlt={imgAlt}
|
|
161
|
-
icon={item.icon}
|
|
162
|
-
label={item.label}
|
|
163
|
-
showLabel={item.showLabel}
|
|
164
|
-
/>
|
|
165
|
-
)
|
|
166
|
-
})}
|
|
167
|
-
</div>
|
|
168
|
-
</div>
|
|
169
|
-
)
|
|
170
|
-
})}
|
|
171
|
-
</div>
|
|
172
|
-
</section>
|
|
173
|
-
)
|
|
174
|
-
}
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import Image from 'next/image'
|
|
5
|
+
import * as LucideIcons from 'lucide-react'
|
|
6
|
+
export type DDTechStackProps = Record<string, any>
|
|
7
|
+
|
|
8
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
9
|
+
type MediaObj = { url?: string | null; alt?: string | null }
|
|
10
|
+
type TechStackItem = {
|
|
11
|
+
image?: string | MediaObj | null
|
|
12
|
+
icon?: string | null
|
|
13
|
+
label?: string | null
|
|
14
|
+
showLabel?: boolean | null
|
|
15
|
+
}
|
|
16
|
+
type TechStackRow = {
|
|
17
|
+
duration?: number | null
|
|
18
|
+
direction?: 'left' | 'right' | null
|
|
19
|
+
items?: TechStackItem[] | null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getMediaUrl(media: string | MediaObj | null | undefined): string | null {
|
|
23
|
+
if (!media) return null
|
|
24
|
+
if (typeof media === 'string') return media
|
|
25
|
+
return media.url ?? null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getMediaAlt(media: string | MediaObj | null | undefined): string | null {
|
|
29
|
+
if (!media || typeof media === 'string') return null
|
|
30
|
+
return (media as MediaObj).alt ?? null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ─── Dynamic Lucide Icon ──────────────────────────────────────────────────────
|
|
34
|
+
function isLocalImageSrc(src?: string | null): src is string {
|
|
35
|
+
return typeof src === 'string' && src.startsWith('/api/media/file/')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function DynamicIcon({
|
|
39
|
+
name,
|
|
40
|
+
size = 36,
|
|
41
|
+
...props
|
|
42
|
+
}: { name?: string | null } & Omit<LucideIcons.LucideProps, 'name'>) {
|
|
43
|
+
if (!name) return null
|
|
44
|
+
const Icon = (LucideIcons as unknown as Record<string, React.FC<LucideIcons.LucideProps>>)[name]
|
|
45
|
+
if (!Icon) return null
|
|
46
|
+
return <Icon size={size} {...props} />
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ─── Tech Item Card ───────────────────────────────────────────────────────────
|
|
50
|
+
function TechItem({
|
|
51
|
+
imageUrl,
|
|
52
|
+
imageAlt,
|
|
53
|
+
icon,
|
|
54
|
+
label,
|
|
55
|
+
showLabel,
|
|
56
|
+
}: {
|
|
57
|
+
imageUrl?: string | null
|
|
58
|
+
imageAlt?: string | null
|
|
59
|
+
icon?: string | null
|
|
60
|
+
label?: string | null
|
|
61
|
+
showLabel?: boolean | null
|
|
62
|
+
}) {
|
|
63
|
+
const safeImageUrl = isLocalImageSrc(imageUrl) ? imageUrl : null
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div className="relative group/tech text-center shrink-0">
|
|
67
|
+
<div className="w-20 h-20 bg-linear-to-b from-[#1a1a1f] to-[#0f0f12] rounded-2xl p-4 transition-all duration-300 hover:scale-105 hover:shadow-[0_0_30px_rgba(140,138,132,0.15)] flex items-center justify-center border border-[#2A2A30] invert-[1] dark:invert-[0]">
|
|
68
|
+
{safeImageUrl ? (
|
|
69
|
+
<Image
|
|
70
|
+
src={safeImageUrl}
|
|
71
|
+
alt={imageAlt ?? label ?? 'Tech logo'}
|
|
72
|
+
width={40}
|
|
73
|
+
height={40}
|
|
74
|
+
loading="lazy"
|
|
75
|
+
className="object-contain w-10 h-10"
|
|
76
|
+
/>
|
|
77
|
+
) : (
|
|
78
|
+
<DynamicIcon className="text-[#9A9790]" name={icon} size={36} />
|
|
79
|
+
)}
|
|
80
|
+
</div>
|
|
81
|
+
{showLabel !== false && label && (
|
|
82
|
+
<span className="text-sm whitespace-nowrap text-[#9A9790] mt-2 block">{label}</span>
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ─── Component ────────────────────────────────────────────────────────────────
|
|
89
|
+
export const DDTechStack: React.FC<DDTechStackProps> = ({
|
|
90
|
+
eyebrow,
|
|
91
|
+
heading,
|
|
92
|
+
techRows,
|
|
93
|
+
sectionId,
|
|
94
|
+
customCSS,
|
|
95
|
+
}) => {
|
|
96
|
+
const rows = (techRows ?? []) as TechStackRow[]
|
|
97
|
+
if (!rows.length) return null
|
|
98
|
+
|
|
99
|
+
const rowMaskStyle: React.CSSProperties = {
|
|
100
|
+
maskImage: 'linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%)',
|
|
101
|
+
WebkitMaskImage:
|
|
102
|
+
'linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%)',
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<section
|
|
107
|
+
id={sectionId ?? undefined}
|
|
108
|
+
className="relative w-full bg-transparent py-12 px-4 md:px-6 max-w-6xl mx-auto"
|
|
109
|
+
>
|
|
110
|
+
{customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
|
|
111
|
+
|
|
112
|
+
{(eyebrow || heading) && (
|
|
113
|
+
<div className="mb-12">
|
|
114
|
+
#BlockName-DDTechStack
|
|
115
|
+
{eyebrow && (
|
|
116
|
+
<p className="text-sm uppercase tracking-widest text-[#9A9790] mb-2">{eyebrow}</p>
|
|
117
|
+
)}
|
|
118
|
+
{heading && (
|
|
119
|
+
<h2
|
|
120
|
+
className="text-3xl font-bold tracking-tighter sm:text-4xl text-stone-900 dark:text-stone-50"
|
|
121
|
+
style={{ fontFamily: "'Space Grotesk', sans-serif" }}
|
|
122
|
+
>
|
|
123
|
+
{heading}
|
|
124
|
+
</h2>
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
)}
|
|
128
|
+
|
|
129
|
+
<div className="relative">
|
|
130
|
+
{rows.map((row, rowIndex) => {
|
|
131
|
+
const duration = row.duration ?? 40
|
|
132
|
+
const direction = row.direction ?? 'left'
|
|
133
|
+
const items = row.items ?? []
|
|
134
|
+
|
|
135
|
+
if (items.length === 0) return null
|
|
136
|
+
|
|
137
|
+
// Quadruple items for a seamless infinite loop
|
|
138
|
+
const duplicated = [...items, ...items, ...items, ...items]
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<div
|
|
142
|
+
key={rowIndex}
|
|
143
|
+
className="overflow-x-hidden overflow-y-visible py-6 relative"
|
|
144
|
+
style={rowMaskStyle}
|
|
145
|
+
>
|
|
146
|
+
<div
|
|
147
|
+
className="flex shrink-0 justify-around gap-8 flex-row animate-marquee"
|
|
148
|
+
style={{
|
|
149
|
+
animationDuration: `${duration}s`,
|
|
150
|
+
animationDirection: direction === 'right' ? 'reverse' : 'normal',
|
|
151
|
+
}}
|
|
152
|
+
>
|
|
153
|
+
{duplicated.map((item, idx) => {
|
|
154
|
+
const imgUrl = getMediaUrl(item.image as string | MediaObj | null)
|
|
155
|
+
const imgAlt = getMediaAlt(item.image as string | MediaObj | null)
|
|
156
|
+
return (
|
|
157
|
+
<TechItem
|
|
158
|
+
key={idx}
|
|
159
|
+
imageUrl={imgUrl}
|
|
160
|
+
imageAlt={imgAlt}
|
|
161
|
+
icon={item.icon}
|
|
162
|
+
label={item.label}
|
|
163
|
+
showLabel={item.showLabel}
|
|
164
|
+
/>
|
|
165
|
+
)
|
|
166
|
+
})}
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
)
|
|
170
|
+
})}
|
|
171
|
+
</div>
|
|
172
|
+
</section>
|
|
173
|
+
)
|
|
174
|
+
}
|
|
@@ -1,153 +1,153 @@
|
|
|
1
|
-
import type { Block } from 'payload'
|
|
2
|
-
import { iconPicker } from '@/fields/lucide-icon-picker/field'
|
|
3
|
-
|
|
4
|
-
export const DDTechStack: Block = {
|
|
5
|
-
slug: 'DD-TechStack',
|
|
6
|
-
interfaceName: 'DDTechStack',
|
|
7
|
-
labels: {
|
|
8
|
-
singular: 'Tech Stack',
|
|
9
|
-
plural: 'Tech Stacks'
|
|
10
|
-
},
|
|
11
|
-
fields: [
|
|
12
|
-
{
|
|
13
|
-
type: 'tabs',
|
|
14
|
-
tabs: [
|
|
15
|
-
{
|
|
16
|
-
label: 'Header',
|
|
17
|
-
fields: [
|
|
18
|
-
{
|
|
19
|
-
name: 'eyebrow',
|
|
20
|
-
type: 'text',
|
|
21
|
-
label: 'Eyebrow Label',
|
|
22
|
-
admin: { description: 'Small text above the heading (e.g. "Our Stack")' }
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
name: 'heading',
|
|
26
|
-
type: 'text',
|
|
27
|
-
label: 'Heading',
|
|
28
|
-
defaultValue: 'Technology Stack',
|
|
29
|
-
admin: { description: 'Main section heading. Leave blank to hide.' }
|
|
30
|
-
}
|
|
31
|
-
]
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
label: 'Rows',
|
|
35
|
-
fields: [
|
|
36
|
-
{
|
|
37
|
-
name: 'techRows',
|
|
38
|
-
type: 'array',
|
|
39
|
-
label: 'Tech Rows',
|
|
40
|
-
minRows: 1,
|
|
41
|
-
maxRows: 3,
|
|
42
|
-
admin: {
|
|
43
|
-
description: 'Up to 3 independently scrolling rows of tech stack items.'
|
|
44
|
-
},
|
|
45
|
-
fields: [
|
|
46
|
-
{
|
|
47
|
-
type: 'row',
|
|
48
|
-
fields: [
|
|
49
|
-
{
|
|
50
|
-
name: 'direction',
|
|
51
|
-
type: 'select',
|
|
52
|
-
label: 'Scroll Direction',
|
|
53
|
-
defaultValue: 'left',
|
|
54
|
-
options: [
|
|
55
|
-
{ label: '← Left', value: 'left' },
|
|
56
|
-
{ label: '→ Right', value: 'right' }
|
|
57
|
-
],
|
|
58
|
-
admin: { width: '50%' }
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
name: 'duration',
|
|
62
|
-
type: 'number',
|
|
63
|
-
label: 'Duration (seconds)',
|
|
64
|
-
defaultValue: 40,
|
|
65
|
-
min: 5,
|
|
66
|
-
max: 180,
|
|
67
|
-
admin: {
|
|
68
|
-
description: 'Scroll cycle time — lower = faster.',
|
|
69
|
-
width: '50%'
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
]
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
name: 'items',
|
|
76
|
-
type: 'array',
|
|
77
|
-
label: 'Tech Items',
|
|
78
|
-
minRows: 1,
|
|
79
|
-
admin: {
|
|
80
|
-
description:
|
|
81
|
-
'Each item shows a logo image or Lucide icon with an optional label. Upload takes priority over icon.'
|
|
82
|
-
},
|
|
83
|
-
fields: [
|
|
84
|
-
{
|
|
85
|
-
name: 'image',
|
|
86
|
-
type: 'upload',
|
|
87
|
-
label: 'Custom Logo',
|
|
88
|
-
relationTo: 'media',
|
|
89
|
-
admin: {
|
|
90
|
-
description:
|
|
91
|
-
'Upload SVG, PNG, JPG, WebP, or GIF. Takes priority over the Lucide icon.'
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
type: 'row',
|
|
96
|
-
fields: [
|
|
97
|
-
iconPicker({
|
|
98
|
-
name: 'icon',
|
|
99
|
-
required: false,
|
|
100
|
-
description: 'Fallback if no logo is uploaded (e.g. Code2, Database)',
|
|
101
|
-
overrides: (field) => ({
|
|
102
|
-
...field,
|
|
103
|
-
label: 'Lucide Icon (fallback)',
|
|
104
|
-
admin: { ...field.admin, width: '40%' }
|
|
105
|
-
})
|
|
106
|
-
}),
|
|
107
|
-
{
|
|
108
|
-
name: 'label',
|
|
109
|
-
type: 'text',
|
|
110
|
-
label: 'Label',
|
|
111
|
-
admin: {
|
|
112
|
-
description: 'e.g. "React", "TypeScript"',
|
|
113
|
-
width: '40%'
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
name: 'showLabel',
|
|
118
|
-
type: 'checkbox',
|
|
119
|
-
label: 'Show Label',
|
|
120
|
-
defaultValue: true,
|
|
121
|
-
admin: { width: '20%' }
|
|
122
|
-
}
|
|
123
|
-
]
|
|
124
|
-
}
|
|
125
|
-
]
|
|
126
|
-
}
|
|
127
|
-
]
|
|
128
|
-
}
|
|
129
|
-
]
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
label: 'Settings',
|
|
133
|
-
fields: [
|
|
134
|
-
{
|
|
135
|
-
name: 'sectionId',
|
|
136
|
-
type: 'text',
|
|
137
|
-
label: 'Section ID',
|
|
138
|
-
admin: {
|
|
139
|
-
description: 'Unique ID for anchor linking (e.g. "tech-stack")'
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
name: 'customCSS',
|
|
144
|
-
type: 'code',
|
|
145
|
-
label: 'Custom CSS',
|
|
146
|
-
admin: { language: 'css' }
|
|
147
|
-
}
|
|
148
|
-
]
|
|
149
|
-
}
|
|
150
|
-
]
|
|
151
|
-
}
|
|
152
|
-
]
|
|
153
|
-
}
|
|
1
|
+
import type { Block } from 'payload'
|
|
2
|
+
import { iconPicker } from '@/fields/lucide-icon-picker/field'
|
|
3
|
+
|
|
4
|
+
export const DDTechStack: Block = {
|
|
5
|
+
slug: 'DD-TechStack',
|
|
6
|
+
interfaceName: 'DDTechStack',
|
|
7
|
+
labels: {
|
|
8
|
+
singular: 'Tech Stack',
|
|
9
|
+
plural: 'Tech Stacks'
|
|
10
|
+
},
|
|
11
|
+
fields: [
|
|
12
|
+
{
|
|
13
|
+
type: 'tabs',
|
|
14
|
+
tabs: [
|
|
15
|
+
{
|
|
16
|
+
label: 'Header',
|
|
17
|
+
fields: [
|
|
18
|
+
{
|
|
19
|
+
name: 'eyebrow',
|
|
20
|
+
type: 'text',
|
|
21
|
+
label: 'Eyebrow Label',
|
|
22
|
+
admin: { description: 'Small text above the heading (e.g. "Our Stack")' }
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'heading',
|
|
26
|
+
type: 'text',
|
|
27
|
+
label: 'Heading',
|
|
28
|
+
defaultValue: 'Technology Stack',
|
|
29
|
+
admin: { description: 'Main section heading. Leave blank to hide.' }
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
label: 'Rows',
|
|
35
|
+
fields: [
|
|
36
|
+
{
|
|
37
|
+
name: 'techRows',
|
|
38
|
+
type: 'array',
|
|
39
|
+
label: 'Tech Rows',
|
|
40
|
+
minRows: 1,
|
|
41
|
+
maxRows: 3,
|
|
42
|
+
admin: {
|
|
43
|
+
description: 'Up to 3 independently scrolling rows of tech stack items.'
|
|
44
|
+
},
|
|
45
|
+
fields: [
|
|
46
|
+
{
|
|
47
|
+
type: 'row',
|
|
48
|
+
fields: [
|
|
49
|
+
{
|
|
50
|
+
name: 'direction',
|
|
51
|
+
type: 'select',
|
|
52
|
+
label: 'Scroll Direction',
|
|
53
|
+
defaultValue: 'left',
|
|
54
|
+
options: [
|
|
55
|
+
{ label: '← Left', value: 'left' },
|
|
56
|
+
{ label: '→ Right', value: 'right' }
|
|
57
|
+
],
|
|
58
|
+
admin: { width: '50%' }
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'duration',
|
|
62
|
+
type: 'number',
|
|
63
|
+
label: 'Duration (seconds)',
|
|
64
|
+
defaultValue: 40,
|
|
65
|
+
min: 5,
|
|
66
|
+
max: 180,
|
|
67
|
+
admin: {
|
|
68
|
+
description: 'Scroll cycle time — lower = faster.',
|
|
69
|
+
width: '50%'
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'items',
|
|
76
|
+
type: 'array',
|
|
77
|
+
label: 'Tech Items',
|
|
78
|
+
minRows: 1,
|
|
79
|
+
admin: {
|
|
80
|
+
description:
|
|
81
|
+
'Each item shows a logo image or Lucide icon with an optional label. Upload takes priority over icon.'
|
|
82
|
+
},
|
|
83
|
+
fields: [
|
|
84
|
+
{
|
|
85
|
+
name: 'image',
|
|
86
|
+
type: 'upload',
|
|
87
|
+
label: 'Custom Logo',
|
|
88
|
+
relationTo: 'media',
|
|
89
|
+
admin: {
|
|
90
|
+
description:
|
|
91
|
+
'Upload SVG, PNG, JPG, WebP, or GIF. Takes priority over the Lucide icon.'
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
type: 'row',
|
|
96
|
+
fields: [
|
|
97
|
+
iconPicker({
|
|
98
|
+
name: 'icon',
|
|
99
|
+
required: false,
|
|
100
|
+
description: 'Fallback if no logo is uploaded (e.g. Code2, Database)',
|
|
101
|
+
overrides: (field) => ({
|
|
102
|
+
...field,
|
|
103
|
+
label: 'Lucide Icon (fallback)',
|
|
104
|
+
admin: { ...field.admin, width: '40%' }
|
|
105
|
+
})
|
|
106
|
+
}),
|
|
107
|
+
{
|
|
108
|
+
name: 'label',
|
|
109
|
+
type: 'text',
|
|
110
|
+
label: 'Label',
|
|
111
|
+
admin: {
|
|
112
|
+
description: 'e.g. "React", "TypeScript"',
|
|
113
|
+
width: '40%'
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'showLabel',
|
|
118
|
+
type: 'checkbox',
|
|
119
|
+
label: 'Show Label',
|
|
120
|
+
defaultValue: true,
|
|
121
|
+
admin: { width: '20%' }
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
]
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
label: 'Settings',
|
|
133
|
+
fields: [
|
|
134
|
+
{
|
|
135
|
+
name: 'sectionId',
|
|
136
|
+
type: 'text',
|
|
137
|
+
label: 'Section ID',
|
|
138
|
+
admin: {
|
|
139
|
+
description: 'Unique ID for anchor linking (e.g. "tech-stack")'
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'customCSS',
|
|
144
|
+
type: 'code',
|
|
145
|
+
label: 'Custom CSS',
|
|
146
|
+
admin: { language: 'css' }
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
}
|
|
152
|
+
]
|
|
153
|
+
}
|