Portfolio — Editorial / Typographic Portfolio
A full one-page designer portfolio dressed as an independent magazine — cream paper, oxblood accent and oversized Fraunces serif. A newspaper masthead, a drop-capped multi-column lede, numbered project articles with footnote captions, a justified two-column about with a pull quote, a dotted-leader skills index, a ruled CV and a centered contact page. Vanilla JS drives a discipline filter, copy-to-clipboard email, scroll-spy nav and a live issue dateline.
MCP
Code
:root {
/* Editorial palette — cream paper + black ink + one warm accent */
--paper: #f4efe4;
--paper-deep: #ece4d2;
--ink: #1a1714;
--ink-soft: #4a443c;
--ink-faint: #7c7468;
--accent: #9a2b1e; /* oxblood red */
--accent-soft: #c2553f;
--rule: #1a1714;
--rule-faint: #c8bfac;
--paper-card: #fbf8f0;
--serif: "Fraunces", "Times New Roman", Georgia, serif;
--body: "Spectral", Georgia, "Times New Roman", serif;
--mono: "IBM Plex Mono", ui-monospace, "SFMono-Regular", monospace;
--measure: 66ch;
--gutter: clamp(1.25rem, 4vw, 4rem);
--maxw: 76rem;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
background:
radial-gradient(120% 80% at 50% -10%, var(--paper-card), transparent 60%),
var(--paper);
color: var(--ink);
font-family: var(--body);
font-size: clamp(1rem, 0.96rem + 0.2vw, 1.125rem);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
/* faint paper grain via tiled gradient */
body::before {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
z-index: 0;
opacity: 0.35;
mix-blend-mode: multiply;
background-image:
radial-gradient(circle at 20% 30%, rgba(120, 100, 70, 0.06) 0 1px, transparent 1px),
radial-gradient(circle at 70% 60%, rgba(120, 100, 70, 0.05) 0 1px, transparent 1px);
background-size: 7px 7px, 11px 11px;
}
.paper {
position: relative;
z-index: 1;
max-width: var(--maxw);
margin: 0 auto;
padding: clamp(1rem, 3vw, 2.5rem) var(--gutter) 0;
}
::selection {
background: var(--accent);
color: var(--paper);
}
a {
color: inherit;
text-decoration: none;
}
em {
font-style: italic;
}
:focus-visible {
outline: 2.5px solid var(--accent);
outline-offset: 3px;
}
.skip-link {
position: absolute;
left: 1rem;
top: -3.5rem;
z-index: 50;
background: var(--ink);
color: var(--paper);
padding: 0.6rem 1rem;
font-family: var(--mono);
font-size: 0.8rem;
transition: top 0.2s ease;
}
.skip-link:focus {
top: 1rem;
}
/* ---------- Masthead ---------- */
.masthead__rule {
height: 2px;
background: var(--rule);
}
.masthead__rule--double {
height: 5px;
background: none;
border-top: 1.5px solid var(--rule);
border-bottom: 1.5px solid var(--rule);
margin-top: 0.6rem;
}
.masthead__top {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
flex-wrap: wrap;
padding: 0.5rem 0;
font-family: var(--mono);
font-size: 0.72rem;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--ink-soft);
}
.masthead__top .dot {
margin: 0 0.4rem;
color: var(--accent);
}
.masthead__price {
margin: 0;
color: var(--accent);
}
.masthead__logo {
display: block;
text-align: center;
padding: clamp(0.5rem, 2vw, 1.4rem) 0 0.3rem;
line-height: 0.92;
}
.masthead__logo-line {
display: block;
font-family: var(--serif);
font-weight: 400;
font-style: italic;
font-size: clamp(1rem, 3vw, 1.6rem);
letter-spacing: 0.02em;
color: var(--ink-soft);
}
.masthead__logo-line--big {
font-style: normal;
font-weight: 800;
font-size: clamp(2.6rem, 11vw, 6.5rem);
letter-spacing: -0.02em;
color: var(--ink);
font-variation-settings: "opsz" 144;
}
.masthead__nav {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: clamp(0.8rem, 4vw, 2.6rem);
padding: 0.7rem 0 0.5rem;
font-family: var(--mono);
font-size: 0.74rem;
letter-spacing: 0.16em;
text-transform: uppercase;
}
.masthead__nav a {
position: relative;
padding: 0.2rem 0;
color: var(--ink-soft);
transition: color 0.18s ease;
}
.masthead__nav a::after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: -2px;
height: 1.5px;
background: var(--accent);
transform: scaleX(0);
transform-origin: left;
transition: transform 0.22s ease;
}
.masthead__nav a:hover,
.masthead__nav a:focus-visible {
color: var(--ink);
}
.masthead__nav a:hover::after,
.masthead__nav a:focus-visible::after,
.masthead__nav a.is-current::after {
transform: scaleX(1);
}
.masthead__nav a.is-current {
color: var(--accent);
}
/* ---------- Shared kickers / rules ---------- */
.kicker {
font-family: var(--mono);
font-size: 0.72rem;
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--accent);
margin: 0 0 1rem;
}
.kicker--center {
text-align: center;
}
.rule-fancy {
display: flex;
align-items: center;
gap: 1.2rem;
margin: clamp(3rem, 7vw, 5.5rem) 0;
color: var(--accent);
}
.rule-fancy span:not(.rule-fancy__mark) {
flex: 1;
height: 1px;
background: var(--rule-faint);
}
.rule-fancy__mark {
flex: 0 0 auto;
font-size: 1.2rem;
}
.section-head {
margin: 0 0 clamp(1.6rem, 4vw, 2.6rem);
border-bottom: 1.5px solid var(--rule);
padding-bottom: 0.8rem;
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 1rem;
flex-wrap: wrap;
}
.section-head__title {
margin: 0;
font-family: var(--serif);
font-weight: 600;
font-size: clamp(1.7rem, 4.5vw, 2.9rem);
letter-spacing: -0.015em;
line-height: 1;
}
.section-head__meta {
margin: 0;
font-family: var(--mono);
font-size: 0.74rem;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--ink-faint);
}
/* ---------- Lede / hero ---------- */
.lede {
padding-top: clamp(2.4rem, 6vw, 4.5rem);
}
.lede__head {
margin: 0 0 clamp(1.6rem, 4vw, 2.6rem);
font-family: var(--serif);
font-weight: 600;
font-size: clamp(2.6rem, 8.5vw, 6rem);
line-height: 0.98;
letter-spacing: -0.03em;
max-width: 16ch;
}
.lede__head em {
color: var(--accent);
font-weight: 600;
}
.lede__body {
display: grid;
grid-template-columns: 1fr 1fr;
gap: clamp(1.4rem, 4vw, 3rem);
align-items: start;
}
.lede__col {
margin: 0;
max-width: var(--measure);
text-align: justify;
hyphens: auto;
}
.lede__sidenote {
grid-column: 1 / -1;
border-top: 1.5px solid var(--rule);
border-bottom: 1.5px solid var(--rule);
padding: 1rem 0;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1rem;
margin-top: 0.6rem;
}
.lede__sidenote p {
margin: 0;
font-family: var(--mono);
font-size: 0.78rem;
line-height: 1.4;
}
.sidenote__label {
display: block;
font-size: 0.64rem;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--ink-faint);
margin-bottom: 0.2rem;
}
.lede__sidenote em {
color: var(--accent);
}
.drop-cap::first-letter {
float: left;
font-family: var(--serif);
font-weight: 700;
font-size: 4.2em;
line-height: 0.74;
padding: 0.06em 0.12em 0 0;
margin-top: 0.06em;
color: var(--accent);
}
.lede__actions {
display: flex;
flex-wrap: wrap;
gap: 1rem;
margin-top: clamp(1.6rem, 4vw, 2.4rem);
}
/* ---------- Buttons ---------- */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.85rem 1.6rem;
font-family: var(--mono);
font-size: 0.78rem;
letter-spacing: 0.12em;
text-transform: uppercase;
cursor: pointer;
border: 1.5px solid var(--ink);
background: transparent;
color: var(--ink);
transition: background 0.18s ease, color 0.18s ease, transform 0.12s ease;
}
.btn:active {
transform: translateY(1px);
}
.btn--ink {
background: var(--ink);
color: var(--paper);
}
.btn--ink:hover,
.btn--ink:focus-visible {
background: var(--accent);
border-color: var(--accent);
}
.btn--ghost:hover,
.btn--ghost:focus-visible {
background: var(--ink);
color: var(--paper);
}
.btn.is-copied {
background: var(--accent);
border-color: var(--accent);
color: var(--paper);
}
.link-btn {
background: none;
border: none;
padding: 0;
font: inherit;
color: var(--accent);
text-decoration: underline;
text-underline-offset: 3px;
cursor: pointer;
}
/* ---------- Work / projects ---------- */
.work__filter {
display: flex;
flex-wrap: wrap;
gap: 0.6rem;
margin-bottom: clamp(1.4rem, 3vw, 2.2rem);
}
.chip {
font-family: var(--mono);
font-size: 0.72rem;
letter-spacing: 0.1em;
text-transform: uppercase;
padding: 0.4rem 0.95rem;
border: 1.5px solid var(--rule-faint);
background: transparent;
color: var(--ink-soft);
cursor: pointer;
transition: border-color 0.18s ease, color 0.18s ease, background 0.18s ease;
}
.chip:hover,
.chip:focus-visible {
border-color: var(--ink);
color: var(--ink);
}
.chip.is-active {
background: var(--ink);
border-color: var(--ink);
color: var(--paper);
}
.work__list {
list-style: none;
margin: 0;
padding: 0;
counter-reset: none;
}
.article {
display: grid;
grid-template-columns: auto 1fr auto;
gap: clamp(1rem, 3vw, 2.4rem);
align-items: start;
padding: clamp(1.4rem, 3vw, 2.2rem) 0;
border-top: 1px solid var(--rule-faint);
transition: background 0.2s ease, padding 0.2s ease;
cursor: default;
}
.article:last-child {
border-bottom: 1px solid var(--rule-faint);
}
.article:hover,
.article:focus-visible {
background:
linear-gradient(90deg, rgba(154, 43, 30, 0.05), transparent 70%);
outline: none;
}
.article.is-hidden {
display: none;
}
.article__no {
font-family: var(--serif);
font-weight: 300;
font-style: italic;
font-size: clamp(2rem, 6vw, 3.6rem);
line-height: 0.9;
color: var(--accent);
}
.article__body {
min-width: 0;
}
.article__tag {
margin: 0 0 0.4rem;
font-family: var(--mono);
font-size: 0.7rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--ink-faint);
}
.article__title {
margin: 0 0 0.6rem;
font-family: var(--serif);
font-weight: 600;
font-size: clamp(1.4rem, 4vw, 2.3rem);
line-height: 1.04;
letter-spacing: -0.01em;
transition: color 0.18s ease;
}
.article:hover .article__title,
.article:focus-visible .article__title {
color: var(--accent);
}
.article__dek {
margin: 0 0 0.7rem;
max-width: 56ch;
color: var(--ink-soft);
}
.article__footnote {
margin: 0;
font-family: var(--mono);
font-size: 0.72rem;
color: var(--ink-faint);
}
.article__footnote sup {
color: var(--accent);
}
.article__year {
font-family: var(--serif);
font-style: italic;
font-size: clamp(1.1rem, 3vw, 1.8rem);
color: var(--ink-faint);
white-space: nowrap;
}
.work__empty {
margin: 2rem 0 0;
font-family: var(--serif);
font-style: italic;
font-size: 1.25rem;
color: var(--ink-soft);
}
/* ---------- About ---------- */
.about__prose {
columns: 2;
column-gap: clamp(1.6rem, 4vw, 3.5rem);
column-rule: 1px solid var(--rule-faint);
text-align: justify;
hyphens: auto;
max-width: 64rem;
}
.about__prose p {
margin: 0 0 1.1rem;
}
.about__prose p:first-child {
margin-top: 0;
}
.pull-quote {
break-inside: avoid;
margin: 1.4rem 0;
padding-left: 1.2rem;
border-left: 3px solid var(--accent);
font-family: var(--serif);
font-style: italic;
font-size: 1.35rem;
line-height: 1.3;
color: var(--ink);
text-align: left;
hyphens: none;
}
/* ---------- Experience / CV ---------- */
.cv {
list-style: none;
margin: 0;
padding: 0;
}
.cv__row {
display: grid;
grid-template-columns: 10rem 1fr;
gap: clamp(1rem, 4vw, 3rem);
padding: 1.4rem 0;
border-top: 1px solid var(--rule-faint);
}
.cv__row:last-child {
border-bottom: 1px solid var(--rule-faint);
}
.cv__years {
font-family: var(--mono);
font-size: 0.82rem;
letter-spacing: 0.04em;
color: var(--accent);
padding-top: 0.3rem;
}
.cv__role {
margin: 0 0 0.2rem;
font-family: var(--serif);
font-weight: 600;
font-size: clamp(1.2rem, 3vw, 1.7rem);
line-height: 1.1;
}
.cv__org {
margin: 0 0 0.4rem;
font-style: italic;
color: var(--ink-soft);
}
.cv__note {
margin: 0;
max-width: 60ch;
color: var(--ink-soft);
font-size: 0.95rem;
}
/* ---------- Skills / index ---------- */
.skills__cols {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: clamp(1.6rem, 4vw, 3rem);
}
.skills__cat {
margin: 0 0 0.8rem;
font-family: var(--serif);
font-weight: 600;
font-style: italic;
font-size: 1.4rem;
color: var(--accent);
}
.skills__list {
list-style: none;
margin: 0;
padding: 0;
}
.skills__list li {
display: flex;
align-items: baseline;
gap: 0.4rem;
padding: 0.45rem 0;
border-bottom: 1px dotted var(--rule-faint);
font-size: 0.98rem;
}
.skills__list .leader {
flex: 1;
border-bottom: 1.5px dotted var(--ink-faint);
transform: translateY(-0.18em);
min-width: 1.5rem;
}
.skills__list em {
font-family: var(--mono);
font-style: normal;
font-size: 0.72rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--ink-faint);
white-space: nowrap;
}
/* ---------- Contact ---------- */
.contact {
text-align: center;
padding: clamp(1rem, 3vw, 2rem) 0;
}
.contact__head {
margin: 0 auto 1.2rem;
font-family: var(--serif);
font-weight: 600;
font-size: clamp(2rem, 6.5vw, 4rem);
line-height: 1.02;
letter-spacing: -0.02em;
max-width: 18ch;
}
.contact__lede {
margin: 0 auto 2rem;
max-width: 48ch;
color: var(--ink-soft);
font-size: 1.1rem;
}
.contact__actions {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 1rem;
margin-bottom: 2rem;
}
.contact__links {
list-style: none;
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 0.8rem;
margin: 0;
padding: 0;
font-family: var(--mono);
font-size: 0.76rem;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.contact__links a {
border-bottom: 1.5px solid transparent;
padding-bottom: 1px;
transition: border-color 0.18s ease, color 0.18s ease;
}
.contact__links a:hover,
.contact__links a:focus-visible {
color: var(--accent);
border-color: var(--accent);
}
/* ---------- Colophon ---------- */
.colophon {
margin-top: clamp(3rem, 7vw, 5rem);
padding-bottom: 2.5rem;
}
.colophon__grid {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 0.8rem 2rem;
padding-top: 1rem;
font-family: var(--mono);
font-size: 0.72rem;
letter-spacing: 0.04em;
color: var(--ink-faint);
}
.colophon__grid em {
color: var(--ink-soft);
}
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 1.6rem;
transform: translate(-50%, 1.5rem);
background: var(--ink);
color: var(--paper);
padding: 0.8rem 1.4rem;
font-family: var(--mono);
font-size: 0.78rem;
letter-spacing: 0.04em;
border-left: 3px solid var(--accent);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s ease, transform 0.25s ease;
z-index: 60;
max-width: calc(100vw - 2rem);
}
.toast.is-show {
opacity: 1;
transform: translate(-50%, 0);
}
/* ---------- Responsive ---------- */
@media (max-width: 820px) {
.lede__body {
grid-template-columns: 1fr;
}
.lede__sidenote {
grid-template-columns: repeat(2, 1fr);
}
.about__prose {
columns: 1;
}
.skills__cols {
grid-template-columns: 1fr;
}
}
@media (max-width: 560px) {
.masthead__top {
justify-content: center;
text-align: center;
}
.section-head {
flex-direction: column;
}
.article {
grid-template-columns: auto 1fr;
}
.article__year {
grid-column: 1 / -1;
text-align: right;
}
.lede__sidenote {
grid-template-columns: 1fr;
}
.cv__row {
grid-template-columns: 1fr;
gap: 0.4rem;
}
.btn {
width: 100%;
}
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
*,
*::before,
*::after {
transition-duration: 0.01ms !important;
animation-duration: 0.01ms !important;
}
}(function () {
"use strict";
var EMAIL = "hello@mayaokafor.studio";
/* ---------- Toast helper ---------- */
var toastEl = document.getElementById("toast");
var toastTimer;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("is-show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-show");
}, 2600);
}
/* ---------- Copy email to clipboard ---------- */
function copyEmail(btn, restoreLabelEl) {
var done = function () {
btn.classList.add("is-copied");
toast("Copied " + EMAIL + " to your clipboard.");
var original = restoreLabelEl ? restoreLabelEl.textContent : null;
if (restoreLabelEl) restoreLabelEl.textContent = "Copied ✓";
setTimeout(function () {
btn.classList.remove("is-copied");
if (restoreLabelEl && original) restoreLabelEl.textContent = original;
}, 1800);
};
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(EMAIL).then(done, fallback);
} else {
fallback();
}
function fallback() {
try {
var ta = document.createElement("textarea");
ta.value = EMAIL;
ta.setAttribute("readonly", "");
ta.style.position = "absolute";
ta.style.left = "-9999px";
document.body.appendChild(ta);
ta.select();
document.execCommand("copy");
document.body.removeChild(ta);
done();
} catch (e) {
toast("Write to " + EMAIL);
}
}
}
var copyBtn1 = document.getElementById("copy-email");
if (copyBtn1) {
copyBtn1.addEventListener("click", function () {
copyEmail(copyBtn1, null);
});
}
var copyBtn2 = document.getElementById("copy-email-2");
var copyLabel = document.getElementById("copy-email-label");
if (copyBtn2) {
copyBtn2.addEventListener("click", function () {
copyEmail(copyBtn2, copyLabel);
});
}
/* ---------- Work filter ---------- */
var chips = Array.prototype.slice.call(document.querySelectorAll(".chip[data-filter]"));
var articles = Array.prototype.slice.call(document.querySelectorAll(".article[data-cat]"));
var emptyEl = document.getElementById("work-empty");
var resetBtn = document.getElementById("reset-filter");
function applyFilter(filter) {
var visible = 0;
articles.forEach(function (a) {
var match = filter === "all" || a.getAttribute("data-cat") === filter;
a.classList.toggle("is-hidden", !match);
if (match) visible++;
});
chips.forEach(function (c) {
var on = c.getAttribute("data-filter") === filter;
c.classList.toggle("is-active", on);
c.setAttribute("aria-pressed", on ? "true" : "false");
});
if (emptyEl) emptyEl.hidden = visible !== 0;
var label = filter === "all" ? "all works" : filter + " works";
toast(
visible === 0
? "No works filed under " + filter + "."
: "Showing " + visible + " " + label + "."
);
}
chips.forEach(function (c) {
c.addEventListener("click", function () {
applyFilter(c.getAttribute("data-filter"));
});
});
if (resetBtn) {
resetBtn.addEventListener("click", function () {
applyFilter("all");
});
}
/* Make article rows keyboard-activatable (Enter/Space -> toast title) */
articles.forEach(function (a) {
a.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
var t = a.querySelector(".article__title");
if (t) toast("Read more: " + t.textContent.trim());
}
});
});
/* ---------- Scroll-spy nav highlight ---------- */
var navLinks = Array.prototype.slice.call(document.querySelectorAll(".masthead__nav a[href^='#']"));
var sections = navLinks
.map(function (link) {
var id = link.getAttribute("href").slice(1);
var el = document.getElementById(id);
return el ? { link: link, el: el } : null;
})
.filter(Boolean);
if ("IntersectionObserver" in window && sections.length) {
var spy = new IntersectionObserver(
function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
navLinks.forEach(function (l) {
l.classList.remove("is-current");
});
var target = entry.target.getAttribute("id");
var match = sections.filter(function (s) {
return s.el.getAttribute("id") === target;
})[0];
if (match) match.link.classList.add("is-current");
}
});
},
{ rootMargin: "-45% 0px -50% 0px", threshold: 0 }
);
sections.forEach(function (s) {
spy.observe(s.el);
});
}
/* ---------- Issue date + year ---------- */
var yearEl = document.getElementById("year");
if (yearEl) yearEl.textContent = String(new Date().getFullYear());
var issueEl = document.getElementById("issue-date");
if (issueEl) {
var months = [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];
var now = new Date();
issueEl.textContent = months[now.getMonth()] + " " + now.getFullYear();
}
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Maya Okafor — Editorial Portfolio</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=Fraunces:ital,opsz,wght@0,9..144,300..900;1,9..144,300..900&family=Spectral:ital,wght@0,400;0,500;1,400&family=IBM+Plex+Mono:wght@400;500&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip-link" href="#work">Skip to work</a>
<div class="paper">
<!-- Masthead -->
<header class="masthead" role="banner">
<div class="masthead__rule" aria-hidden="true"></div>
<div class="masthead__top">
<p class="masthead__dateline">
<span id="issue-date">Vol. IX — No. 04</span>
<span class="dot" aria-hidden="true">·</span>
<span>An independent design quarterly</span>
</p>
<p class="masthead__price">Folio №24</p>
</div>
<a class="masthead__logo" href="#top" id="top" aria-label="Maya Okafor, home">
<span class="masthead__logo-line">The Okafor</span>
<span class="masthead__logo-line masthead__logo-line--big">Review</span>
</a>
<nav class="masthead__nav" aria-label="Sections">
<a href="#work">Work</a>
<a href="#about">About</a>
<a href="#experience">Experience</a>
<a href="#skills">Skills</a>
<a href="#contact">Contact</a>
</nav>
<div class="masthead__rule masthead__rule--double" aria-hidden="true"></div>
</header>
<main>
<!-- Hero / Lede -->
<section class="lede" aria-labelledby="lede-head">
<p class="kicker">Profile · Product Design</p>
<h1 class="lede__head" id="lede-head">
Maya Okafor designs
<em>quiet interfaces</em>
for loud problems.
</h1>
<div class="lede__body">
<p class="lede__col drop-cap">
For twelve years she has worked at the seam where research meets
craft — turning tangled enterprise tools into products people forget
they are even using. Her practice spans fintech ledgers, civic
services and the occasional stubborn calendar.
</p>
<p class="lede__col">
She believes the best design recedes: a typeface that disappears,
a flow with no dead ends, a screen that answers the question before
it is asked. This page is a small anthology of that belief, set in
type and arranged like the magazine she keeps meaning to publish.
</p>
<aside class="lede__sidenote" aria-label="Quick facts">
<p><span class="sidenote__label">Based in</span> Lisbon & remote</p>
<p><span class="sidenote__label">Working since</span> 2013</p>
<p><span class="sidenote__label">Now</span> Principal Designer, Meridian</p>
<p><span class="sidenote__label">Status</span> <em>Open to select work</em></p>
</aside>
</div>
<div class="lede__actions">
<button type="button" class="btn btn--ink" id="copy-email">
Commission a piece
</button>
<a class="btn btn--ghost" href="#work">Read the portfolio ↓</a>
</div>
</section>
<div class="rule-fancy" aria-hidden="true">
<span></span><span class="rule-fancy__mark">❧</span><span></span>
</div>
<!-- Work / Projects -->
<section class="work" id="work" aria-labelledby="work-head">
<div class="section-head">
<h2 class="section-head__title" id="work-head">Selected Works</h2>
<p class="section-head__meta">Six projects · 2018 — 2026 · filter below</p>
</div>
<div class="work__filter" role="group" aria-label="Filter works by discipline">
<button type="button" class="chip is-active" data-filter="all" aria-pressed="true">All</button>
<button type="button" class="chip" data-filter="fintech" aria-pressed="false">Fintech</button>
<button type="button" class="chip" data-filter="civic" aria-pressed="false">Civic</button>
<button type="button" class="chip" data-filter="brand" aria-pressed="false">Brand</button>
<button type="button" class="chip" data-filter="research" aria-pressed="false">Research</button>
</div>
<ol class="work__list" id="work-list">
<li class="article" data-cat="fintech" tabindex="0">
<span class="article__no" aria-hidden="true">01</span>
<div class="article__body">
<p class="article__tag">Fintech · Lead Designer</p>
<h3 class="article__title">The Ledger That Reads Aloud</h3>
<p class="article__dek">
A reconciliation tool for Meridian that turned a 40-column
spreadsheet into a guided narrative — accountants close the
books 60% faster and, for once, enjoy it.
</p>
<p class="article__footnote">
<sup>1</sup> Rolled out to 4,200 finance teams · IF Design Award, 2025
</p>
</div>
<span class="article__year" aria-hidden="true">’25</span>
</li>
<li class="article" data-cat="civic" tabindex="0">
<span class="article__no" aria-hidden="true">02</span>
<div class="article__body">
<p class="article__tag">Civic · Service Design</p>
<h3 class="article__title">Renewing a Passport in Three Taps</h3>
<p class="article__dek">
A ground-up redesign of a national identity service. We cut a
34-field form to a conversational flow and dropped the
abandonment rate from 71% to 12%.
</p>
<p class="article__footnote">
<sup>2</sup> 1.1M citizens served in year one
</p>
</div>
<span class="article__year" aria-hidden="true">’24</span>
</li>
<li class="article" data-cat="brand" tabindex="0">
<span class="article__no" aria-hidden="true">03</span>
<div class="article__body">
<p class="article__tag">Brand · Identity & System</p>
<h3 class="article__title">A Type System for Aster Bank</h3>
<p class="article__dek">
One variable typeface, eleven products, zero inconsistencies.
The voice scales from a watch face to a billboard without ever
raising its tone.
</p>
<p class="article__footnote">
<sup>3</sup> 11 product teams · 1 source of truth
</p>
</div>
<span class="article__year" aria-hidden="true">’23</span>
</li>
<li class="article" data-cat="research" tabindex="0">
<span class="article__no" aria-hidden="true">04</span>
<div class="article__body">
<p class="article__tag">Research · Mixed Methods</p>
<h3 class="article__title">Field Notes from the Night Shift</h3>
<p class="article__dek">
Six weeks shadowing logistics dispatchers to understand how
people make decisions when they are tired, interrupted and
accountable. The report changed our roadmap entirely.
</p>
<p class="article__footnote">
<sup>4</sup> 28 interviews · published internally, 92 pages
</p>
</div>
<span class="article__year" aria-hidden="true">’22</span>
</li>
<li class="article" data-cat="fintech" tabindex="0">
<span class="article__no" aria-hidden="true">05</span>
<div class="article__body">
<p class="article__tag">Fintech · Product Design</p>
<h3 class="article__title">Splitting Bills Without the Math</h3>
<p class="article__dek">
A consumer app where the hard part — who owes whom — simply
vanishes. Reached the App Store front page on a budget of one
designer and a stubborn idea.
</p>
<p class="article__footnote">
<sup>5</sup> 4.8★ · 300k downloads in six months
</p>
</div>
<span class="article__year" aria-hidden="true">’20</span>
</li>
<li class="article" data-cat="civic" tabindex="0">
<span class="article__no" aria-hidden="true">06</span>
<div class="article__body">
<p class="article__tag">Civic · Accessibility</p>
<h3 class="article__title">An Election Site Anyone Can Read</h3>
<p class="article__dek">
A voter-information portal designed first for screen readers,
low bandwidth and high stress — then made beautiful. WCAG AAA,
no exceptions, no excuses.
</p>
<p class="article__footnote">
<sup>6</sup> AAA conformance · used in 14 districts
</p>
</div>
<span class="article__year" aria-hidden="true">’18</span>
</li>
</ol>
<p class="work__empty" id="work-empty" hidden>
Nothing filed under that heading — <button type="button" class="link-btn" id="reset-filter">show all works</button>.
</p>
</section>
<div class="rule-fancy" aria-hidden="true">
<span></span><span class="rule-fancy__mark">❦</span><span></span>
</div>
<!-- About -->
<section class="about" id="about" aria-labelledby="about-head">
<div class="section-head">
<h2 class="section-head__title" id="about-head">A Note from the Author</h2>
<p class="section-head__meta">On process, taste, and the long game</p>
</div>
<div class="about__prose">
<p class="drop-cap">
I came to design sideways — through letterpress in a basement print
shop, then linguistics, then a refusal to choose. The thread running
through all of it is the same: language is an interface, and an
interface is just language that moved. I spend most of my days
arranging words and rectangles until the seams disappear.
</p>
<p>
My method is unglamorous. I read everything. I interview the people
who will actually use the thing, then I interview them again. I draw
on paper before I draw on screens, and I delete more than I keep. The
work that survives is usually the work nobody notices — which is, I
think, the point.
</p>
<blockquote class="pull-quote">
“Good typography is invisible. So is good design. The applause goes
to the work that reads like it was never designed at all.”
</blockquote>
<p>
Off the clock you will find me setting type for poetry chapbooks,
losing at chess, and maintaining a small open-source font with an
unreasonable number of ligatures. I write a slow newsletter about
craft for a few hundred patient subscribers.
</p>
</div>
</section>
<div class="rule-fancy" aria-hidden="true">
<span></span><span class="rule-fancy__mark">✦</span><span></span>
</div>
<!-- Experience -->
<section class="experience" id="experience" aria-labelledby="exp-head">
<div class="section-head">
<h2 class="section-head__title" id="exp-head">Curriculum</h2>
<p class="section-head__meta">An abbreviated record of employment</p>
</div>
<ol class="cv">
<li class="cv__row">
<span class="cv__years">2022 — Now</span>
<div class="cv__detail">
<h3 class="cv__role">Principal Designer</h3>
<p class="cv__org">Meridian — financial infrastructure</p>
<p class="cv__note">Leads a team of nine. Owns the design language across ledger, payments and reporting products.</p>
</div>
</li>
<li class="cv__row">
<span class="cv__years">2019 — 2022</span>
<div class="cv__detail">
<h3 class="cv__role">Senior Product Designer</h3>
<p class="cv__org">Civita — public-sector services studio</p>
<p class="cv__note">Redesigned national identity and voting services for accessibility-first delivery.</p>
</div>
</li>
<li class="cv__row">
<span class="cv__years">2016 — 2019</span>
<div class="cv__detail">
<h3 class="cv__role">Designer & Researcher</h3>
<p class="cv__org">Foundry No. 6 — independent design practice</p>
<p class="cv__note">Brand systems, type, and mixed-methods research for early-stage clients.</p>
</div>
</li>
<li class="cv__row">
<span class="cv__years">2013 — 2016</span>
<div class="cv__detail">
<h3 class="cv__role">Junior Designer</h3>
<p class="cv__org">Press & Pixel — letterpress + digital studio</p>
<p class="cv__note">Set type by hand in the morning, by code in the afternoon.</p>
</div>
</li>
</ol>
</section>
<div class="rule-fancy" aria-hidden="true">
<span></span><span class="rule-fancy__mark">❧</span><span></span>
</div>
<!-- Skills -->
<section class="skills" id="skills" aria-labelledby="skills-head">
<div class="section-head">
<h2 class="section-head__title" id="skills-head">Index of Disciplines</h2>
<p class="section-head__meta">Listed as in a back-of-book index</p>
</div>
<div class="skills__cols">
<div class="skills__group">
<h3 class="skills__cat">Craft</h3>
<ul class="skills__list">
<li>Interaction design <span class="leader" aria-hidden="true"></span> <em>expert</em></li>
<li>Typography & type systems <span class="leader" aria-hidden="true"></span> <em>expert</em></li>
<li>Design systems <span class="leader" aria-hidden="true"></span> <em>expert</em></li>
<li>Prototyping <span class="leader" aria-hidden="true"></span> <em>fluent</em></li>
</ul>
</div>
<div class="skills__group">
<h3 class="skills__cat">Research</h3>
<ul class="skills__list">
<li>Generative interviews <span class="leader" aria-hidden="true"></span> <em>expert</em></li>
<li>Usability testing <span class="leader" aria-hidden="true"></span> <em>fluent</em></li>
<li>Service blueprinting <span class="leader" aria-hidden="true"></span> <em>fluent</em></li>
<li>Synthesis & reporting <span class="leader" aria-hidden="true"></span> <em>expert</em></li>
</ul>
</div>
<div class="skills__group">
<h3 class="skills__cat">Tools</h3>
<ul class="skills__list">
<li>Figma <span class="leader" aria-hidden="true"></span> <em>daily</em></li>
<li>HTML / CSS <span class="leader" aria-hidden="true"></span> <em>fluent</em></li>
<li>Variable fonts <span class="leader" aria-hidden="true"></span> <em>fluent</em></li>
<li>Letterpress <span class="leader" aria-hidden="true"></span> <em>weekends</em></li>
</ul>
</div>
</div>
</section>
<div class="rule-fancy" aria-hidden="true">
<span></span><span class="rule-fancy__mark">✶</span><span></span>
</div>
<!-- Contact -->
<section class="contact" id="contact" aria-labelledby="contact-head">
<p class="kicker kicker--center">Correspondence</p>
<h2 class="contact__head" id="contact-head">
Have a stubborn problem<br />that deserves quiet design?
</h2>
<p class="contact__lede">
I take on a handful of engagements a year. Write with a sentence or two
about the work — I read everything.
</p>
<div class="contact__actions">
<button type="button" class="btn btn--ink" id="copy-email-2">
<span id="copy-email-label">hello@mayaokafor.studio</span>
</button>
<a class="btn btn--ghost" href="#top">Back to the top ↑</a>
</div>
<ul class="contact__links">
<li><a href="#contact">Read.cv</a></li>
<li aria-hidden="true">·</li>
<li><a href="#contact">LinkedIn</a></li>
<li aria-hidden="true">·</li>
<li><a href="#contact">The Newsletter</a></li>
</ul>
</section>
</main>
<footer class="colophon" role="contentinfo">
<div class="masthead__rule masthead__rule--double" aria-hidden="true"></div>
<div class="colophon__grid">
<p>Set in <em>Fraunces</em>, <em>Spectral</em> & <em>IBM Plex Mono</em>.</p>
<p>The Okafor Review — printed nowhere, read everywhere.</p>
<p>© <span id="year">2026</span> Maya Okafor · A fictional portfolio.</p>
</div>
</footer>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Editorial / Typographic Portfolio
A complete single-page portfolio for fictional product designer Maya Okafor, re-skinned as an independent print magazine — “The Okafor Review.” It runs entirely on typography: a newspaper-style masthead with a live dateline and double rules, an oversized Fraunces headline, a drop-capped multi-column lede with a ruled fact strip, six numbered project articles carrying footnote-style captions, a justified two-column about with a pull quote, a dotted-leader skills index set like the back of a book, a ruled CV and a centered contact page. The palette is cream paper, near-black ink and a single oxblood accent; imagery is replaced entirely by type, rules and ornamental dividers.
Every interaction works in vanilla JS. The Selected Works section filters by discipline — All, Fintech, Civic, Brand, Research — updating aria-pressed, showing a graceful empty state with a reset link, and announcing the count via a toast. The two “commission” buttons copy the email to the clipboard (with an execCommand fallback) and confirm in place; a scroll-spy underlines the current masthead nav item with an IntersectionObserver; and the masthead dateline plus colophon year fill in from the real date.
The layout collapses gracefully: the lede and skills drop to one column, the about prose un-columns, and project rows reflow with the year wrapping below on phones down to ~360px. It keeps WCAG AA contrast, visible :focus-visible rings, a skip link, keyboard-activatable project rows, and honors prefers-reduced-motion.
Illustrative portfolio — fictional person and projects.