Web InnoventixFreeCode

Profile Bio

Original · free

bio card with links

byWeb InnoventixReact + Tailwind
profilebioprofiles
Open

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
profile-bio.tsx
"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&apos;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

motion

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 quote

Similar components

Browse all →