Refine homepage and restore hero
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { AppCard } from "@/components/ui/app-card";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type BentoCardProps = {
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
icon?: ReactNode;
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
export function BentoCard({
|
||||
eyebrow,
|
||||
title,
|
||||
description,
|
||||
icon,
|
||||
className,
|
||||
children,
|
||||
}: BentoCardProps) {
|
||||
return (
|
||||
<AppCard
|
||||
layer="single"
|
||||
interactive
|
||||
className={cn(
|
||||
"group h-full border-border/80 bg-surface-2 transition-all duration-300 hover:-translate-y-1 hover:border-border-strong hover:bg-background/80 hover:shadow-lg",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="flex h-full flex-col gap-6 p-6">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="space-y-3">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground">
|
||||
{eyebrow}
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-2xl font-semibold tracking-[-0.05em] text-foreground transition-colors duration-300 group-hover:text-foreground/95">
|
||||
{title}
|
||||
</h3>
|
||||
{description ? (
|
||||
<p className="text-sm leading-7 text-muted-foreground">{description}</p>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
{icon ? (
|
||||
<div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-nested border border-border/70 bg-background text-foreground transition-all duration-300 group-hover:-translate-y-0.5 group-hover:border-border-strong group-hover:shadow-sm">
|
||||
{icon}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</AppCard>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import {
|
||||
Activity,
|
||||
BriefcaseBusiness,
|
||||
Layers3,
|
||||
Mail,
|
||||
Sparkles,
|
||||
TimerReset,
|
||||
} from "lucide-react";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { SectionHeading } from "./section-heading";
|
||||
import { BentoCard } from "./bento-card";
|
||||
import type { BentoSectionCopy } from "./types";
|
||||
|
||||
type BentoGridSectionProps = {
|
||||
copy: BentoSectionCopy;
|
||||
};
|
||||
|
||||
export function BentoGridSection({ copy }: BentoGridSectionProps) {
|
||||
return (
|
||||
<section className="space-y-8">
|
||||
<SectionHeading
|
||||
eyebrow={copy.eyebrow}
|
||||
title={copy.title}
|
||||
description={copy.description}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4 xl:gap-6">
|
||||
<BentoCard
|
||||
eyebrow={copy.about.eyebrow}
|
||||
title={copy.about.title}
|
||||
description={copy.about.description}
|
||||
icon={<BriefcaseBusiness className="h-5 w-5" />}
|
||||
className="xl:col-span-2 xl:row-span-2"
|
||||
>
|
||||
<ul className="grid gap-3 sm:grid-cols-2">
|
||||
{copy.about.points.map((item) => (
|
||||
<li
|
||||
key={item}
|
||||
className="rounded-nested border border-border/70 bg-background px-4 py-3 text-sm text-foreground/88"
|
||||
>
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</BentoCard>
|
||||
|
||||
<BentoCard
|
||||
eyebrow={copy.coreStack.eyebrow}
|
||||
title={copy.coreStack.title}
|
||||
icon={<Layers3 className="h-5 w-5" />}
|
||||
>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{copy.coreStack.items.map((item) => (
|
||||
<Badge key={item} variant="outline" className="border-border/80 bg-background">
|
||||
{item}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</BentoCard>
|
||||
|
||||
<BentoCard
|
||||
eyebrow={copy.availability.eyebrow}
|
||||
title={copy.availability.title}
|
||||
description={copy.availability.description}
|
||||
icon={<Activity className="h-5 w-5" />}
|
||||
>
|
||||
<Badge variant="success" className="w-fit">
|
||||
{copy.availability.status}
|
||||
</Badge>
|
||||
</BentoCard>
|
||||
|
||||
<BentoCard
|
||||
eyebrow={copy.currentFocus.eyebrow}
|
||||
title={copy.currentFocus.title}
|
||||
icon={<Sparkles className="h-5 w-5" />}
|
||||
>
|
||||
<ul className="space-y-3 text-sm text-foreground/88">
|
||||
{copy.currentFocus.items.map((item) => (
|
||||
<li key={item} className="rounded-nested border border-border/70 bg-background px-4 py-3">
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</BentoCard>
|
||||
|
||||
<BentoCard
|
||||
eyebrow={copy.contact.eyebrow}
|
||||
title={copy.contact.title}
|
||||
description={copy.contact.description}
|
||||
icon={<Mail className="h-5 w-5" />}
|
||||
>
|
||||
<div className="rounded-nested border border-border/70 bg-background px-4 py-4">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground">
|
||||
{copy.contact.emailLabel}
|
||||
</p>
|
||||
<p className="mt-2 text-base font-medium text-foreground">
|
||||
{copy.contact.emailValue}
|
||||
</p>
|
||||
</div>
|
||||
</BentoCard>
|
||||
|
||||
<BentoCard
|
||||
eyebrow={copy.highlights.eyebrow}
|
||||
title={copy.highlights.title}
|
||||
icon={<TimerReset className="h-5 w-5" />}
|
||||
>
|
||||
<div className="grid gap-3 sm:grid-cols-3 xl:grid-cols-1">
|
||||
{copy.highlights.items.map((item) => (
|
||||
<div
|
||||
key={item.label}
|
||||
className="rounded-nested border border-border/70 bg-background px-4 py-4"
|
||||
>
|
||||
<p className="text-2xl font-semibold tracking-[-0.05em] text-foreground">
|
||||
{item.value}
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-muted-foreground">{item.label}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</BentoCard>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Bot, Database, Globe, ServerCog } from "lucide-react";
|
||||
|
||||
import { AppCard } from "@/components/ui/app-card";
|
||||
import { SectionHeading } from "./section-heading";
|
||||
import type { CapabilitiesSectionCopy } from "./types";
|
||||
|
||||
type CapabilitiesSectionProps = {
|
||||
copy: CapabilitiesSectionCopy;
|
||||
};
|
||||
|
||||
const capabilityIcons = [Globe, Database, ServerCog, Bot];
|
||||
|
||||
export function CapabilitiesSection({ copy }: CapabilitiesSectionProps) {
|
||||
return (
|
||||
<section className="space-y-8">
|
||||
<SectionHeading
|
||||
eyebrow={copy.eyebrow}
|
||||
title={copy.title}
|
||||
description={copy.description}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4 xl:gap-6">
|
||||
{copy.items.map((item, index) => {
|
||||
const Icon = capabilityIcons[index] ?? Globe;
|
||||
|
||||
return (
|
||||
<AppCard
|
||||
key={item.title}
|
||||
layer="single"
|
||||
interactive
|
||||
className="group border-border/80 bg-surface-2 transition-all duration-300 hover:-translate-y-1 hover:border-border-strong hover:shadow-panel"
|
||||
>
|
||||
<div className="flex h-full flex-col gap-5 p-6">
|
||||
<div className="flex h-11 w-11 items-center justify-center rounded-nested border border-border/70 bg-background text-foreground transition-transform duration-300 group-hover:-translate-y-0.5">
|
||||
<Icon className="h-5 w-5" />
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-xl font-semibold tracking-[-0.04em] text-foreground">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="text-sm leading-7 text-muted-foreground">{item.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</AppCard>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { ArrowUpRight, Mail } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { AppCard } from "@/components/ui/app-card";
|
||||
import type { ContactCtaSectionCopy } from "./types";
|
||||
|
||||
type ContactCtaSectionProps = {
|
||||
copy: ContactCtaSectionCopy;
|
||||
contactHref: string;
|
||||
};
|
||||
|
||||
export function ContactCtaSection({
|
||||
copy,
|
||||
contactHref,
|
||||
}: ContactCtaSectionProps) {
|
||||
return (
|
||||
<section>
|
||||
<AppCard className="overflow-hidden">
|
||||
<div className="grid gap-6 rounded-nested border border-border/70 bg-background p-6 sm:p-8 lg:grid-cols-[minmax(0,1fr)_auto] lg:items-end lg:p-10">
|
||||
<div className="space-y-4">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.16em] text-muted-foreground">
|
||||
{copy.eyebrow}
|
||||
</p>
|
||||
<h2 className="max-w-3xl text-balance text-3xl font-semibold tracking-[-0.05em] text-foreground sm:text-4xl lg:text-5xl">
|
||||
{copy.title}
|
||||
</h2>
|
||||
<p className="max-w-2xl text-sm leading-7 text-muted-foreground sm:text-base">
|
||||
{copy.description}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap gap-6 text-sm">
|
||||
<div className="space-y-1">
|
||||
<p className="font-semibold text-foreground">{copy.emailLabel}</p>
|
||||
<a href={`mailto:${copy.emailValue}`} className="text-muted-foreground hover:text-foreground">
|
||||
{copy.emailValue}
|
||||
</a>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p className="font-semibold text-foreground">{copy.availabilityLabel}</p>
|
||||
<p className="text-muted-foreground">{copy.availabilityValue}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-3 lg:justify-end">
|
||||
<Button asChild size="lg">
|
||||
<Link href={contactHref}>
|
||||
{copy.contactCta}
|
||||
<Mail className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
<Button asChild size="lg" variant="outline">
|
||||
<a href={copy.githubHref} target="_blank" rel="noreferrer">
|
||||
{copy.githubCta}
|
||||
<ArrowUpRight className="h-4 w-4" />
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</AppCard>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import { ArrowRight, MapPin, Sparkles } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { AppCard } from "@/components/ui/app-card";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { HeroCopy } from "./types";
|
||||
|
||||
type HeroSectionProps = {
|
||||
copy: HeroCopy;
|
||||
contactHref: string;
|
||||
portfolioHref: string;
|
||||
};
|
||||
|
||||
export function HeroSection({
|
||||
copy,
|
||||
contactHref,
|
||||
portfolioHref,
|
||||
}: HeroSectionProps) {
|
||||
return (
|
||||
<section className="relative pt-8 sm:pt-12 lg:pt-16">
|
||||
<div className="grid gap-6 lg:grid-cols-[minmax(0,1.15fr)_minmax(320px,0.85fr)] lg:items-end">
|
||||
<div className="space-y-8">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="w-fit border-border/80 bg-background/80 text-foreground shadow-xs"
|
||||
>
|
||||
<span className="mr-2 h-2 w-2 rounded-full bg-status-success" aria-hidden="true" />
|
||||
{copy.availability}
|
||||
</Badge>
|
||||
|
||||
<div className="space-y-5">
|
||||
<p className="text-sm font-semibold uppercase tracking-[0.18em] text-muted-foreground">
|
||||
{copy.name}
|
||||
</p>
|
||||
<h1 className="max-w-4xl text-balance text-5xl font-semibold tracking-[-0.07em] text-foreground sm:text-6xl lg:text-7xl">
|
||||
{copy.headline}
|
||||
</h1>
|
||||
<p className="max-w-2xl text-base leading-8 text-muted-foreground sm:text-lg">
|
||||
{copy.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Button asChild size="lg" className="shadow-sm">
|
||||
<Link href={contactHref}>
|
||||
{copy.primaryCta}
|
||||
<ArrowRight className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="border-border/80 bg-background/70 hover:border-border-strong hover:bg-background"
|
||||
>
|
||||
<Link href={portfolioHref}>{copy.secondaryCta}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{copy.highlights.map((item) => (
|
||||
<Badge
|
||||
key={item}
|
||||
variant="secondary"
|
||||
className="bg-secondary/70 text-foreground shadow-none"
|
||||
>
|
||||
{item}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AppCard className="overflow-hidden">
|
||||
<div className="grid gap-3">
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<MetricCard label={copy.roleLabel} value={copy.roleValue} />
|
||||
<MetricCard label={copy.locationLabel} value={copy.locationValue} icon={MapPin} />
|
||||
</div>
|
||||
<MetricCard label={copy.focusLabel} value={copy.focusValue} icon={Sparkles} strong />
|
||||
</div>
|
||||
</AppCard>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
type MetricCardProps = {
|
||||
label: string;
|
||||
value: string;
|
||||
strong?: boolean;
|
||||
icon?: typeof MapPin;
|
||||
};
|
||||
|
||||
function MetricCard({ label, value, strong = false, icon: Icon }: MetricCardProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-nested border border-border/70 bg-background p-5 transition-all duration-300",
|
||||
strong && "bg-surface-2",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground">
|
||||
{Icon ? <Icon className="h-3.5 w-3.5" /> : null}
|
||||
<span>{label}</span>
|
||||
</div>
|
||||
<p className="mt-3 text-xl font-semibold tracking-[-0.04em] text-foreground">
|
||||
{value}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { StackedMarqueeSection, type MarqueeRow } from "@/components/layout/stacked-marquee-section";
|
||||
import { AppCard } from "@/components/ui/app-card";
|
||||
import { SectionHeading } from "./section-heading";
|
||||
import type { MarqueeSectionCopy } from "./types";
|
||||
|
||||
type MarqueeSectionProps = {
|
||||
copy: MarqueeSectionCopy;
|
||||
rows: MarqueeRow[];
|
||||
};
|
||||
|
||||
export function MarqueeSection({ copy, rows }: MarqueeSectionProps) {
|
||||
return (
|
||||
<section className="space-y-8 overflow-hidden">
|
||||
<SectionHeading
|
||||
eyebrow={copy.eyebrow}
|
||||
title={copy.title}
|
||||
description={copy.description}
|
||||
/>
|
||||
|
||||
<AppCard className="overflow-hidden">
|
||||
<div className="rounded-nested border border-border/70 bg-background py-6">
|
||||
<StackedMarqueeSection rows={rows} className="py-0" />
|
||||
</div>
|
||||
</AppCard>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { AppCard } from "@/components/ui/app-card";
|
||||
import { SectionHeading } from "./section-heading";
|
||||
import type { ProcessSectionCopy } from "./types";
|
||||
|
||||
type ProcessSectionProps = {
|
||||
copy: ProcessSectionCopy;
|
||||
};
|
||||
|
||||
export function ProcessSection({ copy }: ProcessSectionProps) {
|
||||
return (
|
||||
<section className="space-y-8">
|
||||
<SectionHeading
|
||||
eyebrow={copy.eyebrow}
|
||||
title={copy.title}
|
||||
description={copy.description}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4 xl:gap-6">
|
||||
{copy.steps.map((step, index) => (
|
||||
<AppCard
|
||||
key={step.title}
|
||||
layer="single"
|
||||
interactive
|
||||
className="group border-border/80 bg-surface-2 transition-all duration-300 hover:-translate-y-1 hover:border-border-strong hover:shadow-panel"
|
||||
>
|
||||
<div className="flex h-full flex-col gap-5 p-6">
|
||||
<p className="text-sm font-semibold tracking-[0.18em] text-muted-foreground">
|
||||
{String(index + 1).padStart(2, "0")}
|
||||
</p>
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-2xl font-semibold tracking-[-0.05em] text-foreground">
|
||||
{step.title}
|
||||
</h3>
|
||||
<p className="text-sm leading-7 text-muted-foreground">{step.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</AppCard>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { ArrowUpRight } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { AppCard } from "@/components/ui/app-card";
|
||||
import { SectionHeading } from "./section-heading";
|
||||
import type { ProjectCardCopy, ProjectsSectionCopy } from "./types";
|
||||
|
||||
type ProjectsSectionProps = {
|
||||
copy: ProjectsSectionCopy;
|
||||
items: ProjectCardCopy[];
|
||||
};
|
||||
|
||||
export function ProjectsSection({ copy, items }: ProjectsSectionProps) {
|
||||
return (
|
||||
<section className="space-y-10">
|
||||
<SectionHeading
|
||||
eyebrow={copy.eyebrow}
|
||||
title={copy.title}
|
||||
description={copy.description}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 lg:grid-cols-3 xl:gap-6">
|
||||
{items.map((item, index) => (
|
||||
<AppCard
|
||||
key={`${item.title}-${index}`}
|
||||
layer="single"
|
||||
interactive
|
||||
className={[
|
||||
"group h-full border-border/80 bg-surface-2 transition-all duration-300 hover:-translate-y-1 hover:border-border-strong hover:bg-background/80 hover:shadow-lg",
|
||||
index === 0 ? "lg:col-span-2" : "",
|
||||
].join(" ")}
|
||||
>
|
||||
<div className="flex h-full flex-col gap-6 p-6">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{item.meta.map((metaItem) => (
|
||||
<Badge
|
||||
key={metaItem}
|
||||
variant="outline"
|
||||
className="border-border/80 bg-background text-muted-foreground transition-colors duration-300 group-hover:border-border-strong group-hover:text-foreground/80"
|
||||
>
|
||||
{metaItem}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-2xl font-semibold tracking-[-0.05em] text-foreground transition-colors duration-300 group-hover:text-foreground/95">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="text-sm leading-7 text-muted-foreground">{item.summary}</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground">
|
||||
{copy.stackLabel}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{item.stack.map((stackItem) => (
|
||||
<Badge
|
||||
key={stackItem}
|
||||
variant="secondary"
|
||||
className="bg-secondary/70 text-foreground transition-colors duration-300 group-hover:bg-secondary"
|
||||
>
|
||||
{stackItem}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-auto">
|
||||
<Link
|
||||
href={item.href}
|
||||
className="inline-flex items-center gap-2 text-sm font-medium text-foreground transition-all duration-200 group-hover:translate-x-0.5"
|
||||
>
|
||||
{item.cta}
|
||||
<ArrowUpRight className="h-4 w-4 transition-transform duration-200 group-hover:-translate-y-0.5 group-hover:translate-x-0.5" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</AppCard>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type SectionHeadingProps = {
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
description: string;
|
||||
align?: "left" | "center";
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function SectionHeading({
|
||||
eyebrow,
|
||||
title,
|
||||
description,
|
||||
align = "left",
|
||||
className,
|
||||
}: SectionHeadingProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"space-y-4",
|
||||
align === "center" && "mx-auto max-w-3xl text-center",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.16em] text-muted-foreground">
|
||||
{eyebrow}
|
||||
</p>
|
||||
<h2 className="max-w-4xl text-balance text-3xl font-semibold tracking-[-0.05em] text-foreground sm:text-4xl lg:text-5xl">
|
||||
{title}
|
||||
</h2>
|
||||
<p className="max-w-3xl text-sm leading-7 text-muted-foreground sm:text-[1.02rem]">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
export type HeroCopy = {
|
||||
availability: string;
|
||||
name: string;
|
||||
headline: string;
|
||||
description: string;
|
||||
primaryCta: string;
|
||||
secondaryCta: string;
|
||||
locationLabel: string;
|
||||
locationValue: string;
|
||||
roleLabel: string;
|
||||
roleValue: string;
|
||||
focusLabel: string;
|
||||
focusValue: string;
|
||||
highlights: string[];
|
||||
};
|
||||
|
||||
export type BentoSectionCopy = {
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
description: string;
|
||||
about: {
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
description: string;
|
||||
points: string[];
|
||||
};
|
||||
coreStack: {
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
items: string[];
|
||||
};
|
||||
availability: {
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
description: string;
|
||||
status: string;
|
||||
};
|
||||
currentFocus: {
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
items: string[];
|
||||
};
|
||||
contact: {
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
description: string;
|
||||
emailLabel: string;
|
||||
emailValue: string;
|
||||
};
|
||||
highlights: {
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
items: Array<{
|
||||
value: string;
|
||||
label: string;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
|
||||
export type MarqueeSectionCopy = {
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type ProjectCardCopy = {
|
||||
title: string;
|
||||
summary: string;
|
||||
stack: string[];
|
||||
href: string;
|
||||
cta: string;
|
||||
meta: string[];
|
||||
featured?: boolean;
|
||||
};
|
||||
|
||||
export type ProjectsSectionCopy = {
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
description: string;
|
||||
stackLabel: string;
|
||||
fallbackItems: Array<{
|
||||
title: string;
|
||||
summary: string;
|
||||
stack: string[];
|
||||
href: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type CapabilitiesSectionCopy = {
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
description: string;
|
||||
items: Array<{
|
||||
title: string;
|
||||
description: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type ProcessSectionCopy = {
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
description: string;
|
||||
steps: Array<{
|
||||
title: string;
|
||||
description: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type ContactCtaSectionCopy = {
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
description: string;
|
||||
contactCta: string;
|
||||
githubCta: string;
|
||||
emailLabel: string;
|
||||
emailValue: string;
|
||||
availabilityLabel: string;
|
||||
availabilityValue: string;
|
||||
githubHref: string;
|
||||
};
|
||||
Reference in New Issue
Block a user