Profile Tabs
Original · freeprofile page with tabbed content
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/profile-tabs.json"use client";
import { useCallback, useId, useMemo, useRef, useState } from "react";
import type { KeyboardEvent as ReactKeyboardEvent } from "react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
type TabId = "overview" | "projects" | "activity" | "stack";
type Tab = {
id: TabId;
label: string;
badge?: string;
};
type Project = {
name: string;
blurb: string;
role: string;
year: string;
metric: string;
stack: string[];
};
type ActivityItem = {
when: string;
title: string;
detail: string;
kind: "ship" | "write" | "talk" | "merge";
};
type StackGroup = {
heading: string;
note: string;
items: { name: string; level: number; years: string }[];
};
const TABS: Tab[] = [
{ id: "overview", label: "Overview" },
{ id: "projects", label: "Projects", badge: "4" },
{ id: "activity", label: "Activity", badge: "5" },
{ id: "stack", label: "Stack" },
];
const PROJECTS: Project[] = [
{
name: "Ledgerline",
blurb:
"Double-entry accounting engine for freelancers. Rewrote the reconciliation pass so a 40k-transaction import settles in under two seconds instead of timing out.",
role: "Lead engineer",
year: "2025",
metric: "40k txns / 1.8s",
stack: ["TypeScript", "Postgres", "Rust"],
},
{
name: "Северный",
blurb:
"Offline-first field notes app for Arctic survey crews. CRDT sync means two geologists can edit the same log 300km apart with no signal and merge cleanly at camp.",
role: "Founding engineer",
year: "2024",
metric: "0 merge conflicts in 11 months",
stack: ["React Native", "Automerge", "SQLite"],
},
{
name: "Kerning Club",
blurb:
"A type-spacing trainer that shows you your kerning next to a master's. Built the diff renderer that overlays both curves at 8x zoom without a canvas repaint storm.",
role: "Solo build",
year: "2024",
metric: "22k signups, no ad spend",
stack: ["Svelte", "WebGL", "Cloudflare"],
},
{
name: "Quietport",
blurb:
"Status page that refuses to lie. Pulls from real probes instead of a human toggling a switch, and publishes the raw probe history alongside every incident.",
role: "Maintainer",
year: "2023",
metric: "1.4k GitHub stars",
stack: ["Go", "ClickHouse", "HTMX"],
},
];
const ACTIVITY: ActivityItem[] = [
{
when: "2 days ago",
title: "Shipped Ledgerline 3.2",
detail:
"Multi-currency ledgers, plus an audit trail that survives a full database restore. Took four weeks longer than planned because rounding is a liar.",
kind: "ship",
},
{
when: "1 week ago",
title: "Wrote “Your ORM is a distributed system”",
detail:
"3,100 words on why lazy loading across a network boundary is a design decision, not a convenience.",
kind: "write",
},
{
when: "3 weeks ago",
title: "Merged 14 PRs into Quietport",
detail:
"Mostly community fixes for the probe scheduler. Two were from a contributor who had never written Go before — those took the longest and were worth it.",
kind: "merge",
},
{
when: "2 months ago",
title: "Talked at LocalFirst Berlin",
detail:
"Forty minutes on CRDT sync in places with no cell towers. The Q&A ran longer than the talk.",
kind: "talk",
},
{
when: "4 months ago",
title: "Deleted 6,000 lines from Kerning Club",
detail:
"Replaced the plugin architecture nobody used with one exported function. Bundle went from 190kb to 44kb.",
kind: "ship",
},
];
const STACK: StackGroup[] = [
{
heading: "Daily drivers",
note: "Things I'd reach for at 3am without reading the docs.",
items: [
{ name: "TypeScript", level: 95, years: "9 yrs" },
{ name: "Postgres", level: 88, years: "8 yrs" },
{ name: "React", level: 92, years: "9 yrs" },
],
},
{
heading: "Comfortable",
note: "Productive, but I still keep a tab open.",
items: [
{ name: "Rust", level: 64, years: "3 yrs" },
{ name: "Go", level: 71, years: "5 yrs" },
{ name: "WebGL", level: 58, years: "2 yrs" },
],
},
{
heading: "Learning in public",
note: "Actively bad at these and writing about it.",
items: [
{ name: "Zig", level: 28, years: "7 mo" },
{ name: "Elixir", level: 34, years: "1 yr" },
],
},
];
const KIND_STYLES: Record<ActivityItem["kind"], { dot: string; label: string }> = {
ship: { dot: "bg-emerald-500", label: "Release" },
write: { dot: "bg-violet-500", label: "Writing" },
talk: { dot: "bg-amber-500", label: "Talk" },
merge: { dot: "bg-sky-500", label: "Open source" },
};
function IconGithub() {
return (
<svg viewBox="0 0 24 24" aria-hidden="true" className="h-4 w-4" fill="currentColor">
<path d="M12 2C6.48 2 2 6.58 2 12.25c0 4.53 2.87 8.37 6.84 9.72.5.1.68-.22.68-.49l-.01-1.7c-2.78.62-3.37-1.37-3.37-1.37-.45-1.19-1.11-1.5-1.11-1.5-.91-.64.07-.63.07-.63 1 .07 1.53 1.06 1.53 1.06.89 1.57 2.34 1.12 2.91.86.09-.66.35-1.12.63-1.38-2.22-.26-4.56-1.14-4.56-5.07 0-1.12.39-2.03 1.03-2.75-.1-.26-.45-1.3.1-2.71 0 0 .84-.28 2.75 1.05a9.3 9.3 0 0 1 5 0c1.91-1.33 2.75-1.05 2.75-1.05.55 1.41.2 2.45.1 2.71.64.72 1.03 1.63 1.03 2.75 0 3.94-2.34 4.81-4.57 5.06.36.32.68.94.68 1.9l-.01 2.82c0 .27.18.6.69.49A10.03 10.03 0 0 0 22 12.25C22 6.58 17.52 2 12 2Z" />
</svg>
);
}
function IconLink() {
return (
<svg
viewBox="0 0 24 24"
aria-hidden="true"
className="h-4 w-4"
fill="none"
stroke="currentColor"
strokeWidth={1.8}
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M10 13a5 5 0 0 0 7.07 0l2.83-2.83a5 5 0 0 0-7.07-7.07L11.5 4.43" />
<path d="M14 11a5 5 0 0 0-7.07 0L4.1 13.83a5 5 0 0 0 7.07 7.07L12.5 19.5" />
</svg>
);
}
function IconPin() {
return (
<svg
viewBox="0 0 24 24"
aria-hidden="true"
className="h-4 w-4"
fill="none"
stroke="currentColor"
strokeWidth={1.8}
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M12 21s7-5.5 7-11a7 7 0 1 0-14 0c0 5.5 7 11 7 11Z" />
<circle cx="12" cy="10" r="2.5" />
</svg>
);
}
function IconClock() {
return (
<svg
viewBox="0 0 24 24"
aria-hidden="true"
className="h-4 w-4"
fill="none"
stroke="currentColor"
strokeWidth={1.8}
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="9" />
<path d="M12 7v5l3.2 1.9" />
</svg>
);
}
export default function ProfileTabs() {
const reduce = useReducedMotion();
const baseId = useId();
const [active, setActive] = useState<TabId>("overview");
const [copied, setCopied] = useState(false);
const tabRefs = useRef<Record<string, HTMLButtonElement | null>>({});
const copyTimer = useRef<number | null>(null);
const activeIndex = useMemo(() => TABS.findIndex((t) => t.id === active), [active]);
const focusTab = useCallback((id: TabId) => {
setActive(id);
tabRefs.current[id]?.focus();
}, []);
const onTabKeyDown = useCallback(
(event: ReactKeyboardEvent<HTMLButtonElement>) => {
const last = TABS.length - 1;
let next = -1;
if (event.key === "ArrowRight") next = activeIndex === last ? 0 : activeIndex + 1;
else if (event.key === "ArrowLeft") next = activeIndex === 0 ? last : activeIndex - 1;
else if (event.key === "Home") next = 0;
else if (event.key === "End") next = last;
if (next < 0) return;
event.preventDefault();
focusTab(TABS[next].id);
},
[activeIndex, focusTab],
);
const copyEmail = useCallback(() => {
const email = "mara@ledgerline.dev";
const done = () => {
setCopied(true);
if (copyTimer.current !== null) window.clearTimeout(copyTimer.current);
copyTimer.current = window.setTimeout(() => setCopied(false), 2000);
};
if (navigator.clipboard?.writeText) {
navigator.clipboard.writeText(email).then(done, done);
} else {
done();
}
}, []);
const panelMotion = reduce
? { initial: false as const, animate: {}, exit: {} }
: {
initial: { opacity: 0, y: 8 },
animate: { opacity: 1, y: 0 },
exit: { opacity: 0, y: -6 },
};
return (
<section className="relative w-full overflow-hidden bg-slate-50 px-4 py-20 text-slate-900 sm:px-6 sm:py-28 dark:bg-slate-950 dark:text-slate-100">
<style>{`
@keyframes ptabs-sheen {
0% { transform: translateX(-120%) skewX(-12deg); }
100% { transform: translateX(320%) skewX(-12deg); }
}
@keyframes ptabs-pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.35; transform: scale(1.9); }
}
.ptabs-sheen::after {
content: "";
position: absolute;
inset: 0;
width: 40%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.5), transparent);
animation: ptabs-sheen 4.5s ease-in-out infinite;
animation-delay: 1.2s;
pointer-events: none;
}
.ptabs-ping {
animation: ptabs-pulse 2.4s ease-out infinite;
}
@media (prefers-reduced-motion: reduce) {
.ptabs-sheen::after,
.ptabs-ping {
animation: none !important;
}
}
`}</style>
<div
aria-hidden="true"
className="pointer-events-none absolute inset-x-0 top-0 h-72 bg-[radial-gradient(60%_100%_at_50%_0%,rgba(99,102,241,0.14),transparent_70%)] dark:bg-[radial-gradient(60%_100%_at_50%_0%,rgba(129,140,248,0.16),transparent_70%)]"
/>
<div className="relative mx-auto w-full max-w-4xl">
{/* Header card */}
<div className="rounded-3xl border border-slate-200/80 bg-white shadow-[0_1px_0_0_rgba(15,23,42,0.04),0_24px_60px_-30px_rgba(15,23,42,0.35)] dark:border-slate-800 dark:bg-slate-900 dark:shadow-[0_24px_60px_-30px_rgba(0,0,0,0.9)]">
<div className="relative h-28 overflow-hidden rounded-t-3xl bg-gradient-to-br from-indigo-500 via-violet-500 to-sky-400 sm:h-32">
<div className="ptabs-sheen absolute inset-0" />
<div
aria-hidden="true"
className="absolute inset-0 opacity-30 [background-image:linear-gradient(rgba(255,255,255,0.35)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.35)_1px,transparent_1px)] [background-size:28px_28px]"
/>
</div>
<div className="px-5 pb-6 sm:px-8 sm:pb-8">
<div className="-mt-12 flex flex-col gap-5 sm:-mt-14 sm:flex-row sm:items-end sm:justify-between">
<div className="flex items-end gap-4">
<div className="relative">
<div
aria-hidden="true"
className="flex h-24 w-24 select-none items-center justify-center rounded-2xl border-4 border-white bg-gradient-to-br from-slate-800 to-slate-600 text-2xl font-semibold tracking-tight text-white sm:h-28 sm:w-28 dark:border-slate-900 dark:from-slate-200 dark:to-slate-400 dark:text-slate-900"
>
MV
</div>
<span className="absolute -bottom-1 -right-1 flex h-6 w-6 items-center justify-center rounded-full border-4 border-white bg-white dark:border-slate-900 dark:bg-slate-900">
<span className="relative flex h-2.5 w-2.5">
<span className="ptabs-ping absolute inline-flex h-full w-full rounded-full bg-emerald-500" />
<span className="relative inline-flex h-2.5 w-2.5 rounded-full bg-emerald-500" />
</span>
<span className="sr-only">Currently available for work</span>
</span>
</div>
<div className="pb-1">
<h2 className="text-xl font-semibold tracking-tight text-slate-900 sm:text-2xl dark:text-slate-50">
Mara Volkov
</h2>
<p className="mt-0.5 text-sm text-slate-600 dark:text-slate-400">
Systems-leaning product engineer
</p>
<div className="mt-2 flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-slate-500 dark:text-slate-500">
<span className="inline-flex items-center gap-1.5">
<IconPin />
Tallinn, Estonia
</span>
<span className="inline-flex items-center gap-1.5">
<IconClock />
Replies in ~1 day
</span>
</div>
</div>
</div>
<div className="flex flex-wrap items-center gap-2">
<button
type="button"
onClick={copyEmail}
aria-live="polite"
className="inline-flex h-10 items-center justify-center rounded-xl bg-slate-900 px-4 text-sm font-medium text-white transition-colors hover:bg-slate-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-white dark:focus-visible:ring-offset-slate-900"
>
{copied ? "Email copied" : "Copy email"}
</button>
<a
href="https://github.com"
target="_blank"
rel="noopener"
className="inline-flex h-10 w-10 items-center justify-center rounded-xl border border-slate-200 text-slate-600 transition-colors hover:border-slate-300 hover:text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-slate-700 dark:text-slate-400 dark:hover:border-slate-600 dark:hover:text-slate-100 dark:focus-visible:ring-offset-slate-900"
>
<IconGithub />
<span className="sr-only">Mara Volkov on GitHub (opens in a new tab)</span>
</a>
<a
href="https://example.com"
target="_blank"
rel="noopener"
className="inline-flex h-10 w-10 items-center justify-center rounded-xl border border-slate-200 text-slate-600 transition-colors hover:border-slate-300 hover:text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-slate-700 dark:text-slate-400 dark:hover:border-slate-600 dark:hover:text-slate-100 dark:focus-visible:ring-offset-slate-900"
>
<IconLink />
<span className="sr-only">Personal site (opens in a new tab)</span>
</a>
</div>
</div>
{/* Tablist */}
<div className="mt-7 border-b border-slate-200 dark:border-slate-800">
<div
role="tablist"
aria-label="Profile sections"
className="-mb-px flex gap-1 overflow-x-auto"
>
{TABS.map((tab) => {
const selected = tab.id === active;
return (
<button
key={tab.id}
ref={(node) => {
tabRefs.current[tab.id] = node;
}}
type="button"
role="tab"
id={`${baseId}-tab-${tab.id}`}
aria-selected={selected}
aria-controls={`${baseId}-panel-${tab.id}`}
tabIndex={selected ? 0 : -1}
onClick={() => setActive(tab.id)}
onKeyDown={onTabKeyDown}
className={`relative shrink-0 rounded-t-lg px-3.5 py-3 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-indigo-500 sm:px-4 ${
selected
? "text-slate-900 dark:text-slate-50"
: "text-slate-500 hover:text-slate-800 dark:text-slate-400 dark:hover:text-slate-200"
}`}
>
<span className="inline-flex items-center gap-2">
{tab.label}
{tab.badge ? (
<span
className={`rounded-md px-1.5 py-0.5 text-[10px] font-semibold tabular-nums transition-colors ${
selected
? "bg-indigo-100 text-indigo-700 dark:bg-indigo-500/20 dark:text-indigo-300"
: "bg-slate-100 text-slate-500 dark:bg-slate-800 dark:text-slate-400"
}`}
>
{tab.badge}
</span>
) : null}
</span>
{selected ? (
<motion.span
layoutId={`${baseId}-underline`}
className="absolute inset-x-2 -bottom-px h-0.5 rounded-full bg-indigo-600 dark:bg-indigo-400"
transition={
reduce
? { duration: 0 }
: { type: "spring", stiffness: 420, damping: 34 }
}
/>
) : null}
</button>
);
})}
</div>
</div>
{/* Panels */}
<div className="pt-6">
<AnimatePresence mode="wait" initial={false}>
<motion.div
key={active}
{...panelMotion}
transition={reduce ? { duration: 0 } : { duration: 0.22, ease: "easeOut" }}
role="tabpanel"
id={`${baseId}-panel-${active}`}
aria-labelledby={`${baseId}-tab-${active}`}
tabIndex={0}
className="rounded-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500"
>
{active === "overview" ? <OverviewPanel /> : null}
{active === "projects" ? <ProjectsPanel /> : null}
{active === "activity" ? <ActivityPanel /> : null}
{active === "stack" ? <StackPanel reduce={Boolean(reduce)} /> : null}
</motion.div>
</AnimatePresence>
</div>
</div>
</div>
<p className="mt-4 text-center text-xs text-slate-500 dark:text-slate-500">
Use <kbd className="rounded border border-slate-300 px-1 py-0.5 font-sans text-[10px] dark:border-slate-700">←</kbd>{" "}
<kbd className="rounded border border-slate-300 px-1 py-0.5 font-sans text-[10px] dark:border-slate-700">→</kbd>{" "}
on the tabs to move between sections.
</p>
</div>
</section>
);
}
function OverviewPanel() {
return (
<div className="grid gap-6 sm:grid-cols-5">
<div className="sm:col-span-3">
<h3 className="text-sm font-semibold text-slate-900 dark:text-slate-100">
What I actually do
</h3>
<div className="mt-3 space-y-3 text-sm leading-relaxed text-slate-600 dark:text-slate-400">
<p>
I take products that work on a laptop and make them work at three in the morning under
load, offline, or on a decade-old Android. Most of that is unglamorous: query plans,
retry semantics, and deleting the abstraction someone added for a case that never came.
</p>
<p>
Nine years in, split roughly evenly between fintech ledgers and offline-first tooling.
I've never enjoyed a rewrite, but I've done four and I know when one is the honest
answer.
</p>
<p>
Right now I'm taking on one contract at a time — usually a team that has a performance
or data-integrity problem they can name but can't isolate.
</p>
</div>
</div>
<div className="sm:col-span-2">
<h3 className="text-sm font-semibold text-slate-900 dark:text-slate-100">At a glance</h3>
<dl className="mt-3 grid grid-cols-2 gap-3">
{[
{ k: "Years shipping", v: "9" },
{ k: "Prod incidents caused", v: "3" },
{ k: "Open source repos", v: "27" },
{ k: "Timezone", v: "UTC+2" },
].map((stat) => (
<div
key={stat.k}
className="rounded-xl border border-slate-200 bg-slate-50/70 p-3 dark:border-slate-800 dark:bg-slate-800/40"
>
<dt className="text-[11px] uppercase tracking-wide text-slate-500 dark:text-slate-500">
{stat.k}
</dt>
<dd className="mt-1 text-lg font-semibold tabular-nums text-slate-900 dark:text-slate-100">
{stat.v}
</dd>
</div>
))}
</dl>
<div className="mt-3 rounded-xl border border-emerald-200 bg-emerald-50 p-3 dark:border-emerald-500/25 dark:bg-emerald-500/10">
<p className="text-xs font-medium text-emerald-800 dark:text-emerald-300">
Open to contract work from September 2026
</p>
<p className="mt-1 text-xs text-emerald-700/80 dark:text-emerald-400/80">
Two-week minimum. Remote, or on-site in the Baltics.
</p>
</div>
</div>
</div>
);
}
function ProjectsPanel() {
return (
<ul className="grid gap-3 sm:grid-cols-2">
{PROJECTS.map((project) => (
<li key={project.name}>
<article className="group h-full rounded-2xl border border-slate-200 bg-white p-4 transition-colors hover:border-slate-300 dark:border-slate-800 dark:bg-slate-900 dark:hover:border-slate-700">
<div className="flex items-baseline justify-between gap-3">
<h3 className="text-sm font-semibold text-slate-900 dark:text-slate-100">
{project.name}
</h3>
<span className="shrink-0 text-xs tabular-nums text-slate-400 dark:text-slate-600">
{project.year}
</span>
</div>
<p className="mt-0.5 text-xs font-medium text-indigo-600 dark:text-indigo-400">
{project.role}
</p>
<p className="mt-2 text-sm leading-relaxed text-slate-600 dark:text-slate-400">
{project.blurb}
</p>
<p className="mt-3 inline-flex rounded-lg bg-slate-100 px-2 py-1 text-xs font-medium tabular-nums text-slate-700 dark:bg-slate-800 dark:text-slate-300">
{project.metric}
</p>
<ul className="mt-3 flex flex-wrap gap-1.5">
{project.stack.map((tech) => (
<li
key={tech}
className="rounded-md border border-slate-200 px-1.5 py-0.5 text-[11px] text-slate-500 dark:border-slate-700 dark:text-slate-400"
>
{tech}
</li>
))}
</ul>
</article>
</li>
))}
</ul>
);
}
function ActivityPanel() {
return (
<ol className="relative space-y-5 border-l border-slate-200 pl-6 dark:border-slate-800">
{ACTIVITY.map((item) => (
<li key={item.title} className="relative">
<span
aria-hidden="true"
className={`absolute -left-[26.5px] top-1.5 h-2.5 w-2.5 rounded-full ring-4 ring-white dark:ring-slate-900 ${KIND_STYLES[item.kind].dot}`}
/>
<div className="flex flex-wrap items-center gap-2">
<h3 className="text-sm font-semibold text-slate-900 dark:text-slate-100">
{item.title}
</h3>
<span className="rounded-md bg-slate-100 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-slate-500 dark:bg-slate-800 dark:text-slate-400">
{KIND_STYLES[item.kind].label}
</span>
</div>
<p className="mt-0.5 text-xs text-slate-400 dark:text-slate-600">{item.when}</p>
<p className="mt-1.5 text-sm leading-relaxed text-slate-600 dark:text-slate-400">
{item.detail}
</p>
</li>
))}
</ol>
);
}
function StackPanel({ reduce }: { reduce: boolean }) {
return (
<div className="space-y-6">
{STACK.map((group) => (
<div key={group.heading}>
<h3 className="text-sm font-semibold text-slate-900 dark:text-slate-100">
{group.heading}
</h3>
<p className="mt-0.5 text-xs text-slate-500 dark:text-slate-500">{group.note}</p>
<ul className="mt-3 space-y-2.5">
{group.items.map((item) => (
<li key={item.name} className="flex items-center gap-4">
<span className="w-24 shrink-0 text-sm text-slate-700 dark:text-slate-300">
{item.name}
</span>
<div
role="meter"
aria-label={`${item.name} proficiency`}
aria-valuenow={item.level}
aria-valuemin={0}
aria-valuemax={100}
aria-valuetext={`${item.level} out of 100`}
className="h-1.5 flex-1 overflow-hidden rounded-full bg-slate-200 dark:bg-slate-800"
>
<motion.div
initial={reduce ? false : { scaleX: 0 }}
animate={{ scaleX: item.level / 100 }}
transition={reduce ? { duration: 0 } : { duration: 0.6, ease: "easeOut" }}
style={{ transformOrigin: "left" }}
className="h-full w-full rounded-full bg-gradient-to-r from-indigo-500 to-violet-500"
/>
</div>
<span className="w-12 shrink-0 text-right text-xs tabular-nums text-slate-400 dark:text-slate-600">
{item.years}
</span>
</li>
))}
</ul>
</div>
))}
</div>
);
}Dependencies
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
Profile Header
Originalprofile header with cover and avatar

Profile Settings
Originalaccount settings panel

Profile User Card
Originalcompact user profile card

Profile Stats
Originalprofile with stats and badges

Profile Edit
Originaledit profile form

Profile Team
Originalteam member profile grid

Profile Social
Originalsocial profile with follow

Profile Bio
Originalbio card with links

Profile Cover
Originallarge cover profile hero

Spotlight Hero
OriginalA centred hero with a soft radial spotlight, badge and dual call-to-action.

Split Hero
OriginalA two-column hero pairing a headline and CTAs with a product mock and social proof.

Gradient Spotlight Hero
OriginalA minimal centred hero with a soft gradient-mesh backdrop, announcement pill and dual call-to-action buttons.

