@fayz-ai/plugin-linkinbio 0.9.1 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/public/{data.d.ts → data/index.d.ts} +2 -2
- package/dist/public/data/index.d.ts.map +1 -0
- package/dist/public/{data.supabase.d.ts → data/supabase.d.ts} +2 -2
- package/dist/public/data/supabase.d.ts.map +1 -0
- package/dist/public/index.d.ts +8 -8
- package/dist/public/index.d.ts.map +1 -1
- package/dist/public/index.js +2 -2
- package/dist/public/index.js.map +1 -1
- package/dist/public/{registry.d.ts → lib/registry.d.ts} +1 -1
- package/dist/public/lib/registry.d.ts.map +1 -0
- package/dist/public/{utils.d.ts → lib/utils.d.ts} +1 -1
- package/dist/public/lib/utils.d.ts.map +1 -0
- package/dist/public/views/BioPage.d.ts.map +1 -0
- package/dist/public/{BioPageRenderer.d.ts → views/BioPageRenderer.d.ts} +1 -1
- package/dist/public/views/BioPageRenderer.d.ts.map +1 -0
- package/package.json +12 -15
- package/dist/index.cjs +0 -33
- package/dist/index.cjs.map +0 -1
- package/dist/public/BioPage.d.ts.map +0 -1
- package/dist/public/BioPageRenderer.d.ts.map +0 -1
- package/dist/public/__tests__/manifest.test.d.ts +0 -2
- package/dist/public/__tests__/manifest.test.d.ts.map +0 -1
- package/dist/public/data.d.ts.map +0 -1
- package/dist/public/data.supabase.d.ts.map +0 -1
- package/dist/public/index.cjs +0 -1984
- package/dist/public/index.cjs.map +0 -1
- package/dist/public/registry.d.ts.map +0 -1
- package/dist/public/utils.d.ts.map +0 -1
- package/src/index.ts +0 -56
- package/src/public/BioPage.tsx +0 -123
- package/src/public/BioPageRenderer.tsx +0 -251
- package/src/public/__tests__/manifest.test.ts +0 -39
- package/src/public/blocks/BioSection.tsx +0 -66
- package/src/public/blocks/CTASection.tsx +0 -56
- package/src/public/blocks/EmbedSection.tsx +0 -53
- package/src/public/blocks/FeaturedBlock.tsx +0 -70
- package/src/public/blocks/GallerySection.tsx +0 -148
- package/src/public/blocks/HeroSection.tsx +0 -107
- package/src/public/blocks/LinksBlock.tsx +0 -80
- package/src/public/blocks/MusicReleasesBlock.tsx +0 -83
- package/src/public/blocks/ProfileHeader.tsx +0 -174
- package/src/public/blocks/ProjectSection.tsx +0 -158
- package/src/public/blocks/SocialLinksSection.tsx +0 -125
- package/src/public/blocks/StatsSection.tsx +0 -46
- package/src/public/blocks/TextSection.tsx +0 -29
- package/src/public/blocks/TourDatesBlock.tsx +0 -83
- package/src/public/blocks/VenueListSection.tsx +0 -91
- package/src/public/blocks/VideoGridSection.tsx +0 -105
- package/src/public/blocks/index.ts +0 -16
- package/src/public/components/Carousel.tsx +0 -90
- package/src/public/components/MediaKit.tsx +0 -249
- package/src/public/components/PlatformIcon.tsx +0 -56
- package/src/public/components/Reveal.tsx +0 -58
- package/src/public/context.tsx +0 -30
- package/src/public/createPublicLinkInBioPlugin.ts +0 -81
- package/src/public/data.supabase.ts +0 -31
- package/src/public/data.ts +0 -29
- package/src/public/index.ts +0 -75
- package/src/public/registry.ts +0 -40
- package/src/public/utils.ts +0 -102
- package/src/types.ts +0 -337
- /package/dist/public/{BioPage.d.ts → views/BioPage.d.ts} +0 -0
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { ExternalLink } from 'lucide-react';
|
|
2
|
-
import type { SocialLinksSection as SocialLinksSectionType } from '../../types';
|
|
3
|
-
import { platformColors, platformLabels } from '../utils';
|
|
4
|
-
import { PlatformIcon } from '../components/PlatformIcon';
|
|
5
|
-
|
|
6
|
-
interface Props {
|
|
7
|
-
section: SocialLinksSectionType;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export default function SocialLinksSection({ section }: Props) {
|
|
11
|
-
const layout = section.layout ?? 'buttons';
|
|
12
|
-
|
|
13
|
-
return (
|
|
14
|
-
<section className="py-16 md:py-24 px-6" id={section.id}>
|
|
15
|
-
<div className="max-w-xl mx-auto">
|
|
16
|
-
{section.title && (
|
|
17
|
-
<h2
|
|
18
|
-
className="text-3xl md:text-4xl font-bold mb-10 text-center"
|
|
19
|
-
style={{ fontFamily: 'var(--pk-font-heading)', color: 'var(--pk-text)' }}
|
|
20
|
-
>
|
|
21
|
-
{section.title}
|
|
22
|
-
</h2>
|
|
23
|
-
)}
|
|
24
|
-
|
|
25
|
-
{layout === 'icons' && (
|
|
26
|
-
<div className="flex flex-wrap justify-center gap-5">
|
|
27
|
-
{section.links.map((link) => {
|
|
28
|
-
const color = platformColors[link.platform];
|
|
29
|
-
return (
|
|
30
|
-
<a
|
|
31
|
-
key={link.platform + link.url}
|
|
32
|
-
href={link.url}
|
|
33
|
-
target="_blank"
|
|
34
|
-
rel="noopener noreferrer"
|
|
35
|
-
className="group flex items-center justify-center w-16 h-16 rounded-full transition-all duration-300 hover:scale-110 hover:shadow-lg"
|
|
36
|
-
style={{
|
|
37
|
-
backgroundColor: color + '15',
|
|
38
|
-
boxShadow: `0 0 0 1px ${color}25`,
|
|
39
|
-
}}
|
|
40
|
-
title={link.label ?? platformLabels[link.platform]}
|
|
41
|
-
>
|
|
42
|
-
<PlatformIcon platform={link.platform} size={26} style={{ color }} />
|
|
43
|
-
</a>
|
|
44
|
-
);
|
|
45
|
-
})}
|
|
46
|
-
</div>
|
|
47
|
-
)}
|
|
48
|
-
|
|
49
|
-
{layout === 'buttons' && (
|
|
50
|
-
<div className="flex flex-col gap-3">
|
|
51
|
-
{section.links.map((link) => {
|
|
52
|
-
const color = platformColors[link.platform];
|
|
53
|
-
return (
|
|
54
|
-
<a
|
|
55
|
-
key={link.platform + link.url}
|
|
56
|
-
href={link.url}
|
|
57
|
-
target="_blank"
|
|
58
|
-
rel="noopener noreferrer"
|
|
59
|
-
className="group relative flex items-center gap-5 px-6 py-4 font-medium transition-all duration-300 hover:scale-[1.02] overflow-hidden"
|
|
60
|
-
style={{
|
|
61
|
-
backgroundColor: color + '10',
|
|
62
|
-
color: 'var(--pk-text)',
|
|
63
|
-
borderRadius: 'var(--pk-radius)',
|
|
64
|
-
border: `1px solid ${color}30`,
|
|
65
|
-
}}
|
|
66
|
-
>
|
|
67
|
-
{/* Hover glow */}
|
|
68
|
-
<div
|
|
69
|
-
className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-300"
|
|
70
|
-
style={{ background: `linear-gradient(135deg, ${color}18, transparent 60%)` }}
|
|
71
|
-
/>
|
|
72
|
-
|
|
73
|
-
<div className="relative z-10 flex items-center justify-center w-10 h-10 rounded-full shrink-0"
|
|
74
|
-
style={{ backgroundColor: color + '20' }}
|
|
75
|
-
>
|
|
76
|
-
<PlatformIcon platform={link.platform} size={20} style={{ color }} />
|
|
77
|
-
</div>
|
|
78
|
-
|
|
79
|
-
<span className="relative z-10 text-base">{link.label ?? platformLabels[link.platform]}</span>
|
|
80
|
-
|
|
81
|
-
<ExternalLink
|
|
82
|
-
size={16}
|
|
83
|
-
className="relative z-10 ml-auto opacity-0 group-hover:opacity-50 transition-opacity duration-300"
|
|
84
|
-
style={{ color: 'var(--pk-text)' }}
|
|
85
|
-
/>
|
|
86
|
-
</a>
|
|
87
|
-
);
|
|
88
|
-
})}
|
|
89
|
-
</div>
|
|
90
|
-
)}
|
|
91
|
-
|
|
92
|
-
{layout === 'list' && (
|
|
93
|
-
<div className="space-y-1">
|
|
94
|
-
{section.links.map((link) => {
|
|
95
|
-
const color = platformColors[link.platform];
|
|
96
|
-
return (
|
|
97
|
-
<a
|
|
98
|
-
key={link.platform + link.url}
|
|
99
|
-
href={link.url}
|
|
100
|
-
target="_blank"
|
|
101
|
-
rel="noopener noreferrer"
|
|
102
|
-
className="group flex items-center gap-4 py-4 px-3 transition-all duration-200 hover:bg-white/5 rounded-lg"
|
|
103
|
-
>
|
|
104
|
-
<div className="flex items-center justify-center w-9 h-9 rounded-full shrink-0"
|
|
105
|
-
style={{ backgroundColor: color + '18' }}
|
|
106
|
-
>
|
|
107
|
-
<PlatformIcon platform={link.platform} size={18} style={{ color }} />
|
|
108
|
-
</div>
|
|
109
|
-
<span className="text-sm font-medium" style={{ color: 'var(--pk-text)' }}>
|
|
110
|
-
{link.label ?? platformLabels[link.platform]}
|
|
111
|
-
</span>
|
|
112
|
-
<ExternalLink
|
|
113
|
-
size={14}
|
|
114
|
-
className="ml-auto opacity-0 group-hover:opacity-40 transition-opacity"
|
|
115
|
-
style={{ color: 'var(--pk-text)' }}
|
|
116
|
-
/>
|
|
117
|
-
</a>
|
|
118
|
-
);
|
|
119
|
-
})}
|
|
120
|
-
</div>
|
|
121
|
-
)}
|
|
122
|
-
</div>
|
|
123
|
-
</section>
|
|
124
|
-
);
|
|
125
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import type { StatsSection as StatsSectionType } from '../../types';
|
|
2
|
-
|
|
3
|
-
interface Props {
|
|
4
|
-
section: StatsSectionType;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export default function StatsSection({ section }: Props) {
|
|
8
|
-
const isGrid = section.layout === 'grid';
|
|
9
|
-
|
|
10
|
-
return (
|
|
11
|
-
<section className="py-16 md:py-24 px-6" id={section.id}>
|
|
12
|
-
<div className="max-w-4xl mx-auto">
|
|
13
|
-
{section.title && (
|
|
14
|
-
<h2
|
|
15
|
-
className="text-3xl md:text-4xl font-bold mb-8 text-center"
|
|
16
|
-
style={{ fontFamily: 'var(--pk-font-heading)', color: 'var(--pk-text)' }}
|
|
17
|
-
>
|
|
18
|
-
{section.title}
|
|
19
|
-
</h2>
|
|
20
|
-
)}
|
|
21
|
-
|
|
22
|
-
<div
|
|
23
|
-
className={
|
|
24
|
-
isGrid
|
|
25
|
-
? 'grid grid-cols-2 md:grid-cols-4 gap-6'
|
|
26
|
-
: 'flex flex-wrap justify-center gap-8 md:gap-12'
|
|
27
|
-
}
|
|
28
|
-
>
|
|
29
|
-
{section.stats.map((stat, i) => (
|
|
30
|
-
<div key={i} className="text-center">
|
|
31
|
-
<div
|
|
32
|
-
className="text-3xl md:text-4xl font-bold mb-1"
|
|
33
|
-
style={{ fontFamily: 'var(--pk-font-heading)', color: 'var(--pk-primary)' }}
|
|
34
|
-
>
|
|
35
|
-
{stat.value}
|
|
36
|
-
</div>
|
|
37
|
-
<div className="text-sm" style={{ color: 'var(--pk-muted)' }}>
|
|
38
|
-
{stat.label}
|
|
39
|
-
</div>
|
|
40
|
-
</div>
|
|
41
|
-
))}
|
|
42
|
-
</div>
|
|
43
|
-
</div>
|
|
44
|
-
</section>
|
|
45
|
-
);
|
|
46
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { TextSection as TextSectionType } from '../../types';
|
|
2
|
-
import { renderMarkdown } from '../utils';
|
|
3
|
-
|
|
4
|
-
interface Props {
|
|
5
|
-
section: TextSectionType;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export default function TextSection({ section }: Props) {
|
|
9
|
-
return (
|
|
10
|
-
<section className="py-16 md:py-24 px-6" id={section.id}>
|
|
11
|
-
<div className="max-w-3xl mx-auto">
|
|
12
|
-
{section.title && (
|
|
13
|
-
<h2
|
|
14
|
-
className="text-3xl md:text-4xl font-bold mb-8"
|
|
15
|
-
style={{ fontFamily: 'var(--pk-font-heading)', color: 'var(--pk-text)' }}
|
|
16
|
-
>
|
|
17
|
-
{section.title}
|
|
18
|
-
</h2>
|
|
19
|
-
)}
|
|
20
|
-
|
|
21
|
-
<div
|
|
22
|
-
className="text-base md:text-lg leading-relaxed [&_p]:mb-4 [&_strong]:font-semibold [&_em]:italic [&_a]:underline [&_a]:decoration-[var(--pk-primary)]"
|
|
23
|
-
style={{ color: 'var(--pk-muted)' }}
|
|
24
|
-
dangerouslySetInnerHTML={{ __html: renderMarkdown(section.content) }}
|
|
25
|
-
/>
|
|
26
|
-
</div>
|
|
27
|
-
</section>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { MapPin } from 'lucide-react';
|
|
2
|
-
import type { TourDatesSection, TourDateItem } from '../../types';
|
|
3
|
-
|
|
4
|
-
interface Props {
|
|
5
|
-
section: TourDatesSection;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const MONTHS_PT = ['JAN', 'FEV', 'MAR', 'ABR', 'MAI', 'JUN', 'JUL', 'AGO', 'SET', 'OUT', 'NOV', 'DEZ'];
|
|
9
|
-
|
|
10
|
-
function parseDate(date: string): { month: string; day: string; year: string } {
|
|
11
|
-
const m = date.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
|
12
|
-
if (m) {
|
|
13
|
-
const monthIdx = Math.min(11, Math.max(0, parseInt(m[2], 10) - 1));
|
|
14
|
-
return { month: MONTHS_PT[monthIdx], day: String(parseInt(m[3], 10)), year: m[1] };
|
|
15
|
-
}
|
|
16
|
-
return { month: '', day: date, year: '' };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function DateRow({ item, cta }: { item: TourDateItem; cta: string }) {
|
|
20
|
-
const { month, day, year } = parseDate(item.date);
|
|
21
|
-
const hasLink = !!item.url;
|
|
22
|
-
const label = hasLink ? cta : item.soldOut ? 'Esgotado' : 'Em breve';
|
|
23
|
-
const Tag = hasLink ? 'a' : 'div';
|
|
24
|
-
return (
|
|
25
|
-
<Tag
|
|
26
|
-
{...(hasLink ? { href: item.url, target: '_blank', rel: 'noopener noreferrer' } : {})}
|
|
27
|
-
className={`flex items-center gap-4 p-3 transition-all duration-200 ${hasLink ? 'hover:scale-[1.01]' : 'cursor-default'}`}
|
|
28
|
-
style={{
|
|
29
|
-
backgroundColor: 'rgba(255,255,255,0.04)',
|
|
30
|
-
border: '1px solid rgba(255,255,255,0.07)',
|
|
31
|
-
borderRadius: 'var(--pk-radius)',
|
|
32
|
-
}}
|
|
33
|
-
>
|
|
34
|
-
<div
|
|
35
|
-
className="shrink-0 flex flex-col items-center justify-center w-14 h-16"
|
|
36
|
-
style={{ backgroundColor: 'rgba(255,255,255,0.05)', borderRadius: 'calc(var(--pk-radius) - 4px)' }}
|
|
37
|
-
>
|
|
38
|
-
<span className="text-[10px] font-bold tracking-wider" style={{ color: 'var(--pk-primary)' }}>{month}</span>
|
|
39
|
-
<span className="text-xl font-bold leading-none" style={{ color: 'var(--pk-text)', fontFamily: 'var(--pk-font-heading)' }}>{day}</span>
|
|
40
|
-
<span className="text-[10px] opacity-60" style={{ color: 'var(--pk-muted)' }}>{year}</span>
|
|
41
|
-
</div>
|
|
42
|
-
<div className="min-w-0 flex-1">
|
|
43
|
-
<p className="text-sm font-semibold truncate" style={{ color: 'var(--pk-text)' }}>{item.venue}</p>
|
|
44
|
-
{item.city && (
|
|
45
|
-
<p className="text-xs truncate flex items-center gap-1 mt-0.5" style={{ color: 'var(--pk-muted)' }}>
|
|
46
|
-
<MapPin size={11} /> {item.city}
|
|
47
|
-
</p>
|
|
48
|
-
)}
|
|
49
|
-
</div>
|
|
50
|
-
<span
|
|
51
|
-
className="shrink-0 px-4 py-1.5 text-xs font-semibold rounded-full"
|
|
52
|
-
style={
|
|
53
|
-
hasLink
|
|
54
|
-
? { backgroundColor: 'var(--pk-primary)', color: 'var(--pk-bg)' }
|
|
55
|
-
: { backgroundColor: 'transparent', color: 'var(--pk-muted)', border: '1px solid rgba(255,255,255,0.12)' }
|
|
56
|
-
}
|
|
57
|
-
>
|
|
58
|
-
{label}
|
|
59
|
-
</span>
|
|
60
|
-
</Tag>
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export default function TourDatesBlock({ section }: Props) {
|
|
65
|
-
const cta = section.ctaLabel ?? 'Ingressos';
|
|
66
|
-
return (
|
|
67
|
-
<section className="py-6" id={section.id}>
|
|
68
|
-
{section.title && (
|
|
69
|
-
<h2
|
|
70
|
-
className="text-xl md:text-2xl font-bold mb-4 px-1"
|
|
71
|
-
style={{ fontFamily: 'var(--pk-font-heading)', color: 'var(--pk-text)' }}
|
|
72
|
-
>
|
|
73
|
-
{section.title}
|
|
74
|
-
</h2>
|
|
75
|
-
)}
|
|
76
|
-
<div className="flex flex-col gap-3">
|
|
77
|
-
{section.dates.map((item, i) => (
|
|
78
|
-
<DateRow key={item.date + item.venue + i} item={item} cta={cta} />
|
|
79
|
-
))}
|
|
80
|
-
</div>
|
|
81
|
-
</section>
|
|
82
|
-
);
|
|
83
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import type { VenueListSection as VenueListSectionType } from '../../types';
|
|
2
|
-
|
|
3
|
-
interface Props {
|
|
4
|
-
section: VenueListSectionType;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export default function VenueListSection({ section }: Props) {
|
|
8
|
-
const layout = section.layout ?? 'grid';
|
|
9
|
-
|
|
10
|
-
return (
|
|
11
|
-
<section className="py-16 md:py-24 px-6" id={section.id}>
|
|
12
|
-
<div className="max-w-5xl mx-auto">
|
|
13
|
-
{section.title && (
|
|
14
|
-
<h2
|
|
15
|
-
className="text-3xl md:text-4xl font-bold mb-10"
|
|
16
|
-
style={{ fontFamily: 'var(--pk-font-heading)', color: 'var(--pk-text)' }}
|
|
17
|
-
>
|
|
18
|
-
{section.title}
|
|
19
|
-
</h2>
|
|
20
|
-
)}
|
|
21
|
-
|
|
22
|
-
{layout === 'logo-cloud' ? (
|
|
23
|
-
<div className="flex flex-wrap justify-center gap-6 md:gap-8">
|
|
24
|
-
{section.venues.map((venue, i) => (
|
|
25
|
-
<div key={i} className="flex flex-col items-center gap-2">
|
|
26
|
-
{venue.logoUrl ? (
|
|
27
|
-
<img src={venue.logoUrl} alt={venue.name} className="h-12 w-12 object-contain opacity-70" loading="lazy" />
|
|
28
|
-
) : (
|
|
29
|
-
<div
|
|
30
|
-
className="h-12 w-12 flex items-center justify-center text-lg font-bold opacity-70"
|
|
31
|
-
style={{ backgroundColor: 'rgba(255,255,255,0.06)', color: 'var(--pk-text)', borderRadius: 'var(--pk-radius)' }}
|
|
32
|
-
>
|
|
33
|
-
{venue.name[0]}
|
|
34
|
-
</div>
|
|
35
|
-
)}
|
|
36
|
-
<span className="text-xs text-center" style={{ color: 'var(--pk-muted)' }}>{venue.name}</span>
|
|
37
|
-
</div>
|
|
38
|
-
))}
|
|
39
|
-
</div>
|
|
40
|
-
) : layout === 'list' ? (
|
|
41
|
-
<div className="space-y-3">
|
|
42
|
-
{section.venues.map((venue, i) => (
|
|
43
|
-
<div
|
|
44
|
-
key={i}
|
|
45
|
-
className="flex items-center justify-between py-3 px-4"
|
|
46
|
-
style={{ backgroundColor: 'rgba(255,255,255,0.04)', borderRadius: 'var(--pk-radius)' }}
|
|
47
|
-
>
|
|
48
|
-
<span className="font-medium" style={{ color: 'var(--pk-text)' }}>{venue.name}</span>
|
|
49
|
-
<span className="text-sm" style={{ color: 'var(--pk-muted)' }}>
|
|
50
|
-
{[venue.city, venue.year].filter(Boolean).join(' · ')}
|
|
51
|
-
</span>
|
|
52
|
-
</div>
|
|
53
|
-
))}
|
|
54
|
-
</div>
|
|
55
|
-
) : (
|
|
56
|
-
/* Organic flowing tag cloud */
|
|
57
|
-
<div className="flex flex-wrap gap-3">
|
|
58
|
-
{section.venues.map((venue, i) => {
|
|
59
|
-
// Vary sizes based on name length and position for organic feel
|
|
60
|
-
const isLong = venue.name.length > 12;
|
|
61
|
-
const isShort = venue.name.length < 6;
|
|
62
|
-
const sizeClass = isLong ? 'text-base md:text-lg px-6 py-3' : isShort ? 'text-sm px-4 py-2.5' : 'text-sm md:text-base px-5 py-3';
|
|
63
|
-
// Alternate subtle visual weight
|
|
64
|
-
const isHighlight = i % 5 === 0 || i % 7 === 0;
|
|
65
|
-
return (
|
|
66
|
-
<div
|
|
67
|
-
key={i}
|
|
68
|
-
className={`inline-flex flex-col items-start transition-all duration-300 hover:scale-105 ${sizeClass}`}
|
|
69
|
-
style={{
|
|
70
|
-
backgroundColor: isHighlight ? 'rgba(255,255,255,0.08)' : 'rgba(255,255,255,0.03)',
|
|
71
|
-
border: isHighlight ? '1px solid rgba(255,255,255,0.12)' : '1px solid rgba(255,255,255,0.06)',
|
|
72
|
-
borderRadius: '9999px',
|
|
73
|
-
}}
|
|
74
|
-
>
|
|
75
|
-
<span className="font-semibold whitespace-nowrap" style={{ color: 'var(--pk-text)' }}>
|
|
76
|
-
{venue.name}
|
|
77
|
-
{venue.city && (
|
|
78
|
-
<span className="font-normal ml-2 opacity-50" style={{ color: 'var(--pk-muted)', fontSize: '0.8em' }}>
|
|
79
|
-
{venue.city}
|
|
80
|
-
</span>
|
|
81
|
-
)}
|
|
82
|
-
</span>
|
|
83
|
-
</div>
|
|
84
|
-
);
|
|
85
|
-
})}
|
|
86
|
-
</div>
|
|
87
|
-
)}
|
|
88
|
-
</div>
|
|
89
|
-
</section>
|
|
90
|
-
);
|
|
91
|
-
}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import type { VideoGridSection as VideoGridSectionType } from '../../types';
|
|
2
|
-
|
|
3
|
-
interface Props {
|
|
4
|
-
section: VideoGridSectionType;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export default function VideoGridSection({ section }: Props) {
|
|
8
|
-
const isFeatured = section.layout === 'featured';
|
|
9
|
-
const isList = section.layout === 'list';
|
|
10
|
-
|
|
11
|
-
return (
|
|
12
|
-
<section className="py-16 md:py-24 px-6" id={section.id}>
|
|
13
|
-
<div className="max-w-5xl mx-auto">
|
|
14
|
-
{section.title && (
|
|
15
|
-
<h2
|
|
16
|
-
className="text-3xl md:text-4xl font-bold mb-8"
|
|
17
|
-
style={{ fontFamily: 'var(--pk-font-heading)', color: 'var(--pk-text)' }}
|
|
18
|
-
>
|
|
19
|
-
{section.title}
|
|
20
|
-
</h2>
|
|
21
|
-
)}
|
|
22
|
-
|
|
23
|
-
{isFeatured && section.videos.length > 0 ? (
|
|
24
|
-
<div className="space-y-6">
|
|
25
|
-
{/* Featured video */}
|
|
26
|
-
<div className="aspect-video w-full overflow-hidden" style={{ borderRadius: 'var(--pk-radius)' }}>
|
|
27
|
-
<iframe
|
|
28
|
-
src={`https://www.youtube.com/embed/${section.videos[0].videoId}`}
|
|
29
|
-
title={section.videos[0].title ?? 'Video'}
|
|
30
|
-
className="h-full w-full"
|
|
31
|
-
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
32
|
-
allowFullScreen
|
|
33
|
-
loading="lazy"
|
|
34
|
-
/>
|
|
35
|
-
</div>
|
|
36
|
-
{section.videos[0].title && (
|
|
37
|
-
<p className="text-sm font-medium" style={{ color: 'var(--pk-muted)' }}>
|
|
38
|
-
{section.videos[0].title}
|
|
39
|
-
</p>
|
|
40
|
-
)}
|
|
41
|
-
{/* Remaining videos */}
|
|
42
|
-
{section.videos.length > 1 && (
|
|
43
|
-
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
44
|
-
{section.videos.slice(1).map((video, i) => (
|
|
45
|
-
<div key={i}>
|
|
46
|
-
<div className="aspect-video overflow-hidden" style={{ borderRadius: 'var(--pk-radius)' }}>
|
|
47
|
-
<iframe
|
|
48
|
-
src={`https://www.youtube.com/embed/${video.videoId}`}
|
|
49
|
-
title={video.title ?? 'Video'}
|
|
50
|
-
className="h-full w-full"
|
|
51
|
-
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
52
|
-
allowFullScreen
|
|
53
|
-
loading="lazy"
|
|
54
|
-
/>
|
|
55
|
-
</div>
|
|
56
|
-
{video.title && (
|
|
57
|
-
<p className="mt-2 text-sm" style={{ color: 'var(--pk-muted)' }}>
|
|
58
|
-
{video.title}
|
|
59
|
-
</p>
|
|
60
|
-
)}
|
|
61
|
-
</div>
|
|
62
|
-
))}
|
|
63
|
-
</div>
|
|
64
|
-
)}
|
|
65
|
-
</div>
|
|
66
|
-
) : (
|
|
67
|
-
<div className={`grid gap-4 md:gap-6 ${isList ? 'grid-cols-1' : 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3'}`}>
|
|
68
|
-
{section.videos.map((video, i) => (
|
|
69
|
-
<div key={i}>
|
|
70
|
-
<div className="aspect-video overflow-hidden" style={{ borderRadius: 'var(--pk-radius)' }}>
|
|
71
|
-
<iframe
|
|
72
|
-
src={`https://www.youtube.com/embed/${video.videoId}`}
|
|
73
|
-
title={video.title ?? 'Video'}
|
|
74
|
-
className="h-full w-full"
|
|
75
|
-
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
76
|
-
allowFullScreen
|
|
77
|
-
loading="lazy"
|
|
78
|
-
/>
|
|
79
|
-
</div>
|
|
80
|
-
{video.title && (
|
|
81
|
-
<p className="mt-2 text-sm" style={{ color: 'var(--pk-muted)' }}>
|
|
82
|
-
{video.title}
|
|
83
|
-
</p>
|
|
84
|
-
)}
|
|
85
|
-
</div>
|
|
86
|
-
))}
|
|
87
|
-
{section.channelUrl && (
|
|
88
|
-
<a
|
|
89
|
-
href={section.channelUrl}
|
|
90
|
-
target="_blank"
|
|
91
|
-
rel="noopener noreferrer"
|
|
92
|
-
className="aspect-video overflow-hidden flex items-center justify-center border border-white/10 hover:border-white/30 transition-colors"
|
|
93
|
-
style={{ borderRadius: 'var(--pk-radius)', background: 'var(--pk-alt-bg, rgba(255,255,255,0.05))' }}
|
|
94
|
-
>
|
|
95
|
-
<span className="text-lg font-semibold" style={{ color: 'var(--pk-text)', fontFamily: 'var(--pk-font-heading)' }}>
|
|
96
|
-
Ver todos →
|
|
97
|
-
</span>
|
|
98
|
-
</a>
|
|
99
|
-
)}
|
|
100
|
-
</div>
|
|
101
|
-
)}
|
|
102
|
-
</div>
|
|
103
|
-
</section>
|
|
104
|
-
);
|
|
105
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export { default as HeroSection } from './HeroSection';
|
|
2
|
-
export { default as BioSection } from './BioSection';
|
|
3
|
-
export { default as GallerySection } from './GallerySection';
|
|
4
|
-
export { default as VideoGridSection } from './VideoGridSection';
|
|
5
|
-
export { default as SocialLinksSection } from './SocialLinksSection';
|
|
6
|
-
export { default as EmbedSection } from './EmbedSection';
|
|
7
|
-
export { default as ProjectSection } from './ProjectSection';
|
|
8
|
-
export { default as StatsSection } from './StatsSection';
|
|
9
|
-
export { default as VenueListSection } from './VenueListSection';
|
|
10
|
-
export { default as CTASection } from './CTASection';
|
|
11
|
-
export { default as TextSection } from './TextSection';
|
|
12
|
-
export { default as ProfileHeader } from './ProfileHeader';
|
|
13
|
-
export { default as LinksBlock } from './LinksBlock';
|
|
14
|
-
export { default as MusicReleasesBlock } from './MusicReleasesBlock';
|
|
15
|
-
export { default as TourDatesBlock } from './TourDatesBlock';
|
|
16
|
-
export { default as FeaturedBlock } from './FeaturedBlock';
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef, useState, type ReactNode } from 'react';
|
|
2
|
-
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Komi-style horizontal collection: a section title with prev/next controls and
|
|
6
|
-
* a snap-scrolling track. Mobile-first — the track scrolls by touch and the
|
|
7
|
-
* arrows nudge it by ~80% of the viewport. Arrows disable at the track ends.
|
|
8
|
-
*/
|
|
9
|
-
export function Carousel({ title, children }: { title?: string; children: ReactNode }) {
|
|
10
|
-
const trackRef = useRef<HTMLDivElement>(null);
|
|
11
|
-
const [atStart, setAtStart] = useState(true);
|
|
12
|
-
const [atEnd, setAtEnd] = useState(false);
|
|
13
|
-
|
|
14
|
-
const update = () => {
|
|
15
|
-
const el = trackRef.current;
|
|
16
|
-
if (!el) return;
|
|
17
|
-
setAtStart(el.scrollLeft <= 2);
|
|
18
|
-
setAtEnd(el.scrollLeft + el.clientWidth >= el.scrollWidth - 2);
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
update();
|
|
23
|
-
const el = trackRef.current;
|
|
24
|
-
if (!el) return;
|
|
25
|
-
el.addEventListener('scroll', update, { passive: true });
|
|
26
|
-
window.addEventListener('resize', update);
|
|
27
|
-
return () => {
|
|
28
|
-
el.removeEventListener('scroll', update);
|
|
29
|
-
window.removeEventListener('resize', update);
|
|
30
|
-
};
|
|
31
|
-
}, []);
|
|
32
|
-
|
|
33
|
-
const nudge = (dir: 1 | -1) => {
|
|
34
|
-
const el = trackRef.current;
|
|
35
|
-
if (!el) return;
|
|
36
|
-
el.scrollBy({ left: dir * el.clientWidth * 0.8, behavior: 'smooth' });
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const arrowStyle = (disabled: boolean) => ({
|
|
40
|
-
backgroundColor: 'rgba(255,255,255,0.06)',
|
|
41
|
-
color: 'var(--pk-text)',
|
|
42
|
-
opacity: disabled ? 0.25 : 1,
|
|
43
|
-
borderRadius: '9999px',
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
return (
|
|
47
|
-
<div>
|
|
48
|
-
{(title || true) && (
|
|
49
|
-
<div className="flex items-center justify-between mb-4 px-1">
|
|
50
|
-
{title && (
|
|
51
|
-
<h2
|
|
52
|
-
className="text-xl md:text-2xl font-bold"
|
|
53
|
-
style={{ fontFamily: 'var(--pk-font-heading)', color: 'var(--pk-text)' }}
|
|
54
|
-
>
|
|
55
|
-
{title}
|
|
56
|
-
</h2>
|
|
57
|
-
)}
|
|
58
|
-
<div className="flex gap-2">
|
|
59
|
-
<button
|
|
60
|
-
type="button"
|
|
61
|
-
aria-label="Anterior"
|
|
62
|
-
onClick={() => nudge(-1)}
|
|
63
|
-
disabled={atStart}
|
|
64
|
-
className="flex items-center justify-center w-9 h-9 transition-opacity"
|
|
65
|
-
style={arrowStyle(atStart)}
|
|
66
|
-
>
|
|
67
|
-
<ChevronLeft size={18} />
|
|
68
|
-
</button>
|
|
69
|
-
<button
|
|
70
|
-
type="button"
|
|
71
|
-
aria-label="Próximo"
|
|
72
|
-
onClick={() => nudge(1)}
|
|
73
|
-
disabled={atEnd}
|
|
74
|
-
className="flex items-center justify-center w-9 h-9 transition-opacity"
|
|
75
|
-
style={arrowStyle(atEnd)}
|
|
76
|
-
>
|
|
77
|
-
<ChevronRight size={18} />
|
|
78
|
-
</button>
|
|
79
|
-
</div>
|
|
80
|
-
</div>
|
|
81
|
-
)}
|
|
82
|
-
<div
|
|
83
|
-
ref={trackRef}
|
|
84
|
-
className="flex gap-3 overflow-x-auto snap-x snap-mandatory scrollbar-hide pb-1"
|
|
85
|
-
>
|
|
86
|
-
{children}
|
|
87
|
-
</div>
|
|
88
|
-
</div>
|
|
89
|
-
);
|
|
90
|
-
}
|