Profile Bio
Original · freebio card with links
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-bio.json"use client";
import type { KeyboardEvent } from "react";
import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
import { motion, useReducedMotion } from "motion/react";
type LinkKind = "site" | "writing" | "code" | "talk" | "mail";
type BioLink = {
id: string;
kind: LinkKind;
label: string;
detail: string;
href: string;
meta: string;
};
type StatItem = {
id: string;
value: string;
label: string;
};
const LINKS: readonly BioLink[] = [
{
id: "atlas",
kind: "site",
label: "Atlas Type Foundry",
detail: "Retail foundry — 14 families, variable-first",
href: "https://example.com/atlas-foundry",
meta: "atlas-foundry.type",
},
{
id: "notes",
kind: "writing",
label: "Kerning Notes",
detail: "Fortnightly letter on spacing, hinting, and licence traps",
href: "https://example.com/kerning-notes",
meta: "4,180 readers",
},
{
id: "metrics",
kind: "code",
label: "metrics-diff",
detail: "CLI that diffs font metrics between two builds",
href: "https://example.com/metrics-diff",
meta: "MIT · 2.1k stars",
},
{
id: "talk",
kind: "talk",
label: "\"Optical sizes are not a gimmick\"",
detail: "TypeTech Lisbon, 42 min, transcript included",
href: "https://example.com/optical-sizes-talk",
meta: "Recorded talk",
},
{
id: "mail",
kind: "mail",
label: "mira@atlas-foundry.type",
detail: "Custom type, licensing questions, or a second opinion",
href: "mailto:mira@atlas-foundry.type",
meta: "Replies in ~2 days",
},
];
const STATS: readonly StatItem[] = [
{ id: "s1", value: "14", label: "Families shipped" },
{ id: "s2", value: "9 yrs", label: "Drawing letters" },
{ id: "s3", value: "31", label: "Scripts covered" },
];
function LinkGlyph({ kind }: { kind: LinkKind }) {
const common = {
width: 16,
height: 16,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
strokeWidth: 1.7,
strokeLinecap: "round" as const,
strokeLinejoin: "round" as const,
"aria-hidden": true,
focusable: "false" as const,
};
if (kind === "site") {
return (
<svg {...common}>
<circle cx="12" cy="12" r="9" />
<path d="M3 12h18M12 3c2.5 2.7 2.5 15.3 0 18M12 3c-2.5 2.7-2.5 15.3 0 18" />
</svg>
);
}
if (kind === "writing") {
return (
<svg {...common}>
<path d="M4 5h16v14H4z" />
<path d="M8 9h8M8 13h8M8 17h4" />
</svg>
);
}
if (kind === "code") {
return (
<svg {...common}>
<path d="M9 7l-5 5 5 5M15 7l5 5-5 5" />
</svg>
);
}
if (kind === "talk") {
return (
<svg {...common}>
<path d="M12 3a3 3 0 0 1 3 3v6a3 3 0 0 1-6 0V6a3 3 0 0 1 3-3z" />
<path d="M5 11a7 7 0 0 0 14 0M12 18v3" />
</svg>
);
}
return (
<svg {...common}>
<path d="M3 6h18v12H3z" />
<path d="M3 7l9 6 9-6" />
</svg>
);
}
function ArrowGlyph() {
return (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
focusable="false"
>
<path d="M7 17L17 7M8 7h9v9" />
</svg>
);
}
function CopyGlyph({ done }: { done: boolean }) {
return done ? (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
focusable="false"
>
<path d="M4 12.5l5 5L20 6.5" />
</svg>
) : (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.7"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
focusable="false"
>
<rect x="9" y="9" width="11" height="11" rx="2" />
<path d="M15 5H6a1 1 0 0 0-1 1v9" />
</svg>
);
}
export default function ProfileBio() {
const reduced = useReducedMotion();
const headingId = useId();
const listLabelId = useId();
const [copied, setCopied] = useState(false);
const [available, setAvailable] = useState(true);
const [activeIndex, setActiveIndex] = useState(0);
const itemRefs = useRef<Array<HTMLAnchorElement | null>>([]);
const copyTimer = useRef<number | null>(null);
useEffect(() => {
return () => {
if (copyTimer.current !== null) window.clearTimeout(copyTimer.current);
};
}, []);
const handleCopy = useCallback(async () => {
const handle = "mira@atlas-foundry.type";
try {
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(handle);
} else {
throw new Error("clipboard unavailable");
}
} catch {
const ta = document.createElement("textarea");
ta.value = handle;
ta.setAttribute("readonly", "");
ta.style.position = "fixed";
ta.style.opacity = "0";
document.body.appendChild(ta);
ta.select();
try {
document.execCommand("copy");
} catch {
/* nothing else to try */
}
document.body.removeChild(ta);
}
setCopied(true);
if (copyTimer.current !== null) window.clearTimeout(copyTimer.current);
copyTimer.current = window.setTimeout(() => setCopied(false), 2000);
}, []);
const focusItem = useCallback((index: number) => {
const next = itemRefs.current[index];
if (next) {
setActiveIndex(index);
next.focus();
}
}, []);
const onListKeyDown = useCallback(
(event: KeyboardEvent<HTMLAnchorElement>, index: number) => {
const last = LINKS.length - 1;
if (event.key === "ArrowDown") {
event.preventDefault();
focusItem(index === last ? 0 : index + 1);
} else if (event.key === "ArrowUp") {
event.preventDefault();
focusItem(index === 0 ? last : index - 1);
} else if (event.key === "Home") {
event.preventDefault();
focusItem(0);
} else if (event.key === "End") {
event.preventDefault();
focusItem(last);
}
},
[focusItem],
);
const transition = useMemo(
() => ({ duration: reduced ? 0 : 0.5, ease: [0.22, 1, 0.36, 1] as const }),
[reduced],
);
return (
<section
aria-labelledby={headingId}
className="relative w-full overflow-hidden bg-slate-50 px-5 py-20 sm:py-28 dark:bg-zinc-950"
>
<style>{`
@keyframes pbio_pulse_ring {
0% { transform: scale(1); opacity: 0.55; }
70% { transform: scale(2.4); opacity: 0; }
100% { transform: scale(2.4); opacity: 0; }
}
@keyframes pbio_drift {
0% { transform: translate3d(0, 0, 0); }
50% { transform: translate3d(0, -14px, 0); }
100% { transform: translate3d(0, 0, 0); }
}
.pbio-ring { animation: pbio_pulse_ring 2.6s cubic-bezier(0.4, 0, 0.2, 1) infinite; }
.pbio-drift { animation: pbio_drift 14s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
.pbio-ring, .pbio-drift { animation: none !important; }
}
`}</style>
<div
aria-hidden="true"
className="pbio-drift pointer-events-none absolute -top-24 left-1/2 h-72 w-72 -translate-x-1/2 rounded-full bg-indigo-300/30 blur-3xl dark:bg-indigo-600/15"
/>
<div
aria-hidden="true"
className="pointer-events-none absolute inset-x-0 bottom-0 h-40 bg-gradient-to-t from-slate-50 to-transparent dark:from-zinc-950"
/>
<motion.div
initial={reduced ? false : { opacity: 0, y: 18 }}
whileInView={reduced ? undefined : { opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.25 }}
transition={transition}
className="relative mx-auto w-full max-w-2xl"
>
<article className="rounded-3xl border border-slate-200 bg-white shadow-xl shadow-slate-900/5 dark:border-zinc-800 dark:bg-zinc-900 dark:shadow-black/40">
<div className="relative h-28 overflow-hidden rounded-t-3xl bg-gradient-to-br from-indigo-500 via-violet-500 to-sky-400 dark:from-indigo-600 dark:via-violet-700 dark:to-sky-700">
<div
aria-hidden="true"
className="absolute inset-0 opacity-40 [background-image:radial-gradient(circle_at_1px_1px,rgba(255,255,255,0.55)_1px,transparent_0)] [background-size:14px_14px]"
/>
</div>
<div className="px-6 pb-8 sm:px-9 sm:pb-10">
<div className="-mt-12 flex items-end justify-between gap-4">
<div className="relative">
<div className="flex h-24 w-24 items-center justify-center rounded-2xl border-4 border-white bg-slate-900 text-3xl font-semibold tracking-tight text-white dark:border-zinc-900 dark:bg-zinc-100 dark:text-zinc-900">
MK
</div>
{available ? (
<span className="absolute -right-1 -bottom-1 flex h-5 w-5 items-center justify-center">
<span
aria-hidden="true"
className="pbio-ring absolute h-3 w-3 rounded-full bg-emerald-500"
/>
<span className="relative h-3.5 w-3.5 rounded-full border-2 border-white bg-emerald-500 dark:border-zinc-900" />
</span>
) : null}
</div>
<button
type="button"
onClick={() => setAvailable((v) => !v)}
aria-pressed={available}
className="mb-1 inline-flex items-center gap-2 rounded-full border border-slate-200 bg-slate-50 px-3 py-1.5 text-xs font-medium text-slate-600 transition-colors hover:bg-slate-100 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-zinc-700 dark:bg-zinc-800 dark:text-zinc-300 dark:hover:bg-zinc-700 dark:focus-visible:ring-offset-zinc-900"
>
<span
aria-hidden="true"
className={`h-1.5 w-1.5 rounded-full ${
available ? "bg-emerald-500" : "bg-amber-500"
}`}
/>
{available ? "Taking commissions" : "Booked till March"}
</button>
</div>
<header className="mt-5">
<h2
id={headingId}
className="text-2xl font-semibold tracking-tight text-slate-900 sm:text-3xl dark:text-zinc-50"
>
Mira Kalvāns
</h2>
<p className="mt-1 text-sm font-medium text-indigo-600 dark:text-indigo-400">
Type designer · Riga, Latvia (UTC+2)
</p>
<p className="mt-4 text-[0.95rem] leading-relaxed text-slate-600 dark:text-zinc-400">
I draw text faces that survive small sizes and bad printers. Ten
years of that started at a newspaper in Riga, where a headline set
in the wrong optical size could cost the front page. Now I run
Atlas, teach spacing to people who think it's kerning, and
keep an unreasonable archive of Latvian sign painting.
</p>
</header>
<dl className="mt-6 grid grid-cols-3 divide-x divide-slate-200 rounded-2xl border border-slate-200 bg-slate-50/70 py-4 dark:divide-zinc-800 dark:border-zinc-800 dark:bg-zinc-800/40">
{STATS.map((s) => (
<div key={s.id} className="px-3 text-center">
<dt className="sr-only">{s.label}</dt>
<dd>
<span className="block text-lg font-semibold tabular-nums text-slate-900 dark:text-zinc-50">
{s.value}
</span>
<span className="mt-0.5 block text-[0.7rem] font-medium uppercase tracking-wide text-slate-500 dark:text-zinc-500">
{s.label}
</span>
</dd>
</div>
))}
</dl>
<div className="mt-6 flex flex-wrap items-center gap-3">
<a
href="https://example.com/atlas-foundry/commissions"
target="_blank"
rel="noopener"
className="inline-flex flex-1 items-center justify-center gap-2 rounded-xl bg-slate-900 px-4 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-slate-800 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-zinc-100 dark:text-zinc-900 dark:hover:bg-white dark:focus-visible:ring-offset-zinc-900"
>
Start a commission
<ArrowGlyph />
</a>
<button
type="button"
onClick={handleCopy}
className="inline-flex items-center justify-center gap-2 rounded-xl border border-slate-200 bg-white px-4 py-2.5 text-sm font-semibold text-slate-700 transition-colors hover:bg-slate-50 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-zinc-700 dark:bg-zinc-900 dark:text-zinc-200 dark:hover:bg-zinc-800 dark:focus-visible:ring-offset-zinc-900"
>
<CopyGlyph done={copied} />
{copied ? "Copied" : "Copy email"}
</button>
<span aria-live="polite" className="sr-only">
{copied ? "Email address copied to clipboard" : ""}
</span>
</div>
<h3
id={listLabelId}
className="mt-9 text-[0.7rem] font-semibold uppercase tracking-[0.14em] text-slate-400 dark:text-zinc-500"
>
Where to find the work
</h3>
<ul
aria-labelledby={listLabelId}
className="mt-3 flex flex-col gap-1.5"
>
{LINKS.map((link, i) => {
const isMail = link.kind === "mail";
return (
<li key={link.id}>
<a
ref={(el) => {
itemRefs.current[i] = el;
}}
href={link.href}
{...(isMail ? {} : { target: "_blank", rel: "noopener" })}
tabIndex={i === activeIndex ? 0 : -1}
onFocus={() => setActiveIndex(i)}
onKeyDown={(e) => onListKeyDown(e, i)}
className="group flex items-center gap-3.5 rounded-2xl border border-transparent px-3 py-3 transition-colors hover:border-slate-200 hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:hover:border-zinc-800 dark:hover:bg-zinc-800/60 dark:focus-visible:ring-offset-zinc-900"
>
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl border border-slate-200 bg-white text-slate-500 transition-colors group-hover:border-indigo-200 group-hover:text-indigo-600 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-400 dark:group-hover:border-indigo-800 dark:group-hover:text-indigo-400">
<LinkGlyph kind={link.kind} />
</span>
<span className="min-w-0 flex-1">
<span className="block truncate text-sm font-semibold text-slate-900 dark:text-zinc-100">
{link.label}
</span>
<span className="mt-0.5 block truncate text-xs text-slate-500 dark:text-zinc-500">
{link.detail}
</span>
</span>
<span className="hidden shrink-0 text-[0.7rem] font-medium text-slate-400 sm:block dark:text-zinc-600">
{link.meta}
</span>
<span className="shrink-0 text-slate-300 transition-transform group-hover:translate-x-0.5 group-hover:text-indigo-500 dark:text-zinc-700 dark:group-hover:text-indigo-400">
<ArrowGlyph />
</span>
</a>
</li>
);
})}
</ul>
<p className="mt-7 border-t border-slate-200 pt-5 text-xs leading-relaxed text-slate-400 dark:border-zinc-800 dark:text-zinc-600">
Licences are per-family, perpetual, and readable without a lawyer.
Students get everything free — email from the .edu address and skip
the pitch.
</p>
</div>
</article>
</motion.div>
</section>
);
}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 Tabs
Originalprofile page with tabbed content

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.

