Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/components/bento/BentoCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { ReactNode } from 'react';

type BentoCardProps = {
children: ReactNode;
className?: string;
title?: string;
subtitle?: string;
id?: string;
};

export default function BentoCard({ children, className = '', title, subtitle, id }: BentoCardProps) {
return (
<section
id={id}
className={`relative overflow-hidden rounded-2xl border border-white/10 bg-white/[0.03] p-5 shadow-[0_0_0_1px_rgba(255,255,255,0.02)] backdrop-blur-sm transition duration-300 hover:border-neon/30 ${className}`.trim()}
>
<div className="pointer-events-none absolute inset-0 bg-gradient-to-br from-neon/[0.03] via-transparent to-electric/10" aria-hidden="true" />
<div className="relative z-10">
{title && <h3 className="text-lg font-semibold tracking-tight text-white">{title}</h3>}
{subtitle && <p className="mt-1 text-sm text-slate-400">{subtitle}</p>}
{children}
</div>
</section>
);
}
16 changes: 16 additions & 0 deletions src/components/bento/BentoGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { ReactNode } from 'react';

type BentoGridProps = {
children: ReactNode;
className?: string;
};

export default function BentoGrid({ children, className = '' }: BentoGridProps) {
return (
<div
className={`grid grid-cols-1 gap-4 md:grid-cols-6 lg:grid-cols-12 ${className}`.trim()}
>
{children}
</div>
);
}
55 changes: 55 additions & 0 deletions src/components/bento/CaseStudyCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import SmartLink from '../SmartLink';
import BentoCard from './BentoCard';

type CaseStudyCardProps = {
title: string;
description: string;
bullets: string[];
metric?: string;
caseStudyUrl?: string;
evidenceUrl?: string;
className?: string;
};

export default function CaseStudyCard({
title,
description,
bullets,
metric,
caseStudyUrl,
evidenceUrl,
className = '',
}: CaseStudyCardProps) {
return (
<BentoCard title={title} className={className}>
<p className="mt-3 text-sm leading-relaxed text-slate-300">{description}</p>
{metric && (
<p className="mt-3 rounded-lg border border-neon/30 bg-neon/10 px-3 py-2 text-sm font-medium text-neon">
{metric}
</p>
)}
<ul className="mt-3 space-y-2 text-sm text-slate-300">
{bullets.map((bullet) => (
<li key={bullet} className="flex gap-2">
<span className="mt-1 h-1.5 w-1.5 rounded-full bg-neon/80" aria-hidden="true" />
<span>{bullet}</span>
</li>
))}
</ul>
{(caseStudyUrl || evidenceUrl) && (
<div className="mt-4 flex flex-wrap gap-2">
{caseStudyUrl && (
<SmartLink href={caseStudyUrl} className="btn-primary rounded-lg px-3 py-2 text-xs font-medium">
Open Case Study
</SmartLink>
)}
{evidenceUrl && (
<SmartLink href={evidenceUrl} className="btn-secondary rounded-lg px-3 py-2 text-xs font-medium">
View Evidence
</SmartLink>
)}
</div>
)}
</BentoCard>
);
}
11 changes: 11 additions & 0 deletions src/components/bento/MetricBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
type MetricBadgeProps = {
label: string;
};

export default function MetricBadge({ label }: MetricBadgeProps) {
return (
<span className="inline-flex items-center rounded-full border border-neon/20 bg-neon/10 px-3 py-1 text-xs font-medium text-neon">
{label}
</span>
);
}
23 changes: 23 additions & 0 deletions src/components/bento/ProofCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import BentoCard from './BentoCard';

type ProofCardProps = {
title: string;
items: string[];
caption: string;
className?: string;
};

export default function ProofCard({ title, items, caption, className = '' }: ProofCardProps) {
return (
<BentoCard title={title} className={className}>
<ul className="mt-3 space-y-2 text-sm text-slate-200">
{items.map((item) => (
<li key={item} className="rounded-lg border border-white/10 bg-black/20 px-3 py-2 font-mono text-xs sm:text-sm">
{item}
</li>
))}
</ul>
<p className="mt-3 text-xs text-slate-400">{caption}</p>
</BentoCard>
);
}
21 changes: 21 additions & 0 deletions src/components/bento/SkillClusterCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import BentoCard from './BentoCard';

type SkillClusterCardProps = {
title: string;
skills: string[];
className?: string;
};

export default function SkillClusterCard({ title, skills, className = '' }: SkillClusterCardProps) {
return (
<BentoCard title={title} className={className}>
<div className="mt-3 flex flex-wrap gap-2">
{skills.map((skill) => (
<span key={skill} className="rounded-md border border-white/10 bg-black/25 px-2 py-1 text-xs text-slate-300">
{skill}
</span>
))}
</div>
</BentoCard>
);
}
Loading