@olwiba/ui 0.1.4 → 0.1.7

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.
@@ -26,7 +26,7 @@ export function Footer({
26
26
  const copyrightText = legal ?? `\u00A9 ${new Date().getFullYear()} ${brand.name}. All rights reserved.`;
27
27
 
28
28
  return (
29
- <footer className="overflow-hidden rounded-2xl border bg-muted text-foreground">
29
+ <footer className="overflow-hidden rounded-2xl border bg-card text-foreground">
30
30
  <div className="mx-auto max-w-7xl px-6 py-20 sm:py-24 lg:px-8">
31
31
  {/* Brand */}
32
32
  <div className="flex justify-center">
@@ -35,11 +35,7 @@ export function Footer({
35
35
  className: 'flex items-center gap-2',
36
36
  children: (
37
37
  <>
38
- {brand.logo && (
39
- <span className="flex h-8 w-8 items-center justify-center rounded-lg bg-foreground/10 text-foreground">
40
- {brand.logo}
41
- </span>
42
- )}
38
+ {brand.logo}
43
39
  <span className="text-lg font-semibold text-foreground">{brand.name}</span>
44
40
  </>
45
41
  ),
@@ -0,0 +1,71 @@
1
+ 'use client';
2
+
3
+ import type { LucideIcon } from 'lucide-react';
4
+ import { cn, useUIVariant } from '@olwiba/cn';
5
+ import { FeatureCard } from '../components/FeatureCard';
6
+ import { SectionTitle } from './SectionTitle';
7
+ import { StaggerChildren } from '../motion/StaggerChildren';
8
+ import { FadeIn } from '../motion/FadeIn';
9
+
10
+ export interface GroupedFeatureGroup {
11
+ label: string;
12
+ features: Array<{ icon: LucideIcon; title: string; description: string; href?: string }>;
13
+ }
14
+
15
+ export interface GroupedFeaturesSectionProps {
16
+ badge?: string;
17
+ title?: string;
18
+ description?: string;
19
+ groups: GroupedFeatureGroup[];
20
+ }
21
+
22
+ export function GroupedFeaturesSection({
23
+ badge = 'Features',
24
+ title = 'Everything you need to ship',
25
+ description,
26
+ groups,
27
+ }: GroupedFeaturesSectionProps) {
28
+ const mode = useUIVariant();
29
+ const sectionClasses = cn(
30
+ 'overflow-hidden bg-card',
31
+ mode === 'smooth' && 'rounded-3xl border',
32
+ mode === 'playful' && 'rounded-2xl border-primary/25 border',
33
+ !mode && 'rounded-2xl border',
34
+ );
35
+
36
+ return (
37
+ <section className={sectionClasses}>
38
+ <div className="px-6 py-14 sm:px-10 sm:py-20">
39
+ <div className="mx-auto max-w-4xl">
40
+ <SectionTitle title={title} description={description} badge={badge} />
41
+
42
+ <div className="mt-14 space-y-14">
43
+ {groups.map((group, i) => (
44
+ <div key={group.label}>
45
+ <FadeIn direction="up" delay={i * 60}>
46
+ <div className="mb-6 flex items-center gap-3">
47
+ <span className="text-sm font-semibold tracking-wide text-muted-foreground uppercase">
48
+ {group.label}
49
+ </span>
50
+ <div className="h-px flex-1 bg-border" />
51
+ </div>
52
+ </FadeIn>
53
+ <StaggerChildren className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
54
+ {group.features.map((feature) => (
55
+ <FeatureCard
56
+ key={feature.title}
57
+ icon={feature.icon}
58
+ title={feature.title}
59
+ description={feature.description}
60
+ href={feature.href}
61
+ />
62
+ ))}
63
+ </StaggerChildren>
64
+ </div>
65
+ ))}
66
+ </div>
67
+ </div>
68
+ </div>
69
+ </section>
70
+ );
71
+ }
@@ -40,11 +40,7 @@ export function Navbar({
40
40
  href: brandHref,
41
41
  children: (
42
42
  <span className="flex items-center gap-2 font-semibold">
43
- {brand.logo && (
44
- <span className="flex h-9 w-9 items-center justify-center rounded-xl bg-primary text-primary-foreground">
45
- {brand.logo}
46
- </span>
47
- )}
43
+ {brand.logo}
48
44
  <span>{brand.name}</span>
49
45
  </span>
50
46
  ),
@@ -100,11 +96,7 @@ export function Navbar({
100
96
  href: brandHref,
101
97
  children: (
102
98
  <span className="flex items-center gap-2 font-semibold">
103
- {brand.logo && (
104
- <span className="flex h-7 w-7 items-center justify-center rounded-lg bg-primary text-primary-foreground">
105
- {brand.logo}
106
- </span>
107
- )}
99
+ {brand.logo}
108
100
  <span>{brand.name}</span>
109
101
  </span>
110
102
  ),
@@ -57,13 +57,14 @@ export function NewsletterSection({
57
57
  <div className="relative px-6 py-14 sm:px-10 sm:py-20">
58
58
  <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,hsl(var(--primary)/0.1),transparent_60%)]" />
59
59
  <div className="relative mx-auto max-w-xl text-center">
60
- <div className="mb-4 inline-flex h-10 w-10 items-center justify-center rounded-2xl bg-primary/10 text-primary">
61
- <Mail className="size-5" />
60
+ <div className="mb-4 flex items-center justify-center gap-2">
61
+ <div className="inline-flex h-8 w-8 items-center justify-center rounded-xl bg-primary/10 text-primary">
62
+ <Mail className="size-4" />
63
+ </div>
64
+ <Badge variant="secondary">
65
+ {badge}
66
+ </Badge>
62
67
  </div>
63
-
64
- <Badge variant="secondary" className="mb-3">
65
- {badge}
66
- </Badge>
67
68
  <h2 className="text-balance text-3xl font-semibold tracking-tight sm:text-4xl">{title}</h2>
68
69
  <p className="mx-auto mt-4 max-w-sm text-pretty text-muted-foreground">{description}</p>
69
70
 
@@ -4,6 +4,7 @@ import * as React from 'react';
4
4
  import { Badge, Button, cn, useUIVariant } from '@olwiba/cn';
5
5
  import { PricingCard, type PricingFeature } from '../components/PricingCard';
6
6
  import { StaggerChildren } from '../motion/StaggerChildren';
7
+ import { CountdownTimer } from '../motion/CountdownTimer';
7
8
  import type { AppShellRenderLink } from '../app/AppShell';
8
9
 
9
10
  export interface PricingPlan {
@@ -13,18 +14,25 @@ export interface PricingPlan {
13
14
  description: string;
14
15
  cta: string;
15
16
  highlighted?: boolean;
17
+ disabled?: boolean;
16
18
  features: PricingFeature[];
19
+ /** Overrides computed price display (e.g. "$?" for community/reach-out tiers). */
20
+ priceDisplay?: string;
21
+ /** Overrides computed period display. */
22
+ periodDisplay?: string;
17
23
  }
18
24
 
19
25
  export interface PricingSectionProps {
20
26
  title?: string;
21
27
  description?: string;
22
- badge?: string;
28
+ badge?: React.ReactNode;
23
29
  plans: PricingPlan[];
24
30
  saveBadge?: string;
25
31
  isAuthenticated?: boolean;
26
32
  renderLink?: AppShellRenderLink;
27
33
  footnote?: string;
34
+ mode?: 'subscription' | 'one-time';
35
+ foundingDeadline?: string;
28
36
  }
29
37
 
30
38
  const defaultRenderLink: AppShellRenderLink = ({ href, children, className }) => (
@@ -40,15 +48,18 @@ export function PricingSection({
40
48
  isAuthenticated,
41
49
  renderLink = defaultRenderLink,
42
50
  footnote,
51
+ mode = 'subscription',
52
+ foundingDeadline,
43
53
  }: PricingSectionProps) {
44
54
  const [annual, setAnnual] = React.useState(false);
45
- const mode = useUIVariant()
55
+ const isOneTime = mode === 'one-time';
56
+ const uiMode = useUIVariant();
46
57
  const sectionClasses = cn(
47
58
  'overflow-hidden bg-card',
48
- mode === 'smooth' && 'rounded-3xl border',
49
- mode === 'playful' && 'rounded-2xl border-primary/25 border',
50
- !mode && 'rounded-2xl border',
51
- )
59
+ uiMode === 'smooth' && 'rounded-3xl border',
60
+ uiMode === 'playful' && 'rounded-2xl border-primary/25 border',
61
+ !uiMode && 'rounded-2xl border',
62
+ );
52
63
 
53
64
  return (
54
65
  <section className={sectionClasses}>
@@ -57,7 +68,9 @@ export function PricingSection({
57
68
  {/* Header */}
58
69
  <div className="text-center">
59
70
  {badge && (
60
- <Badge variant="secondary" className="mb-4">{badge}</Badge>
71
+ typeof badge === 'string'
72
+ ? <Badge variant="secondary" className="mb-4">{badge}</Badge>
73
+ : <div className="mb-4">{badge}</div>
61
74
  )}
62
75
  <h2 className="text-balance text-3xl font-semibold tracking-tight sm:text-4xl">
63
76
  {title}
@@ -68,51 +81,65 @@ export function PricingSection({
68
81
  </p>
69
82
  )}
70
83
 
71
- {/* Billing toggle */}
72
- <div className="mt-6 inline-flex items-center gap-3 rounded-full border bg-muted p-1">
73
- <Button
74
- variant="ghost"
75
- size="sm"
76
- onClick={() => setAnnual(false)}
77
- className={cn(
78
- 'rounded-full px-4 py-1.5 text-sm font-medium transition-colors',
79
- !annual ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground',
80
- )}
81
- >
82
- Monthly
83
- </Button>
84
- <Button
85
- variant="ghost"
86
- size="sm"
87
- onClick={() => setAnnual(true)}
88
- className={cn(
89
- 'flex items-center gap-2 rounded-full px-4 py-1.5 text-sm font-medium transition-colors',
90
- annual ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground',
91
- )}
92
- >
93
- Annual
94
- {saveBadge && (
95
- <Badge variant="secondary" className="text-xs">{saveBadge}</Badge>
96
- )}
97
- </Button>
98
- </div>
84
+ {/* Billing toggle — subscription mode only */}
85
+ {!isOneTime && (
86
+ <div className="mt-6 inline-flex items-center gap-3 rounded-full border bg-muted p-1">
87
+ <Button
88
+ variant="ghost"
89
+ size="sm"
90
+ onClick={() => setAnnual(false)}
91
+ className={cn(
92
+ 'rounded-full px-4 py-1.5 text-sm font-medium transition-colors',
93
+ !annual ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground',
94
+ )}
95
+ >
96
+ Monthly
97
+ </Button>
98
+ <Button
99
+ variant="ghost"
100
+ size="sm"
101
+ onClick={() => setAnnual(true)}
102
+ className={cn(
103
+ 'flex items-center gap-2 rounded-full px-4 py-1.5 text-sm font-medium transition-colors',
104
+ annual ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground',
105
+ )}
106
+ >
107
+ Annual
108
+ {saveBadge && (
109
+ <Badge variant="secondary" className="text-xs">{saveBadge}</Badge>
110
+ )}
111
+ </Button>
112
+ </div>
113
+ )}
99
114
  </div>
100
115
 
101
116
  {/* Plan cards */}
102
117
  <StaggerChildren className="mt-10 grid gap-4 lg:grid-cols-3">
103
118
  {plans.map((plan) => {
104
- const price = annual ? plan.annual : plan.monthly;
119
+ const rawPrice = isOneTime ? plan.monthly : (annual ? plan.annual : plan.monthly);
120
+ const price = plan.priceDisplay ?? `$${rawPrice}`;
121
+ const period = plan.periodDisplay ?? (isOneTime ? 'one-time' : (rawPrice > 0 ? '/mo' : ''));
122
+ const badge = plan.highlighted && foundingDeadline
123
+ ? (
124
+ <span className="inline-flex items-center rounded-full border bg-secondary px-2.5 py-0.5 text-xs font-semibold text-secondary-foreground">
125
+ <CountdownTimer deadline={foundingDeadline} compact />
126
+ </span>
127
+ )
128
+ : plan.highlighted
129
+ ? 'Founding member'
130
+ : undefined;
105
131
  return (
106
132
  <PricingCard
107
133
  key={plan.name}
108
134
  name={plan.name}
109
- price={`$${price}`}
110
- period={price > 0 ? '/mo' : ''}
135
+ price={price}
136
+ period={period}
111
137
  description={plan.description}
112
138
  features={plan.features}
113
139
  cta={plan.cta}
114
140
  highlighted={plan.highlighted}
115
- badge={plan.highlighted ? 'Most popular' : undefined}
141
+ disabled={plan.disabled}
142
+ badge={badge}
116
143
  />
117
144
  );
118
145
  })}
@@ -0,0 +1,86 @@
1
+ 'use client';
2
+
3
+ import { Check, X } from 'lucide-react';
4
+ import { cn, useUIVariant } from '@olwiba/cn';
5
+ import { SectionTitle } from './SectionTitle';
6
+ import { FadeIn } from '../motion/FadeIn';
7
+
8
+ export interface QualificationItem {
9
+ text: string;
10
+ }
11
+
12
+ export interface QualificationColumn {
13
+ heading: string;
14
+ variant: 'positive' | 'negative';
15
+ items: string[];
16
+ }
17
+
18
+ export interface QualificationSectionProps {
19
+ badge?: string;
20
+ title?: string;
21
+ description?: string;
22
+ columns: [QualificationColumn, QualificationColumn];
23
+ }
24
+
25
+ export function QualificationSection({
26
+ badge = 'Who it is for',
27
+ title = 'Is this for you?',
28
+ description,
29
+ columns,
30
+ }: QualificationSectionProps) {
31
+ const mode = useUIVariant();
32
+ const sectionClasses = cn(
33
+ 'overflow-hidden bg-card',
34
+ mode === 'smooth' && 'rounded-3xl border',
35
+ mode === 'playful' && 'rounded-2xl border-primary/25 border',
36
+ !mode && 'rounded-2xl border',
37
+ );
38
+
39
+ return (
40
+ <section className={sectionClasses}>
41
+ <div className="px-6 py-14 sm:px-10 sm:py-20">
42
+ <div className="mx-auto max-w-4xl">
43
+ <SectionTitle badge={badge} title={title} description={description} />
44
+
45
+ <FadeIn direction="up" className="mt-12 grid gap-6 sm:grid-cols-2">
46
+ {columns.map((col) => {
47
+ const isPositive = col.variant === 'positive';
48
+ const Icon = isPositive ? Check : X;
49
+
50
+ return (
51
+ <div
52
+ key={col.heading}
53
+ className={cn(
54
+ 'flex flex-col gap-4 rounded-2xl border p-6',
55
+ isPositive ? 'border-border bg-muted/30' : 'border-border bg-muted/10',
56
+ )}
57
+ >
58
+ <h3 className="text-sm font-semibold uppercase tracking-widest text-muted-foreground">
59
+ {col.heading}
60
+ </h3>
61
+ <ul className="flex flex-col gap-3">
62
+ {col.items.map((item) => (
63
+ <li key={item} className="flex items-start gap-3">
64
+ <span
65
+ className={cn(
66
+ 'mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full',
67
+ isPositive
68
+ ? 'bg-emerald-500/15 text-emerald-600 dark:text-emerald-400'
69
+ : 'bg-rose-500/15 text-rose-600 dark:text-rose-400',
70
+ )}
71
+ >
72
+ <Icon className="size-3" />
73
+ </span>
74
+ <span className="text-sm leading-relaxed text-foreground/80">{item}</span>
75
+ </li>
76
+ ))}
77
+ </ul>
78
+ </div>
79
+ );
80
+ })}
81
+ </FadeIn>
82
+ </div>
83
+ </div>
84
+ </section>
85
+ );
86
+ }
@@ -0,0 +1,97 @@
1
+ 'use client';
2
+
3
+ import { cn, useUIVariant } from '@olwiba/cn';
4
+ import { SectionTitle } from './SectionTitle';
5
+ import { FadeIn } from '../motion/FadeIn';
6
+
7
+ export interface StepItem {
8
+ emoji?: string;
9
+ title: string;
10
+ description: string;
11
+ }
12
+
13
+ export interface StepsSectionProps {
14
+ badge?: string;
15
+ title?: string;
16
+ description?: string;
17
+ steps: StepItem[];
18
+ }
19
+
20
+ export function StepsSection({
21
+ badge = 'How it works',
22
+ title = 'From zero to shipped',
23
+ description,
24
+ steps,
25
+ }: StepsSectionProps) {
26
+ const mode = useUIVariant();
27
+ const sectionClasses = cn(
28
+ 'overflow-hidden bg-card',
29
+ mode === 'smooth' && 'rounded-3xl border',
30
+ mode === 'playful' && 'rounded-2xl border-primary/25 border',
31
+ !mode && 'rounded-2xl border',
32
+ );
33
+
34
+ return (
35
+ <section className={sectionClasses}>
36
+ <div className="px-6 py-14 sm:px-10 sm:py-20">
37
+ <div className="mx-auto max-w-4xl">
38
+ <SectionTitle badge={badge} title={title} description={description} />
39
+
40
+ <div className="mt-12">
41
+ {/* Desktop: horizontal row */}
42
+ <div className="hidden sm:grid" style={{ gridTemplateColumns: `repeat(${steps.length}, 1fr)` }}>
43
+ {steps.map((step, i) => (
44
+ <FadeIn key={step.title} direction="up" delay={i * 80}>
45
+ <div className="relative flex flex-col items-center text-center px-4">
46
+ {/* Dashed connector to next step */}
47
+ {i < steps.length - 1 && (
48
+ <div
49
+ aria-hidden="true"
50
+ className="absolute top-6 left-1/2 w-full border-t border-dashed border-border"
51
+ />
52
+ )}
53
+ {/* Emoji badge (or fallback number) */}
54
+ <div className="relative z-10 mb-3 flex h-12 w-12 items-center justify-center rounded-full bg-muted text-2xl">
55
+ {step.emoji ?? (
56
+ <span className="text-sm font-semibold text-primary">{i + 1}</span>
57
+ )}
58
+ </div>
59
+ <h3 className="font-semibold text-foreground">{step.title}</h3>
60
+ <p className="mt-2 text-sm leading-relaxed text-muted-foreground">{step.description}</p>
61
+ </div>
62
+ </FadeIn>
63
+ ))}
64
+ </div>
65
+
66
+ {/* Mobile: vertical timeline */}
67
+ <div className="flex flex-col gap-0 sm:hidden">
68
+ {steps.map((step, i) => (
69
+ <FadeIn key={step.title} direction="up" delay={i * 80}>
70
+ <div className="relative flex gap-4 pb-8 last:pb-0">
71
+ {/* Vertical connector */}
72
+ {i < steps.length - 1 && (
73
+ <div
74
+ aria-hidden="true"
75
+ className="absolute left-[23px] top-14 bottom-0 w-px border-l border-dashed border-border"
76
+ />
77
+ )}
78
+ {/* Emoji badge (or fallback number) */}
79
+ <div className="relative z-10 flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-muted text-2xl">
80
+ {step.emoji ?? (
81
+ <span className="text-sm font-semibold text-primary">{i + 1}</span>
82
+ )}
83
+ </div>
84
+ <div className="pt-3">
85
+ <h3 className="font-semibold text-foreground">{step.title}</h3>
86
+ <p className="mt-1.5 text-sm leading-relaxed text-muted-foreground">{step.description}</p>
87
+ </div>
88
+ </div>
89
+ </FadeIn>
90
+ ))}
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ </section>
96
+ );
97
+ }
@@ -3,7 +3,20 @@
3
3
  import { Github, Linkedin, Twitter } from 'lucide-react';
4
4
  import { Avatar, AvatarFallback, AvatarImage, Badge } from '@olwiba/cn';
5
5
 
6
- const team = [
6
+ export interface TeamMember {
7
+ name: string;
8
+ role: string;
9
+ bio: string;
10
+ avatar?: string;
11
+ initials: string;
12
+ social?: {
13
+ twitter?: string | null;
14
+ github?: string | null;
15
+ linkedin?: string | null;
16
+ };
17
+ }
18
+
19
+ const defaultTeam: TeamMember[] = [
7
20
  {
8
21
  name: 'Olivia Reed',
9
22
  role: 'Co-Founder & CEO',
@@ -28,49 +41,37 @@ const team = [
28
41
  initials: 'PN',
29
42
  social: { twitter: '#', github: null, linkedin: '#' },
30
43
  },
31
- {
32
- name: 'Daniel Frost',
33
- role: 'Senior Engineer',
34
- bio: 'Accessibility and performance specialist. If it doesn\'t work with a keyboard, it\'s broken.',
35
- avatar: 'https://ui.shadcn.com/avatars/04.png',
36
- initials: 'DF',
37
- social: { twitter: null, github: '#', linkedin: '#' },
38
- },
39
- {
40
- name: 'Aisha Okafor',
41
- role: 'Developer Relations',
42
- bio: 'Writes docs, builds examples, and makes sure every engineer can ship their first block in under an hour.',
43
- avatar: 'https://ui.shadcn.com/avatars/05.png',
44
- initials: 'AO',
45
- social: { twitter: '#', github: '#', linkedin: '#' },
46
- },
47
- {
48
- name: 'Tom Halvorsen',
49
- role: 'Growth',
50
- bio: 'Turns happy users into loud advocates. Runs the community and keeps the changelog sharp.',
51
- avatar: 'https://ui.shadcn.com/avatars/06.png',
52
- initials: 'TH',
53
- social: { twitter: '#', github: null, linkedin: '#' },
54
- },
55
44
  ];
56
45
 
57
- export function TeamSection() {
46
+ export interface TeamSectionProps {
47
+ members?: TeamMember[];
48
+ badge?: string;
49
+ title?: string;
50
+ description?: string;
51
+ }
52
+
53
+ export function TeamSection({
54
+ members = defaultTeam,
55
+ badge = 'Team',
56
+ title = 'The people behind Olwiba',
57
+ description = 'A small team with deep experience in design systems, open source, and developer tooling.',
58
+ }: TeamSectionProps) {
58
59
  return (
59
60
  <section className="overflow-hidden rounded-2xl border bg-card">
60
61
  <div className="px-6 py-14 sm:px-10 sm:py-20">
61
62
  <div className="mx-auto max-w-5xl">
62
63
  <div className="text-center">
63
- <Badge variant="secondary" className="mb-4">Team</Badge>
64
+ <Badge variant="secondary" className="mb-4">{badge}</Badge>
64
65
  <h2 className="text-balance text-3xl font-semibold tracking-tight sm:text-4xl">
65
- The people behind Olwiba
66
+ {title}
66
67
  </h2>
67
68
  <p className="mx-auto mt-4 max-w-xl text-pretty text-muted-foreground">
68
- A small team with deep experience in design systems, open source, and developer tooling.
69
+ {description}
69
70
  </p>
70
71
  </div>
71
72
 
72
73
  <div className="mt-12 grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
73
- {team.map((member) => (
74
+ {members.map((member) => (
74
75
  <div key={member.name} className="rounded-2xl border bg-muted/40 p-5">
75
76
  <div className="flex items-start gap-4">
76
77
  <Avatar className="size-12 rounded-xl">
@@ -83,23 +84,25 @@ export function TeamSection() {
83
84
  </div>
84
85
  </div>
85
86
  <p className="mt-3 text-sm leading-relaxed text-muted-foreground">{member.bio}</p>
86
- <div className="mt-4 flex items-center gap-2">
87
- {member.social.twitter && (
88
- <a href={member.social.twitter} aria-label="Twitter" className="flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground">
89
- <Twitter className="size-3.5" />
90
- </a>
91
- )}
92
- {member.social.github && (
93
- <a href={member.social.github} aria-label="GitHub" className="flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground">
94
- <Github className="size-3.5" />
95
- </a>
96
- )}
97
- {member.social.linkedin && (
98
- <a href={member.social.linkedin} aria-label="LinkedIn" className="flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground">
99
- <Linkedin className="size-3.5" />
100
- </a>
101
- )}
102
- </div>
87
+ {member.social && (
88
+ <div className="mt-4 flex items-center gap-2">
89
+ {member.social.twitter && (
90
+ <a href={member.social.twitter} aria-label="Twitter" className="flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground">
91
+ <Twitter className="size-3.5" />
92
+ </a>
93
+ )}
94
+ {member.social.github && (
95
+ <a href={member.social.github} aria-label="GitHub" className="flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground">
96
+ <Github className="size-3.5" />
97
+ </a>
98
+ )}
99
+ {member.social.linkedin && (
100
+ <a href={member.social.linkedin} aria-label="LinkedIn" className="flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground">
101
+ <Linkedin className="size-3.5" />
102
+ </a>
103
+ )}
104
+ </div>
105
+ )}
103
106
  </div>
104
107
  ))}
105
108
  </div>