Portfolio — Glassmorphism Portfolio
A full one-page personal portfolio rendered in a glassmorphism style: a colorful animated gradient backdrop with floating blurred orbs sits behind frosted, translucent glass cards that use backdrop-filter blur, luminous borders, and soft glow. It composes hero, selected work, about, experience, skills, and contact sections, with filterable projects, an accent-tint switcher, scroll-reveal animations, animated skill bars, copy-to-clipboard links, and an inline-validated contact form.
MCP
Code
/* ============================================================
Maya Okafor — Glassmorphism Portfolio
Palette + tokens FIRST. Colorful gradient background, frosted
glass surfaces (backdrop-filter), floating blurred orbs.
============================================================ */
:root {
/* Backdrop gradient stops */
--bg-1: #1b1248;
--bg-2: #3a1d6e;
--bg-3: #0e1d52;
--bg-4: #07294d;
/* Orb colors */
--orb-a: #7c5cff;
--orb-b: #ff5ea8;
--orb-c: #29d3c2;
--orb-d: #ffb84d;
--orb-e: #4d8bff;
/* Accent (toggleable tint) */
--accent: #8a7bff;
--accent-2: #29d3c2;
--accent-ink: #0e1030;
/* Glass surface */
--glass-bg: rgba(255, 255, 255, 0.08);
--glass-bg-strong: rgba(255, 255, 255, 0.12);
--glass-border: rgba(255, 255, 255, 0.18);
--glass-border-hi: rgba(255, 255, 255, 0.36);
--glass-shadow: 0 20px 60px -20px rgba(8, 6, 40, 0.65);
--glass-glow: 0 0 0 1px rgba(255, 255, 255, 0.05) inset,
0 1px 0 0 rgba(255, 255, 255, 0.22) inset;
--blur: 18px;
/* Text */
--ink: #f6f4ff;
--ink-soft: rgba(246, 244, 255, 0.74);
--ink-faint: rgba(246, 244, 255, 0.52);
--radius: 22px;
--radius-sm: 13px;
--maxw: 1160px;
--ease: cubic-bezier(0.22, 1, 0.36, 1);
--font-display: "Sora", system-ui, -apple-system, "Segoe UI", sans-serif;
--font-body: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
}
*,
*::before,
*::after { box-sizing: border-box; }
* { margin: 0; }
html { scroll-behavior: smooth; }
body {
min-height: 100vh;
font-family: var(--font-body);
font-size: 16px;
line-height: 1.5;
color: var(--ink);
background:
radial-gradient(1200px 800px at 12% -10%, var(--bg-2), transparent 60%),
radial-gradient(1000px 700px at 100% 0%, var(--bg-1), transparent 55%),
linear-gradient(160deg, var(--bg-3), var(--bg-4) 70%, #06122e);
background-attachment: fixed;
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
img, svg { display: block; max-width: 100%; }
a { color: inherit; }
/* ---------- Floating blurred orbs ---------- */
.orbs {
position: fixed;
inset: 0;
z-index: 0;
pointer-events: none;
overflow: hidden;
}
.orb {
position: absolute;
border-radius: 50%;
filter: blur(60px);
opacity: 0.65;
mix-blend-mode: screen;
will-change: transform;
}
.orb--a { width: 42vmax; height: 42vmax; left: -8vmax; top: -10vmax; background: var(--orb-a); animation: drift 26s var(--ease) infinite alternate; }
.orb--b { width: 34vmax; height: 34vmax; right: -6vmax; top: 6vmax; background: var(--orb-b); animation: drift 32s var(--ease) infinite alternate-reverse; }
.orb--c { width: 30vmax; height: 30vmax; left: 20vmax; top: 55vmax; background: var(--orb-c); animation: drift 30s var(--ease) infinite alternate; }
.orb--d { width: 26vmax; height: 26vmax; right: 8vmax; top: 70vmax; background: var(--orb-d); opacity: 0.5; animation: drift 36s var(--ease) infinite alternate-reverse; }
.orb--e { width: 28vmax; height: 28vmax; left: 38vmax; top: 120vmax; background: var(--orb-e); animation: drift 28s var(--ease) infinite alternate; }
@keyframes drift {
from { transform: translate3d(0, 0, 0) scale(1); }
to { transform: translate3d(6vmax, 8vmax, 0) scale(1.12); }
}
/* ---------- Glass primitive ---------- */
.glass {
position: relative;
background: var(--glass-bg);
border: 1px solid var(--glass-border);
border-radius: var(--radius);
box-shadow: var(--glass-shadow), var(--glass-glow);
-webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
backdrop-filter: blur(var(--blur)) saturate(140%);
}
/* ---------- Skip link ---------- */
.skip-link {
position: fixed;
left: 16px;
top: -60px;
z-index: 100;
padding: 10px 16px;
border-radius: 999px;
background: var(--accent);
color: var(--accent-ink);
font-weight: 600;
text-decoration: none;
transition: top 0.2s var(--ease);
}
.skip-link:focus-visible { top: 16px; }
/* ---------- Layout wrappers ---------- */
main { position: relative; z-index: 1; }
.section {
width: min(100% - 2.4rem, var(--maxw));
margin: clamp(3.5rem, 8vw, 6.5rem) auto 0;
}
/* ---------- Header / Nav ---------- */
.site-header {
position: sticky;
top: 0;
z-index: 40;
padding: 0.9rem clamp(0.8rem, 3vw, 1.4rem);
}
.nav {
width: min(100%, var(--maxw));
margin: 0 auto;
display: flex;
align-items: center;
gap: 1rem;
padding: 0.6rem 0.85rem;
border-radius: 999px;
}
.nav__brand {
display: inline-flex;
align-items: center;
gap: 0.6rem;
text-decoration: none;
font-family: var(--font-display);
font-weight: 700;
}
.nav__mark {
display: grid;
place-items: center;
width: 36px;
height: 36px;
border-radius: 12px;
font-size: 0.85rem;
letter-spacing: 0.04em;
background: linear-gradient(135deg, var(--accent), var(--accent-2));
color: var(--accent-ink);
box-shadow: 0 6px 16px -6px var(--accent);
}
.nav__name { font-size: 0.98rem; }
.nav__links {
list-style: none;
display: flex;
gap: 0.35rem;
margin-left: auto;
padding: 0;
}
.nav__links a {
display: inline-block;
padding: 0.5rem 0.85rem;
border-radius: 999px;
font-size: 0.92rem;
font-weight: 500;
color: var(--ink-soft);
text-decoration: none;
transition: background 0.2s var(--ease), color 0.2s var(--ease);
}
.nav__links a:hover,
.nav__links a.is-current {
background: var(--glass-bg-strong);
color: var(--ink);
}
.nav__cta {
display: inline-flex;
align-items: center;
gap: 0.45rem;
padding: 0.5rem 0.95rem;
border-radius: 999px;
border: 1px solid var(--glass-border);
background: var(--glass-bg-strong);
color: var(--ink);
font: inherit;
font-size: 0.88rem;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s var(--ease), border-color 0.2s var(--ease);
}
.nav__cta:hover { transform: translateY(-1px); border-color: var(--glass-border-hi); }
.nav__cta .dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: linear-gradient(135deg, var(--accent), var(--accent-2));
box-shadow: 0 0 10px var(--accent);
}
.nav__toggle {
display: none;
flex-direction: column;
gap: 5px;
width: 42px;
height: 38px;
margin-left: auto;
padding: 0;
border: 1px solid var(--glass-border);
border-radius: 12px;
background: var(--glass-bg-strong);
cursor: pointer;
}
.nav__toggle span {
width: 18px;
height: 2px;
margin: 0 auto;
border-radius: 2px;
background: var(--ink);
transition: transform 0.25s var(--ease), opacity 0.25s var(--ease);
}
.nav__toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav__toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav__toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
/* ---------- Buttons ---------- */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.8rem 1.4rem;
border-radius: 999px;
font-family: var(--font-display);
font-weight: 600;
font-size: 0.96rem;
text-decoration: none;
cursor: pointer;
border: 1px solid transparent;
transition: transform 0.2s var(--ease), box-shadow 0.2s var(--ease), background 0.2s var(--ease);
}
.btn--solid {
background: linear-gradient(135deg, var(--accent), var(--accent-2));
color: var(--accent-ink);
box-shadow: 0 14px 30px -12px var(--accent);
}
.btn--solid:hover { transform: translateY(-2px); box-shadow: 0 20px 40px -14px var(--accent); }
.btn--ghost {
background: var(--glass-bg-strong);
border-color: var(--glass-border);
color: var(--ink);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
}
.btn--ghost:hover { transform: translateY(-2px); border-color: var(--glass-border-hi); }
/* ---------- Hero ---------- */
.hero {
width: min(100% - 2.4rem, var(--maxw));
margin: clamp(1.4rem, 4vw, 2.6rem) auto 0;
display: grid;
grid-template-columns: 1.55fr 1fr;
gap: 1.2rem;
align-items: stretch;
}
.hero__card { padding: clamp(1.6rem, 4vw, 2.8rem); }
.eyebrow {
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-size: 0.82rem;
font-weight: 600;
letter-spacing: 0.02em;
color: var(--ink-soft);
text-transform: uppercase;
}
.status-dot {
width: 9px;
height: 9px;
border-radius: 50%;
background: #4ade80;
box-shadow: 0 0 0 0 rgba(74, 222, 128, 0.6);
animation: pulse 2.4s infinite;
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(74, 222, 128, 0.55); }
70% { box-shadow: 0 0 0 9px rgba(74, 222, 128, 0); }
100% { box-shadow: 0 0 0 0 rgba(74, 222, 128, 0); }
}
.hero__card h1 {
margin: 0.9rem 0 0.7rem;
font-family: var(--font-display);
font-weight: 800;
font-size: clamp(2rem, 5.2vw, 3.4rem);
line-height: 1.05;
letter-spacing: -0.02em;
}
.hero__card h1 em {
font-style: italic;
background: linear-gradient(120deg, var(--accent-2), var(--accent));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.hero__lead {
max-width: 52ch;
color: var(--ink-soft);
font-size: clamp(1rem, 1.6vw, 1.12rem);
}
.hero__actions {
display: flex;
flex-wrap: wrap;
gap: 0.8rem;
margin-top: 1.4rem;
}
.hero__stats {
display: flex;
flex-wrap: wrap;
gap: 1.6rem;
margin-top: 1.8rem;
padding-top: 1.4rem;
border-top: 1px solid var(--glass-border);
}
.hero__stats dt { font-size: 0.78rem; color: var(--ink-faint); text-transform: uppercase; letter-spacing: 0.04em; }
.hero__stats dd {
font-family: var(--font-display);
font-weight: 700;
font-size: clamp(1.5rem, 3vw, 2rem);
background: linear-gradient(120deg, #fff, var(--accent-2));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.hero__aside {
padding: clamp(1.4rem, 3vw, 1.8rem);
display: flex;
flex-direction: column;
}
.aside__title {
font-family: var(--font-display);
font-size: 0.82rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--ink-faint);
font-weight: 600;
}
.aside__list { list-style: none; padding: 0; margin: 1rem 0 0; display: grid; gap: 0.85rem; }
.aside__list li { display: grid; gap: 0.15rem; }
.aside__k { font-size: 0.76rem; color: var(--ink-faint); text-transform: uppercase; letter-spacing: 0.06em; }
.aside__list li span:last-child { font-weight: 500; }
.aside__marquee {
margin-top: auto;
margin-bottom: -0.4rem;
padding-top: 1.4rem;
overflow: hidden;
-webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}
.marquee__track {
display: inline-flex;
gap: 0.7rem;
white-space: nowrap;
color: var(--ink-soft);
font-size: 0.86rem;
font-weight: 600;
animation: marquee 18s linear infinite;
}
@keyframes marquee { to { transform: translateX(-50%); } }
/* ---------- Section heads ---------- */
.section__head { margin-bottom: 1.4rem; }
.section__head h2 {
font-family: var(--font-display);
font-weight: 700;
font-size: clamp(1.6rem, 3.6vw, 2.4rem);
letter-spacing: -0.02em;
}
.section__sub { color: var(--ink-soft); margin-top: 0.3rem; }
/* ---------- Filters / chips ---------- */
.filters { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 1.4rem; }
.chip {
padding: 0.5rem 1.05rem;
border-radius: 999px;
border: 1px solid var(--glass-border);
background: var(--glass-bg);
color: var(--ink-soft);
font: inherit;
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
transition: transform 0.18s var(--ease), color 0.18s var(--ease), border-color 0.18s var(--ease), background 0.18s var(--ease);
}
.chip:hover { transform: translateY(-1px); color: var(--ink); border-color: var(--glass-border-hi); }
.chip.is-active {
color: var(--accent-ink);
background: linear-gradient(135deg, var(--accent), var(--accent-2));
border-color: transparent;
}
/* ---------- Work grid ---------- */
.work-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.2rem;
}
.card {
overflow: hidden;
display: flex;
flex-direction: column;
transition: transform 0.3s var(--ease), border-color 0.3s var(--ease), box-shadow 0.3s var(--ease);
}
.card:hover,
.card:focus-visible {
transform: translateY(-6px);
border-color: var(--glass-border-hi);
box-shadow: var(--glass-shadow), 0 0 40px -12px var(--accent);
}
.card.is-hidden { display: none; }
.card__thumb {
position: relative;
aspect-ratio: 16 / 10;
display: grid;
place-items: center;
border-bottom: 1px solid var(--glass-border);
}
.card__thumb::after {
content: "";
position: absolute;
inset: 0;
background: radial-gradient(120% 80% at 30% 10%, rgba(255, 255, 255, 0.28), transparent 55%);
mix-blend-mode: overlay;
}
.thumb__glyph {
font-size: 2.4rem;
color: rgba(255, 255, 255, 0.9);
text-shadow: 0 4px 18px rgba(0, 0, 0, 0.35);
}
.thumb--a { background: linear-gradient(135deg, #7c5cff, #ff5ea8); }
.thumb--b { background: linear-gradient(135deg, #29d3c2, #4d8bff); }
.thumb--c { background: linear-gradient(135deg, #ff7e5f, #ffb84d); }
.thumb--d { background: linear-gradient(135deg, #5b6cff, #a855f7); }
.thumb--e { background: linear-gradient(135deg, #0ea5e9, #22d3ee); }
.thumb--f { background: linear-gradient(135deg, #f472b6, #c084fc); }
.card__body { padding: 1.15rem 1.25rem 1.35rem; display: flex; flex-direction: column; gap: 0.45rem; }
.card__year { font-size: 0.78rem; color: var(--ink-faint); letter-spacing: 0.04em; }
.card__body h3 { font-family: var(--font-display); font-weight: 600; font-size: 1.18rem; }
.card__body p { color: var(--ink-soft); font-size: 0.94rem; }
.card__tags { list-style: none; display: flex; flex-wrap: wrap; gap: 0.4rem; padding: 0; margin-top: 0.3rem; }
.card__tags li {
font-size: 0.72rem;
font-weight: 600;
padding: 0.22rem 0.6rem;
border-radius: 999px;
background: var(--glass-bg-strong);
border: 1px solid var(--glass-border);
color: var(--ink-soft);
}
.work-empty { text-align: center; color: var(--ink-soft); padding: 2rem; }
/* ---------- About ---------- */
.about { display: grid; grid-template-columns: 1.7fr 1fr; gap: 1.2rem; }
.about__main { padding: clamp(1.6rem, 4vw, 2.4rem); }
.about__main h2 {
font-family: var(--font-display);
font-weight: 700;
font-size: clamp(1.6rem, 3.6vw, 2.2rem);
margin-bottom: 0.9rem;
}
.about__main p { color: var(--ink-soft); margin-bottom: 0.9rem; max-width: 62ch; }
.about__values {
list-style: none;
padding: 0;
margin-top: 1.4rem;
display: grid;
gap: 0.9rem;
grid-template-columns: repeat(3, 1fr);
}
.about__values li {
padding: 0.95rem 1rem;
border-radius: var(--radius-sm);
background: var(--glass-bg-strong);
border: 1px solid var(--glass-border);
}
.about__values strong { display: block; font-family: var(--font-display); margin-bottom: 0.2rem; }
.about__values span { font-size: 0.86rem; color: var(--ink-soft); }
.about__side { padding: clamp(1.4rem, 3vw, 1.8rem); }
.award-list { list-style: none; padding: 0; margin-top: 1rem; display: grid; gap: 0.8rem; }
.award-list li {
display: flex;
gap: 0.7rem;
align-items: baseline;
padding-bottom: 0.7rem;
border-bottom: 1px solid var(--glass-border);
font-size: 0.92rem;
}
.award-list li:last-child { border-bottom: 0; padding-bottom: 0; }
.award__year {
font-family: var(--font-display);
font-weight: 700;
color: var(--accent-2);
min-width: 3rem;
}
/* ---------- Timeline ---------- */
.timeline { list-style: none; padding: clamp(1.4rem, 3.5vw, 2rem); margin: 0; }
.timeline__item {
display: grid;
grid-template-columns: 9.5rem 1fr;
gap: 1.2rem;
padding: 1.1rem 0;
border-bottom: 1px solid var(--glass-border);
}
.timeline__item:last-child { border-bottom: 0; padding-bottom: 0; }
.timeline__item:first-child { padding-top: 0; }
.timeline__time {
font-family: var(--font-display);
font-weight: 600;
font-size: 0.86rem;
color: var(--accent-2);
letter-spacing: 0.02em;
}
.timeline__body h3 { font-family: var(--font-display); font-weight: 600; font-size: 1.08rem; }
.timeline__body p { color: var(--ink-soft); font-size: 0.94rem; margin-top: 0.2rem; }
/* ---------- Skills ---------- */
.skills-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.2rem; }
.skill { padding: clamp(1.3rem, 3vw, 1.7rem); }
.skill h3 { font-family: var(--font-display); font-weight: 600; font-size: 1.1rem; margin-bottom: 1rem; }
.skill__bars { list-style: none; padding: 0; display: grid; gap: 0.9rem; }
.skill__bars li { display: grid; gap: 0.4rem; }
.skill__bars span { font-size: 0.88rem; color: var(--ink-soft); }
.bar {
display: block;
height: 7px;
border-radius: 999px;
background: var(--glass-bg-strong);
overflow: hidden;
position: relative;
}
.bar::after {
content: "";
position: absolute;
inset: 0;
width: var(--v, 0%);
border-radius: 999px;
background: linear-gradient(90deg, var(--accent), var(--accent-2));
transform-origin: left;
transform: scaleX(0);
transition: transform 1s var(--ease);
}
.bar.is-filled::after { transform: scaleX(1); }
.skill__chips { list-style: none; padding: 0; display: flex; flex-wrap: wrap; gap: 0.5rem; }
.skill__chips li {
padding: 0.4rem 0.85rem;
border-radius: 999px;
background: var(--glass-bg-strong);
border: 1px solid var(--glass-border);
font-size: 0.84rem;
font-weight: 500;
}
/* ---------- Contact ---------- */
.contact__card {
padding: clamp(1.6rem, 4vw, 2.6rem);
display: grid;
grid-template-columns: 1fr 1.1fr;
gap: clamp(1.4rem, 4vw, 2.6rem);
align-items: start;
}
.contact__intro h2 {
font-family: var(--font-display);
font-weight: 700;
font-size: clamp(1.7rem, 4vw, 2.4rem);
letter-spacing: -0.02em;
}
.contact__intro p { color: var(--ink-soft); margin-top: 0.6rem; max-width: 34ch; }
.contact__links { list-style: none; padding: 0; margin-top: 1.3rem; display: grid; gap: 0.65rem; }
.contact__links a {
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-weight: 500;
text-decoration: none;
color: var(--ink);
border-bottom: 1px solid transparent;
transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
width: fit-content;
}
.contact__links a:hover { color: var(--accent-2); border-color: var(--accent-2); }
.contact__form { display: grid; gap: 0.9rem; }
.field { display: grid; gap: 0.35rem; }
.field label { font-size: 0.84rem; font-weight: 600; color: var(--ink-soft); }
.field input,
.field textarea {
width: 100%;
padding: 0.8rem 0.95rem;
border-radius: var(--radius-sm);
border: 1px solid var(--glass-border);
background: rgba(255, 255, 255, 0.06);
color: var(--ink);
font: inherit;
font-size: 0.95rem;
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
resize: vertical;
}
.field input::placeholder,
.field textarea::placeholder { color: var(--ink-faint); }
.field input:focus,
.field textarea:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(138, 123, 255, 0.3);
}
.field.has-error input,
.field.has-error textarea { border-color: #ff7a90; box-shadow: 0 0 0 3px rgba(255, 122, 144, 0.22); }
.field__error { color: #ff9aab; font-size: 0.78rem; min-height: 0.9rem; }
.contact__form .btn { margin-top: 0.3rem; }
/* ---------- Footer ---------- */
.site-footer { padding: clamp(2.5rem, 6vw, 4rem) clamp(0.8rem, 3vw, 1.4rem) 1.4rem; position: relative; z-index: 1; }
.footer__bar {
width: min(100%, var(--maxw));
margin: 0 auto;
padding: 1rem 1.4rem;
border-radius: 999px;
display: flex;
flex-wrap: wrap;
gap: 0.6rem;
justify-content: space-between;
align-items: center;
font-size: 0.86rem;
color: var(--ink-soft);
}
.footer__top { text-decoration: none; font-weight: 600; color: var(--ink); }
.footer__top:hover { color: var(--accent-2); }
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 1.6rem;
transform: translate(-50%, 140%);
z-index: 80;
padding: 0.85rem 1.3rem;
border-radius: 999px;
background: var(--glass-bg-strong);
border: 1px solid var(--glass-border-hi);
-webkit-backdrop-filter: blur(16px);
backdrop-filter: blur(16px);
box-shadow: var(--glass-shadow);
font-weight: 600;
font-size: 0.92rem;
opacity: 0;
transition: transform 0.4s var(--ease), opacity 0.4s var(--ease);
pointer-events: none;
max-width: calc(100% - 2rem);
}
.toast.is-visible { transform: translate(-50%, 0); opacity: 1; }
/* ---------- Reveal on scroll ---------- */
.reveal { opacity: 0; transform: translateY(22px); transition: opacity 0.7s var(--ease), transform 0.7s var(--ease); }
.reveal.is-in { opacity: 1; transform: none; }
/* ---------- Focus visibility ---------- */
:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 3px; border-radius: 6px; }
/* ============================================================
Responsive
============================================================ */
@media (max-width: 940px) {
.hero { grid-template-columns: 1fr; }
.about { grid-template-columns: 1fr; }
.contact__card { grid-template-columns: 1fr; }
.work-grid { grid-template-columns: repeat(2, 1fr); }
.skills-grid { grid-template-columns: 1fr; }
}
@media (max-width: 720px) {
.nav__cta { display: none; }
.nav__toggle { display: flex; }
.nav__links {
position: absolute;
top: calc(100% + 0.6rem);
left: 0;
right: 0;
flex-direction: column;
gap: 0.2rem;
padding: 0.7rem;
border-radius: var(--radius);
background: var(--glass-bg-strong);
border: 1px solid var(--glass-border);
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
box-shadow: var(--glass-shadow);
transform-origin: top;
transform: scaleY(0.92);
opacity: 0;
visibility: hidden;
transition: opacity 0.2s var(--ease), transform 0.2s var(--ease), visibility 0.2s;
}
.nav__links.is-open { opacity: 1; visibility: visible; transform: none; }
.nav { flex-wrap: wrap; position: relative; }
}
@media (max-width: 560px) {
body { font-size: 15px; }
.work-grid { grid-template-columns: 1fr; }
.about__values { grid-template-columns: 1fr; }
.timeline__item { grid-template-columns: 1fr; gap: 0.35rem; }
.hero__stats { gap: 1.1rem; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
scroll-behavior: auto !important;
}
.reveal { opacity: 1; transform: none; }
}/* ============================================================
Maya Okafor — Glassmorphism Portfolio
Vanilla JS: nav menu, accent tint toggle, scroll spy,
project filtering, scroll reveal + skill bars, copy-to-clipboard,
form validation. No external libs.
============================================================ */
(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");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-visible");
}, 2600);
}
/* ---------- Mobile nav ---------- */
var navToggle = document.getElementById("navToggle");
var navLinks = document.getElementById("navLinks");
if (navToggle && navLinks) {
navToggle.addEventListener("click", function () {
var open = navLinks.classList.toggle("is-open");
navToggle.setAttribute("aria-expanded", String(open));
});
// Close on link click (mobile)
navLinks.addEventListener("click", function (e) {
if (e.target.closest("a")) {
navLinks.classList.remove("is-open");
navToggle.setAttribute("aria-expanded", "false");
}
});
}
/* ---------- Accent tint toggle ---------- */
var tints = [
{ name: "Aurora", accent: "#8a7bff", accent2: "#29d3c2" },
{ name: "Sunset", accent: "#ff7e5f", accent2: "#ffb84d" },
{ name: "Bloom", accent: "#ff5ea8", accent2: "#a855f7" },
{ name: "Tide", accent: "#22d3ee", accent2: "#4d8bff" }
];
var tintIndex = 0;
var themeToggle = document.getElementById("themeToggle");
if (themeToggle) {
themeToggle.addEventListener("click", function () {
tintIndex = (tintIndex + 1) % tints.length;
var t = tints[tintIndex];
var root = document.documentElement.style;
root.setProperty("--accent", t.accent);
root.setProperty("--accent-2", t.accent2);
themeToggle.setAttribute("aria-pressed", String(tintIndex !== 0));
toast("Accent: " + t.name);
});
}
/* ---------- Project filtering ---------- */
var chips = Array.prototype.slice.call(document.querySelectorAll(".chip"));
var cards = Array.prototype.slice.call(document.querySelectorAll(".work-grid .card"));
var emptyMsg = document.getElementById("workEmpty");
function applyFilter(filter) {
var shown = 0;
cards.forEach(function (card) {
var tags = card.getAttribute("data-tags") || "";
var match = filter === "all" || tags.indexOf(filter) !== -1;
card.classList.toggle("is-hidden", !match);
if (match) shown++;
});
if (emptyMsg) emptyMsg.hidden = shown !== 0;
}
chips.forEach(function (chip) {
chip.addEventListener("click", function () {
chips.forEach(function (c) {
c.classList.remove("is-active");
c.setAttribute("aria-pressed", "false");
});
chip.classList.add("is-active");
chip.setAttribute("aria-pressed", "true");
applyFilter(chip.getAttribute("data-filter"));
});
});
/* ---------- Scroll spy (highlight nav) ---------- */
var navAnchors = Array.prototype.slice.call(document.querySelectorAll("[data-nav]"));
var sections = navAnchors
.map(function (a) { return document.querySelector(a.getAttribute("href")); })
.filter(Boolean);
if ("IntersectionObserver" in window && sections.length) {
var spy = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
var id = entry.target.id;
navAnchors.forEach(function (a) {
a.classList.toggle("is-current", a.getAttribute("href") === "#" + id);
});
}
});
}, { rootMargin: "-45% 0px -50% 0px" });
sections.forEach(function (s) { spy.observe(s); });
}
/* ---------- Scroll reveal + skill bars ---------- */
var revealTargets = Array.prototype.slice.call(
document.querySelectorAll(".section, .hero__card, .hero__aside, .card")
);
revealTargets.forEach(function (el) { el.classList.add("reveal"); });
var bars = Array.prototype.slice.call(document.querySelectorAll(".bar"));
if ("IntersectionObserver" in window) {
var revealObs = new IntersectionObserver(function (entries, obs) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
entry.target.classList.add("is-in");
obs.unobserve(entry.target);
}
});
}, { threshold: 0.12 });
revealTargets.forEach(function (el) { revealObs.observe(el); });
var barObs = new IntersectionObserver(function (entries, obs) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
entry.target.classList.add("is-filled");
obs.unobserve(entry.target);
}
});
}, { threshold: 0.4 });
bars.forEach(function (b) { barObs.observe(b); });
} else {
revealTargets.forEach(function (el) { el.classList.add("is-in"); });
bars.forEach(function (b) { b.classList.add("is-filled"); });
}
/* ---------- Copy-to-clipboard links ---------- */
document.querySelectorAll("[data-copy]").forEach(function (link) {
link.addEventListener("click", function (e) {
e.preventDefault();
var val = link.getAttribute("data-copy");
var done = function () { toast("Copied: " + val); };
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(val).then(done).catch(function () {
fallbackCopy(val); done();
});
} else {
fallbackCopy(val); done();
}
});
});
function fallbackCopy(text) {
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();
try { document.execCommand("copy"); } catch (err) { /* no-op */ }
document.body.removeChild(ta);
}
/* ---------- Contact form validation ---------- */
var form = document.getElementById("contactForm");
if (form) {
var emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
function setError(input, msg) {
var field = input.closest(".field");
var err = field ? field.querySelector(".field__error") : null;
if (field) field.classList.toggle("has-error", !!msg);
if (err) err.textContent = msg || "";
input.setAttribute("aria-invalid", msg ? "true" : "false");
}
function validate() {
var ok = true;
var name = form.elements.name;
var email = form.elements.email;
var message = form.elements.message;
if (!name.value.trim()) { setError(name, "Please enter your name."); ok = false; }
else setError(name, "");
if (!email.value.trim()) { setError(email, "Email is required."); ok = false; }
else if (!emailRe.test(email.value.trim())) { setError(email, "Enter a valid email."); ok = false; }
else setError(email, "");
if (message.value.trim().length < 12) { setError(message, "Tell me a bit more (12+ chars)."); ok = false; }
else setError(message, "");
return ok;
}
// Clear error as the user fixes a field
form.addEventListener("input", function (e) {
var field = e.target.closest(".field");
if (field && field.classList.contains("has-error")) {
field.classList.remove("has-error");
var err = field.querySelector(".field__error");
if (err) err.textContent = "";
}
});
form.addEventListener("submit", function (e) {
e.preventDefault();
if (validate()) {
var name = form.elements.name.value.trim().split(" ")[0];
form.reset();
toast("Thanks, " + name + " — brief received!");
} else {
toast("Please fix the highlighted fields.");
var firstErr = form.querySelector(".has-error input, .has-error textarea");
if (firstErr) firstErr.focus();
}
});
}
/* ---------- Footer year (keeps copy honest) ---------- */
// Static fictional year retained in markup; nothing dynamic needed.
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Maya Okafor — Product Designer</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=Sora:wght@400;500;600;700;800&family=Inter:wght@400;500;600&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<!-- Floating blurred orbs behind every glass panel -->
<div class="orbs" aria-hidden="true">
<span class="orb orb--a"></span>
<span class="orb orb--b"></span>
<span class="orb orb--c"></span>
<span class="orb orb--d"></span>
<span class="orb orb--e"></span>
</div>
<a class="skip-link" href="#work">Skip to work</a>
<header class="site-header">
<nav class="glass nav" aria-label="Primary">
<a class="nav__brand" href="#top" aria-label="Maya Okafor, home">
<span class="nav__mark" aria-hidden="true">MO</span>
<span class="nav__name">Maya Okafor</span>
</a>
<button class="nav__toggle" id="navToggle" aria-expanded="false" aria-controls="navLinks" aria-label="Toggle navigation">
<span></span><span></span><span></span>
</button>
<ul class="nav__links" id="navLinks">
<li><a href="#work" data-nav>Work</a></li>
<li><a href="#about" data-nav>About</a></li>
<li><a href="#experience" data-nav>Experience</a></li>
<li><a href="#skills" data-nav>Skills</a></li>
<li><a href="#contact" data-nav>Contact</a></li>
</ul>
<button class="nav__cta" id="themeToggle" type="button" aria-pressed="false" title="Toggle accent tint">
<span class="dot" aria-hidden="true"></span> Tint
</button>
</nav>
</header>
<main id="top">
<!-- HERO -->
<section class="hero" id="hero" aria-labelledby="hero-title">
<div class="glass hero__card">
<p class="eyebrow"><span class="status-dot" aria-hidden="true"></span> Available for select projects · Summer 2026</p>
<h1 id="hero-title">Designing calm, <em>useful</em> products people actually finish using.</h1>
<p class="hero__lead">
I'm Maya — a product designer blending systems thinking with playful, tactile
interfaces. I help teams ship clear flows, design systems, and the small details
that make software feel effortless.
</p>
<div class="hero__actions">
<a class="btn btn--solid" href="#work">View selected work</a>
<a class="btn btn--ghost" href="#contact">Start a project</a>
</div>
<dl class="hero__stats">
<div><dt>Years designing</dt><dd>9</dd></div>
<div><dt>Products shipped</dt><dd>40+</dd></div>
<div><dt>Avg. NPS lift</dt><dd>+22</dd></div>
</dl>
</div>
<aside class="glass hero__aside" aria-label="Now">
<h2 class="aside__title">Now</h2>
<ul class="aside__list">
<li><span class="aside__k">Role</span><span>Principal Designer, Lumen Labs</span></li>
<li><span class="aside__k">Based</span><span>Lisbon · GMT+1</span></li>
<li><span class="aside__k">Focus</span><span>Design systems & AI tooling</span></li>
</ul>
<div class="aside__marquee" aria-hidden="true">
<div class="marquee__track">
<span>Figma</span><span>·</span><span>Design Systems</span><span>·</span><span>Prototyping</span><span>·</span><span>Research</span><span>·</span><span>Motion</span><span>·</span>
<span>Figma</span><span>·</span><span>Design Systems</span><span>·</span><span>Prototyping</span><span>·</span><span>Research</span><span>·</span><span>Motion</span><span>·</span>
</div>
</div>
</aside>
</section>
<!-- WORK -->
<section class="section" id="work" aria-labelledby="work-title">
<div class="section__head">
<h2 id="work-title">Selected work</h2>
<p class="section__sub">Six projects across fintech, health, and developer tools.</p>
</div>
<div class="filters" role="group" aria-label="Filter projects by discipline">
<button class="chip is-active" data-filter="all" aria-pressed="true">All</button>
<button class="chip" data-filter="product" aria-pressed="false">Product</button>
<button class="chip" data-filter="systems" aria-pressed="false">Systems</button>
<button class="chip" data-filter="research" aria-pressed="false">Research</button>
</div>
<div class="work-grid" id="workGrid">
<article class="glass card" data-tags="product systems" tabindex="0">
<div class="card__thumb thumb--a" aria-hidden="true"><span class="thumb__glyph">◈</span></div>
<div class="card__body">
<span class="card__year">2025</span>
<h3>Halo Wallet</h3>
<p>A consumer fintech app redesign that cut onboarding from 11 steps to 4 and lifted activation by 31%.</p>
<ul class="card__tags"><li>Product</li><li>Mobile</li><li>Systems</li></ul>
</div>
</article>
<article class="glass card" data-tags="systems" tabindex="0">
<div class="card__thumb thumb--b" aria-hidden="true"><span class="thumb__glyph">▣</span></div>
<div class="card__body">
<span class="card__year">2024</span>
<h3>Prism Design System</h3>
<p>Token-driven component library powering 9 product squads, with themeable surfaces and motion specs.</p>
<ul class="card__tags"><li>Systems</li><li>Tokens</li><li>Docs</li></ul>
</div>
</article>
<article class="glass card" data-tags="product research" tabindex="0">
<div class="card__thumb thumb--c" aria-hidden="true"><span class="thumb__glyph">✦</span></div>
<div class="card__body">
<span class="card__year">2024</span>
<h3>Vesta Health</h3>
<p>Care-coordination dashboard for nurses, co-designed through 18 field interviews and weekly usability tests.</p>
<ul class="card__tags"><li>Product</li><li>Research</li><li>Health</li></ul>
</div>
</article>
<article class="glass card" data-tags="research" tabindex="0">
<div class="card__thumb thumb--d" aria-hidden="true"><span class="thumb__glyph">◑</span></div>
<div class="card__body">
<span class="card__year">2023</span>
<h3>Signal Insights</h3>
<p>A research repository turning scattered interviews into searchable, taggable, shareable evidence.</p>
<ul class="card__tags"><li>Research</li><li>Tooling</li></ul>
</div>
</article>
<article class="glass card" data-tags="product systems" tabindex="0">
<div class="card__thumb thumb--e" aria-hidden="true"><span class="thumb__glyph">❖</span></div>
<div class="card__body">
<span class="card__year">2023</span>
<h3>Forge IDE</h3>
<p>Developer tool redesign with a focus-first layout and command palette that became the team's daily driver.</p>
<ul class="card__tags"><li>Product</li><li>DevTools</li><li>Systems</li></ul>
</div>
</article>
<article class="glass card" data-tags="product research" tabindex="0">
<div class="card__thumb thumb--f" aria-hidden="true"><span class="thumb__glyph">◓</span></div>
<div class="card__body">
<span class="card__year">2022</span>
<h3>Tidepool Learning</h3>
<p>An adaptive course player for teens that improved lesson completion by 19% across two pilots.</p>
<ul class="card__tags"><li>Product</li><li>Research</li><li>EdTech</li></ul>
</div>
</article>
</div>
<p class="work-empty" id="workEmpty" hidden>No projects match that filter yet.</p>
</section>
<!-- ABOUT -->
<section class="section about" id="about" aria-labelledby="about-title">
<div class="glass about__main">
<h2 id="about-title">About</h2>
<p>
I started in editorial design, fell for interaction, and never looked back. Today I
work where research, systems, and craft overlap — the place where a product stops
feeling like software and starts feeling like a tool you trust.
</p>
<p>
I care about clarity over cleverness, defaults that respect people's time, and
design systems that empower teams instead of policing them. When I'm not in Figma,
I'm restoring film cameras or running a tiny risograph print shop.
</p>
<ul class="about__values">
<li><strong>Systems</strong><span>Reusable, documented, themeable.</span></li>
<li><strong>Evidence</strong><span>Decisions backed by real users.</span></li>
<li><strong>Craft</strong><span>The last 10% is the product.</span></li>
</ul>
</div>
<aside class="glass about__side" aria-label="Recognition">
<h3 class="aside__title">Recognition</h3>
<ul class="award-list">
<li><span class="award__year">2025</span> Awwwards · Site of the Day</li>
<li><span class="award__year">2024</span> CSS Design Awards · Best UX</li>
<li><span class="award__year">2023</span> Fast Co. · Innovation by Design (finalist)</li>
</ul>
</aside>
</section>
<!-- EXPERIENCE -->
<section class="section" id="experience" aria-labelledby="exp-title">
<div class="section__head">
<h2 id="exp-title">Experience</h2>
<p class="section__sub">A decade of building products and the systems behind them.</p>
</div>
<ol class="glass timeline">
<li class="timeline__item">
<div class="timeline__time">2023 — Now</div>
<div class="timeline__body">
<h3>Principal Designer · Lumen Labs</h3>
<p>Lead design across AI tooling. Built Prism, our design system, and mentor a team of five.</p>
</div>
</li>
<li class="timeline__item">
<div class="timeline__time">2020 — 2023</div>
<div class="timeline__body">
<h3>Senior Product Designer · Northwind</h3>
<p>Owned the wallet and payments experience; shipped the redesign that lifted activation 31%.</p>
</div>
</li>
<li class="timeline__item">
<div class="timeline__time">2017 — 2020</div>
<div class="timeline__body">
<h3>Product Designer · Vesta Health</h3>
<p>Designed clinician tools end-to-end, from field research through high-fidelity delivery.</p>
</div>
</li>
<li class="timeline__item">
<div class="timeline__time">2015 — 2017</div>
<div class="timeline__body">
<h3>Visual Designer · Atlas Studio</h3>
<p>Brand and editorial work for early-stage startups; learned to ship fast and write clearly.</p>
</div>
</li>
</ol>
</section>
<!-- SKILLS -->
<section class="section" id="skills" aria-labelledby="skills-title">
<div class="section__head">
<h2 id="skills-title">Skills</h2>
<p class="section__sub">What I bring to a product team.</p>
</div>
<div class="skills-grid">
<div class="glass skill">
<h3>Design</h3>
<ul class="skill__bars">
<li><span>Interaction</span><i class="bar" style="--v:95%"></i></li>
<li><span>Design systems</span><i class="bar" style="--v:92%"></i></li>
<li><span>Prototyping</span><i class="bar" style="--v:88%"></i></li>
<li><span>Motion</span><i class="bar" style="--v:74%"></i></li>
</ul>
</div>
<div class="glass skill">
<h3>Research</h3>
<ul class="skill__bars">
<li><span>Interviews</span><i class="bar" style="--v:90%"></i></li>
<li><span>Usability testing</span><i class="bar" style="--v:86%"></i></li>
<li><span>Synthesis</span><i class="bar" style="--v:83%"></i></li>
<li><span>Surveys</span><i class="bar" style="--v:70%"></i></li>
</ul>
</div>
<div class="glass skill">
<h3>Tools</h3>
<ul class="skill__chips">
<li>Figma</li><li>Framer</li><li>Storybook</li><li>Tokens Studio</li>
<li>Notion</li><li>Maze</li><li>HTML/CSS</li><li>After Effects</li>
</ul>
</div>
</div>
</section>
<!-- CONTACT -->
<section class="section contact" id="contact" aria-labelledby="contact-title">
<div class="glass contact__card">
<div class="contact__intro">
<h2 id="contact-title">Let's build something clear.</h2>
<p>Tell me about your product and timeline — I reply within two business days.</p>
<ul class="contact__links">
<li><a href="mailto:hello@mayaokafor.design">hello@mayaokafor.design</a></li>
<li><a href="#" data-copy="@maya.okafor">@maya.okafor</a></li>
<li><a href="#" data-copy="dribbble.com/mayaokafor">Dribbble</a></li>
</ul>
</div>
<form class="contact__form" id="contactForm" novalidate>
<div class="field">
<label for="cName">Name</label>
<input id="cName" name="name" type="text" autocomplete="name" required />
<small class="field__error" data-for="cName"></small>
</div>
<div class="field">
<label for="cEmail">Email</label>
<input id="cEmail" name="email" type="email" autocomplete="email" required />
<small class="field__error" data-for="cEmail"></small>
</div>
<div class="field">
<label for="cMsg">Project brief</label>
<textarea id="cMsg" name="message" rows="4" required></textarea>
<small class="field__error" data-for="cMsg"></small>
</div>
<button class="btn btn--solid" type="submit">Send brief</button>
</form>
</div>
</section>
</main>
<footer class="site-footer">
<div class="glass footer__bar">
<span>© 2026 Maya Okafor — Fictional portfolio.</span>
<a href="#top" class="footer__top">Back to top ↑</a>
</div>
</footer>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Glassmorphism Portfolio
A complete single-person portfolio dressed entirely in glass. A multi-layer gradient
background and five slowly drifting, blurred color orbs sit behind every panel, while
the content rides on frosted cards built with backdrop-filter blur, translucent borders,
and an inner highlight that reads as light catching an edge. The same fictional content —
designer Maya Okafor and six projects — is composed into hero, work, about, experience,
skills, and contact sections, all re-skinned as glass.
Interactions are vanilla JS and all functional. The work grid filters by discipline through glass chips, the nav highlights the section in view via an IntersectionObserver, and a tint button cycles the accent gradient (Aurora, Sunset, Bloom, Tide) live by swapping CSS custom properties. Cards and sections fade up on scroll, skill bars animate to their values when revealed, contact links copy to the clipboard with a toast, and the contact form validates name, email, and message inline before confirming.
The layout collapses cleanly from a three-column grid down to a single column near 360px,
the nav folds into a frosted dropdown on small screens, focus-visible rings keep it
keyboard-friendly, and prefers-reduced-motion disables the orb drift and reveals.
Illustrative portfolio — fictional person and projects.