Portfolio — Hero / Intro Header Variants
Three drop-in portfolio hero headers for the same fictional designer, stacked so you can compare and copy the one you like. A centered name with role, one-liner and copy-email CTA; a split layout pairing intro text with an animated CSS avatar shape; and a big kinetic name with a self-typing role rotator. Vanilla JS powers the rotating titles, a cursor-follow gradient glow and a clipboard copy-email button with toast feedback. Neutral Inter base, accessible, responsive.
MCP
Code
/* =================================================================
Portfolio — Hero / Intro Header Variants
Neutral base palette, "Inter". Three drop-in headers.
================================================================= */
:root {
--ink: #16151a;
--ink-soft: #46434f;
--ink-mute: #76737f;
--line: #e7e5ea;
--line-strong: #d6d3dc;
--paper: #ffffff;
--paper-2: #faf9fb;
--paper-3: #f3f1f6;
--accent: #5b54ff;
--accent-ink: #4640e6;
--accent-soft: #eeedff;
--invert-bg: #16151a;
--invert-ink: #f6f5f9;
--invert-mute: #9c98a8;
--radius-sm: 10px;
--radius: 16px;
--radius-lg: 26px;
--shadow-sm: 0 1px 2px rgba(22, 21, 26, 0.06);
--shadow-md: 0 14px 40px -18px rgba(22, 21, 26, 0.28);
--shadow-lg: 0 36px 90px -40px rgba(22, 21, 26, 0.45);
--ease: cubic-bezier(0.22, 1, 0.36, 1);
--maxw: 1120px;
--font: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
--font-display: "Inter Tight", "Inter", system-ui, sans-serif;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
font-family: var(--font);
font-size: 16px;
line-height: 1.55;
color: var(--ink);
background:
radial-gradient(120% 70% at 50% -10%, var(--paper-3), transparent 60%),
var(--paper-2);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
a {
color: inherit;
text-decoration: none;
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
:focus-visible {
outline: 3px solid var(--accent);
outline-offset: 3px;
border-radius: 4px;
}
.skip-link {
position: absolute;
left: 16px;
top: -64px;
z-index: 50;
padding: 10px 16px;
background: var(--ink);
color: #fff;
border-radius: var(--radius-sm);
font-weight: 600;
transition: top 0.18s var(--ease);
}
.skip-link:focus {
top: 16px;
}
/* ---------------- Page chrome ---------------- */
.page-head {
border-bottom: 1px solid var(--line);
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(8px);
}
.page-head__inner {
max-width: var(--maxw);
margin: 0 auto;
padding: 22px 24px;
display: flex;
align-items: center;
gap: 16px;
}
.page-head__mark {
font-size: 1.6rem;
color: var(--accent);
line-height: 1;
}
.page-head__title {
margin: 0;
font-family: var(--font-display);
font-size: clamp(1.1rem, 2.6vw, 1.4rem);
font-weight: 800;
letter-spacing: -0.02em;
}
.page-head__sub {
margin: 2px 0 0;
color: var(--ink-mute);
font-size: 0.92rem;
}
.stack {
max-width: var(--maxw);
margin: 0 auto;
padding: 36px 24px 24px;
display: flex;
flex-direction: column;
gap: 36px;
}
.page-foot {
max-width: var(--maxw);
margin: 0 auto;
padding: 8px 24px 56px;
color: var(--ink-mute);
font-size: 0.85rem;
}
/* ---------------- Shared hero shell ---------------- */
.hero {
position: relative;
border: 1px solid var(--line);
border-radius: var(--radius-lg);
background: var(--paper);
box-shadow: var(--shadow-md);
overflow: hidden;
}
.hero__tag {
position: absolute;
top: 18px;
left: 18px;
z-index: 3;
display: inline-block;
padding: 6px 12px;
border-radius: 999px;
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--ink-soft);
background: var(--paper-3);
border: 1px solid var(--line);
}
.hero__eyebrow {
display: inline-block;
margin: 0 0 18px;
padding: 7px 14px;
border-radius: 999px;
font-size: 0.78rem;
font-weight: 600;
letter-spacing: 0.01em;
color: var(--accent-ink);
background: var(--accent-soft);
}
.hero__eyebrow--invert {
color: var(--invert-ink);
background: rgba(255, 255, 255, 0.1);
}
.hero__name {
margin: 0;
font-family: var(--font-display);
font-weight: 800;
letter-spacing: -0.035em;
line-height: 0.98;
font-size: clamp(2.6rem, 8vw, 4.6rem);
}
.hero__role {
margin: 16px auto 0;
font-size: clamp(1.1rem, 2.6vw, 1.45rem);
font-weight: 600;
color: var(--ink);
}
.hero__line {
margin: 14px auto 0;
max-width: 46ch;
color: var(--ink-soft);
font-size: 1.02rem;
}
/* ---------------- Buttons ---------------- */
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 12px 20px;
border-radius: 999px;
font-family: var(--font);
font-size: 0.95rem;
font-weight: 600;
cursor: pointer;
border: 1px solid transparent;
transition:
transform 0.18s var(--ease),
background 0.18s var(--ease),
color 0.18s var(--ease),
border-color 0.18s var(--ease),
box-shadow 0.18s var(--ease);
}
.btn:active {
transform: translateY(1px) scale(0.99);
}
.btn--solid {
background: var(--ink);
color: #fff;
box-shadow: var(--shadow-sm);
}
.btn--solid:hover {
background: var(--accent);
box-shadow: 0 10px 24px -10px var(--accent);
}
.btn--ghost {
background: var(--paper);
color: var(--ink);
border-color: var(--line-strong);
}
.btn--ghost:hover {
border-color: var(--accent);
color: var(--accent-ink);
background: var(--accent-soft);
}
.btn--invert {
background: var(--invert-ink);
color: var(--invert-bg);
}
.btn--invert:hover {
background: var(--accent);
color: #fff;
}
.btn--invert-ghost {
background: transparent;
color: var(--invert-ink);
border-color: rgba(255, 255, 255, 0.28);
}
.btn--invert-ghost:hover {
border-color: var(--invert-ink);
background: rgba(255, 255, 255, 0.08);
}
.copy-email {
font-variant-numeric: tabular-nums;
}
.copy-email.is-copied {
border-color: #19a974;
color: #0f7a55;
background: #e7f8ef;
}
.copy-email__icon {
font-size: 1rem;
}
/* =================================================================
VARIANT 1 — Centered
================================================================= */
.hero--centered {
padding: clamp(56px, 9vw, 96px) 24px clamp(48px, 7vw, 80px);
text-align: center;
background:
radial-gradient(80% 120% at 50% 0%, var(--paper-2), var(--paper) 70%);
}
.hero__center {
max-width: 680px;
margin: 0 auto;
}
.hero__cta {
margin-top: 30px;
display: flex;
flex-wrap: wrap;
gap: 12px;
justify-content: center;
}
.hero__meta {
margin-top: 34px;
display: flex;
flex-wrap: wrap;
gap: 10px 22px;
justify-content: center;
color: var(--ink-mute);
font-size: 0.9rem;
font-weight: 500;
}
/* =================================================================
VARIANT 2 — Split (text / avatar)
================================================================= */
.hero--split {
--glow-x: 80%;
--glow-y: 30%;
background:
radial-gradient(
340px 340px at var(--glow-x) var(--glow-y),
rgba(91, 84, 255, 0.1),
transparent 70%
),
var(--paper);
padding: clamp(28px, 5vw, 56px);
padding-top: clamp(56px, 8vw, 72px);
}
.hero__split {
display: grid;
grid-template-columns: 1.1fr 0.9fr;
align-items: center;
gap: clamp(24px, 5vw, 56px);
}
.hero__eyebrow.hero__eyebrow,
.hero__split-text .hero__eyebrow {
margin-bottom: 12px;
}
.hero__name--left {
text-align: left;
}
.hero__role--left {
margin-left: 0;
text-align: left;
color: var(--ink-soft);
}
.hero__line--left {
margin-left: 0;
text-align: left;
max-width: 40ch;
}
.hero__cta--left {
justify-content: flex-start;
margin-top: 28px;
}
/* Avatar shape */
.hero__split-art {
position: relative;
display: grid;
place-items: center;
min-height: 320px;
}
.avatar {
position: relative;
width: clamp(220px, 30vw, 300px);
aspect-ratio: 1;
}
.avatar__blob {
position: absolute;
inset: 0;
border-radius: 46% 54% 58% 42% / 52% 44% 56% 48%;
background:
conic-gradient(from 140deg, var(--accent), #8a84ff, #b9b4ff, var(--accent));
box-shadow: var(--shadow-lg);
animation: blob-morph 14s ease-in-out infinite;
}
@keyframes blob-morph {
0%, 100% {
border-radius: 46% 54% 58% 42% / 52% 44% 56% 48%;
transform: rotate(0deg);
}
50% {
border-radius: 60% 40% 42% 58% / 44% 58% 42% 56%;
transform: rotate(6deg);
}
}
.avatar__face {
position: absolute;
inset: 10% 10% 0;
width: 80%;
height: auto;
filter: drop-shadow(0 18px 24px rgba(22, 21, 26, 0.25));
}
.avatar__chip {
position: absolute;
padding: 7px 13px;
border-radius: 999px;
background: var(--paper);
border: 1px solid var(--line);
box-shadow: var(--shadow-md);
font-size: 0.8rem;
font-weight: 600;
color: var(--ink);
white-space: nowrap;
animation: chip-float 6s ease-in-out infinite;
}
.avatar__chip--1 {
top: 6%;
left: -14%;
animation-delay: 0s;
}
.avatar__chip--2 {
top: 44%;
right: -16%;
animation-delay: 1.4s;
}
.avatar__chip--3 {
bottom: 4%;
left: -8%;
animation-delay: 2.8s;
}
@keyframes chip-float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-9px); }
}
/* =================================================================
VARIANT 3 — Kinetic name + rotator
================================================================= */
.hero--kinetic {
background:
radial-gradient(120% 120% at 100% 0%, #2c2942, var(--invert-bg) 55%);
color: var(--invert-ink);
border-color: #2a2733;
padding: clamp(48px, 8vw, 88px) clamp(24px, 5vw, 56px);
text-align: center;
}
.hero--kinetic .hero__tag {
color: var(--invert-mute);
background: rgba(255, 255, 255, 0.06);
border-color: rgba(255, 255, 255, 0.12);
}
.hero__kinetic {
max-width: 820px;
margin: 0 auto;
}
.kinetic-name {
margin: 0;
font-family: var(--font-display);
font-weight: 900;
letter-spacing: -0.045em;
line-height: 0.86;
font-size: clamp(3.2rem, 13vw, 8.5rem);
display: flex;
flex-direction: column;
align-items: center;
}
.kinetic-name__word {
position: relative;
display: inline-block;
background: linear-gradient(180deg, #ffffff, #c8c4e6);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
transition: transform 0.3s var(--ease);
cursor: default;
}
.kinetic-name__word:nth-child(2) {
color: transparent;
-webkit-text-stroke: 2px rgba(255, 255, 255, 0.55);
background: none;
-webkit-background-clip: border-box;
}
/* hover ghost duplicate using ::before */
.kinetic-name__word::before {
content: attr(data-text);
position: absolute;
inset: 0;
-webkit-text-stroke: 0;
background: linear-gradient(180deg, var(--accent), #8a84ff);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
opacity: 0;
transform: translate(6px, 6px);
transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
pointer-events: none;
}
.kinetic-name__word:hover {
transform: translate(-3px, -3px);
}
.kinetic-name__word:hover::before {
opacity: 0.55;
transform: translate(10px, 10px);
}
.rotator {
margin: 26px 0 0;
font-size: clamp(1.1rem, 3vw, 1.6rem);
font-weight: 500;
color: var(--invert-mute);
min-height: 1.6em;
}
.rotator__static {
color: var(--invert-ink);
}
.rotator__dynamic {
display: inline-flex;
align-items: baseline;
}
.rotator__text {
color: #fff;
font-weight: 700;
background: linear-gradient(90deg, #b9b4ff, var(--accent));
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
.rotator__caret {
display: inline-block;
width: 3px;
height: 1.05em;
margin-left: 3px;
border-radius: 2px;
background: var(--accent);
transform: translateY(0.12em);
animation: caret-blink 1s steps(1) infinite;
}
@keyframes caret-blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
.hero__cta--invert {
justify-content: center;
margin-top: 34px;
}
/* ---------------- Toast ---------------- */
.toast {
position: fixed;
left: 50%;
bottom: 28px;
transform: translate(-50%, 24px);
z-index: 60;
max-width: min(90vw, 360px);
padding: 13px 20px;
border-radius: 999px;
background: var(--ink);
color: #fff;
font-size: 0.9rem;
font-weight: 600;
box-shadow: var(--shadow-lg);
opacity: 0;
pointer-events: none;
transition:
opacity 0.25s var(--ease),
transform 0.25s var(--ease);
}
.toast.is-visible {
opacity: 1;
transform: translate(-50%, 0);
}
/* ---------------- Responsive ---------------- */
@media (max-width: 820px) {
.hero__split {
grid-template-columns: 1fr;
text-align: center;
}
.hero__name--left,
.hero__role--left,
.hero__line--left {
text-align: center;
}
.hero__line--left {
margin-left: auto;
margin-right: auto;
}
.hero__cta--left {
justify-content: center;
}
.hero__split-art {
order: -1;
min-height: 280px;
}
}
@media (max-width: 520px) {
.stack {
padding: 24px 16px 16px;
gap: 24px;
}
.page-head__inner {
padding: 18px 16px;
}
.hero__tag {
position: static;
margin: 0 0 18px;
}
.hero--centered,
.hero--kinetic {
padding-left: 18px;
padding-right: 18px;
}
.hero--split {
padding: 20px 18px;
}
.btn {
width: 100%;
justify-content: center;
}
.avatar__chip--1 { left: 0; }
.avatar__chip--2 { right: 0; }
.avatar__chip--3 { left: 0; }
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
}
.rotator__caret {
animation: none;
opacity: 1;
}
}/* =================================================================
Portfolio — Hero / Intro Header Variants
Vanilla JS: role rotator, cursor-follow glow, copy-email.
================================================================= */
(function () {
"use strict";
/* ---------- Toast helper ---------- */
var toastEl = document.getElementById("toast");
var toastTimer = null;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("is-visible");
window.clearTimeout(toastTimer);
toastTimer = window.setTimeout(function () {
toastEl.classList.remove("is-visible");
}, 2200);
}
/* ---------- Copy email ---------- */
function copyText(text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
return navigator.clipboard.writeText(text);
}
// Fallback for non-secure contexts.
return new Promise(function (resolve, reject) {
try {
var ta = document.createElement("textarea");
ta.value = text;
ta.setAttribute("readonly", "");
ta.style.position = "absolute";
ta.style.left = "-9999px";
document.body.appendChild(ta);
ta.select();
document.execCommand("copy");
document.body.removeChild(ta);
resolve();
} catch (err) {
reject(err);
}
});
}
var copyButtons = document.querySelectorAll(".copy-email");
Array.prototype.forEach.call(copyButtons, function (btn) {
var label = btn.querySelector(".copy-email__label");
var originalLabel = label ? label.textContent : "";
btn.addEventListener("click", function () {
var email = btn.getAttribute("data-email") || "";
copyText(email).then(
function () {
btn.classList.add("is-copied");
if (label) label.textContent = "Copied ✓";
toast("Email copied — " + email);
window.setTimeout(function () {
btn.classList.remove("is-copied");
if (label) label.textContent = originalLabel;
}, 1800);
},
function () {
toast("Copy failed — " + email);
}
);
});
});
/* ---------- Role rotator (Variant 3) ---------- */
var rotatorTextEl = document.getElementById("rotator-text");
if (rotatorTextEl) {
var roles = [
"product designer",
"systems thinker",
"prototyper",
"design-systems lead",
"front-end tinkerer",
"problem framer"
];
var reduceMotion =
window.matchMedia &&
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (reduceMotion) {
// Simple cross-fade swap, no per-character typing.
var rIdx = 0;
window.setInterval(function () {
rIdx = (rIdx + 1) % roles.length;
rotatorTextEl.textContent = roles[rIdx];
}, 2600);
} else {
var idx = 0;
var charPos = roles[0].length; // start fully typed on first word
var deleting = false;
var TYPE_MS = 70;
var DELETE_MS = 38;
var HOLD_MS = 1500;
function tick() {
var word = roles[idx];
if (!deleting) {
charPos++;
rotatorTextEl.textContent = word.slice(0, charPos);
if (charPos >= word.length) {
deleting = true;
return window.setTimeout(tick, HOLD_MS);
}
return window.setTimeout(tick, TYPE_MS);
}
charPos--;
rotatorTextEl.textContent = word.slice(0, Math.max(charPos, 0));
if (charPos <= 0) {
deleting = false;
idx = (idx + 1) % roles.length;
return window.setTimeout(tick, 320);
}
return window.setTimeout(tick, DELETE_MS);
}
// Kick off after the initial hold so the first word reads cleanly.
window.setTimeout(function () {
deleting = true;
tick();
}, HOLD_MS);
}
}
/* ---------- Cursor-follow glow (Variant 2) ---------- */
var followEls = document.querySelectorAll("[data-cursor-follow]");
var supportsHover =
!window.matchMedia || window.matchMedia("(hover: hover)").matches;
if (supportsHover) {
Array.prototype.forEach.call(followEls, function (el) {
el.addEventListener("pointermove", function (e) {
var rect = el.getBoundingClientRect();
var x = ((e.clientX - rect.left) / rect.width) * 100;
var y = ((e.clientY - rect.top) / rect.height) * 100;
el.style.setProperty("--glow-x", x.toFixed(1) + "%");
el.style.setProperty("--glow-y", y.toFixed(1) + "%");
});
el.addEventListener("pointerleave", function () {
el.style.setProperty("--glow-x", "80%");
el.style.setProperty("--glow-y", "30%");
});
});
}
/* ---------- Résumé link is illustrative ---------- */
var resumeLink = document.querySelector('a[href="#resume"]');
if (resumeLink) {
resumeLink.addEventListener("click", function (e) {
e.preventDefault();
toast("Résumé download is illustrative in this demo");
});
}
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Portfolio — Hero / Intro Header Variants</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=Inter+Tight:wght@600;700;800;900&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip-link" href="#variant-1">Skip to content</a>
<header class="page-head">
<div class="page-head__inner">
<span class="page-head__mark" aria-hidden="true">◖</span>
<div>
<h1 class="page-head__title">Hero / Intro Header Variants</h1>
<p class="page-head__sub">
Three drop-in portfolio headers for the same person — pick one, paste it in.
</p>
</div>
</div>
</header>
<main class="stack">
<!-- ============================================================= -->
<!-- VARIANT 1 — Centered name + role + one-line + CTA -->
<!-- ============================================================= -->
<section
id="variant-1"
class="hero hero--centered"
aria-labelledby="v1-name"
>
<span class="hero__tag">Variant 01 · Centered</span>
<div class="hero__center">
<p class="hero__eyebrow">Product Designer · Available June ’26</p>
<h2 class="hero__name" id="v1-name">Maya Okafor</h2>
<p class="hero__role">
I design calm, accountable interfaces for complex products.
</p>
<p class="hero__line">
Currently shaping the design system at a fintech in Lisbon —
previously fixed onboarding for two million users at a mapping
startup. I care about clarity, motion that means something, and
shipping.
</p>
<div class="hero__cta">
<a class="btn btn--solid" href="#work">View work</a>
<button
class="btn btn--ghost copy-email"
type="button"
data-email="maya@okafor.design"
>
<span class="copy-email__icon" aria-hidden="true">✉</span>
<span class="copy-email__label">maya@okafor.design</span>
</button>
</div>
<ul class="hero__meta" role="list">
<li>📍 Lisbon, PT</li>
<li>↳ Design systems</li>
<li>★ 9 yrs shipping</li>
</ul>
</div>
</section>
<!-- ============================================================= -->
<!-- VARIANT 2 — Split: left text / right avatar shape -->
<!-- ============================================================= -->
<section
id="variant-2"
class="hero hero--split"
aria-labelledby="v2-name"
data-cursor-follow
>
<span class="hero__tag">Variant 02 · Split</span>
<div class="hero__split">
<div class="hero__split-text">
<p class="hero__eyebrow">Hello, I’m</p>
<h2 class="hero__name hero__name--left" id="v2-name">
Maya<br />Okafor
</h2>
<p class="hero__role hero__role--left">
Senior Product Designer & design-systems lead.
</p>
<p class="hero__line hero__line--left">
I turn fuzzy problems into shippable, accessible interfaces — from
first sketch to the last pixel of the production component.
</p>
<div class="hero__cta hero__cta--left">
<a class="btn btn--solid" href="#work">See projects</a>
<a class="btn btn--ghost" href="#resume">Download résumé</a>
</div>
</div>
<div class="hero__split-art">
<div class="avatar" aria-hidden="true">
<div class="avatar__blob"></div>
<svg class="avatar__face" viewBox="0 0 200 200" role="img">
<defs>
<linearGradient id="skin" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#f6d2b8" />
<stop offset="1" stop-color="#d99b76" />
</linearGradient>
</defs>
<circle cx="100" cy="86" r="46" fill="url(#skin)" />
<path
d="M54 78c0-30 24-50 46-50s46 18 46 48c-6-14-20-22-34-22-6 8-18 12-30 10-10-2-20 2-28 14z"
fill="#2a2320"
/>
<circle cx="84" cy="86" r="4" fill="#2a2320" />
<circle cx="116" cy="86" r="4" fill="#2a2320" />
<path
d="M86 104q14 12 28 0"
fill="none"
stroke="#2a2320"
stroke-width="4"
stroke-linecap="round"
/>
<path
d="M60 158c8-22 26-34 40-34s32 12 40 34z"
fill="#5b54ff"
/>
</svg>
</div>
<span class="avatar__chip avatar__chip--1">Figma</span>
<span class="avatar__chip avatar__chip--2">Systems</span>
<span class="avatar__chip avatar__chip--3">Prototyping</span>
</div>
</div>
</section>
<!-- ============================================================= -->
<!-- VARIANT 3 — Big kinetic name + typed role rotator -->
<!-- ============================================================= -->
<section
id="variant-3"
class="hero hero--kinetic"
aria-labelledby="v3-name"
>
<span class="hero__tag">Variant 03 · Kinetic</span>
<div class="hero__kinetic">
<p class="hero__eyebrow hero__eyebrow--invert">Portfolio · 2026</p>
<h2 class="kinetic-name" id="v3-name" aria-label="Maya Okafor">
<span class="kinetic-name__word" data-text="Maya">Maya</span>
<span class="kinetic-name__word" data-text="Okafor">Okafor</span>
</h2>
<p class="rotator">
<span class="rotator__static">I’m a</span>
<span class="rotator__dynamic" aria-live="polite">
<span class="rotator__text" id="rotator-text">product designer</span
><span class="rotator__caret" aria-hidden="true"></span>
</span>
</p>
<div class="hero__cta hero__cta--invert">
<a class="btn btn--invert" href="#work">Explore the work</a>
<button
class="btn btn--invert-ghost copy-email"
type="button"
data-email="maya@okafor.design"
>
<span class="copy-email__icon" aria-hidden="true">✉</span>
<span class="copy-email__label">Copy email</span>
</button>
</div>
</div>
</section>
</main>
<footer class="page-foot">
<p>
Illustrative portfolio — fictional person and projects. Built with HTML,
CSS & vanilla JS.
</p>
</footer>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Hero / Intro Header Variants
Three self-contained portfolio headers for the same person, stacked on one page so you can audition each style side by side and lift the one that fits. They share a neutral Inter palette and the same content — name, role, one-liner and calls to action — re-skinned three ways: a centered classic, a split text-plus-avatar layout, and a kinetic oversized name.
Every interaction actually works. The kinetic header runs a self-typing role rotator that cycles through six titles with a blinking caret, the split header has a cursor-follow gradient glow that tracks the pointer across the panel, and both the centered and kinetic headers carry a copy-email button that writes the address to the clipboard and confirms with a toast. The avatar is a pure-CSS morphing blob with an inline-SVG face and floating skill chips — no images or external assets beyond the Google Fonts link.
It is built to be pasted in: each <section> is an independent drop-in <header> you can take on its own. The layout collapses gracefully to a single column on tablets and full-width buttons on phones, respects prefers-reduced-motion (the rotator falls back to a quiet cross-fade), and keeps WCAG AA contrast with visible focus rings and a skip link.
Illustrative portfolio — fictional person and projects.