Portfolio — Brutalist Portfolio
A full one-page brutalist portfolio for a fictional product designer, built on raw paper white, harsh black borders, and one clashing electric accent. Ships a scrolling marquee, count-up stats, filterable project grid, animated skill bars, a validated contact form, and a loud INVERT theme toggle. Mono and sans type mix, hard 3px borders, zero rounded corners, and snappy offset hovers throughout. Self-contained HTML, CSS, and vanilla JS — no frameworks, no external images.
MCP
Code
/* ============================================================
BRUTALIST PORTFOLIO — Maya Okafor
Raw white + harsh black borders + one clashing accent.
============================================================ */
:root {
--bg: #f4f3ee; /* raw paper white */
--ink: #0a0a0a; /* harsh black */
--accent: #ff3b00; /* clashing electric orange-red */
--accent-2: #2b34ff; /* secondary clash */
--line: 3px; /* default hard border */
--line-fat: 5px;
--shadow: 8px 8px 0 var(--ink);
--shadow-accent: 8px 8px 0 var(--accent);
--gap: 28px;
--maxw: 1180px;
--mono: "Space Mono", ui-monospace, "SFMono-Regular", Menlo, monospace;
--sans: "Archivo", system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
--display: "Archivo Black", var(--sans);
}
/* INVERTED THEME (toggled) */
body.invert {
--bg: #0a0a0a;
--ink: #f4f3ee;
--accent: #d4ff00;
--accent-2: #ff3b00;
--shadow: 8px 8px 0 var(--ink);
--shadow-accent: 8px 8px 0 var(--accent);
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
}
body {
margin: 0;
background:
repeating-linear-gradient(0deg, transparent 0 39px, rgba(0,0,0,.035) 39px 40px),
repeating-linear-gradient(90deg, transparent 0 39px, rgba(0,0,0,.035) 39px 40px),
var(--bg);
color: var(--ink);
font-family: var(--sans);
line-height: 1.5;
font-weight: 500;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
transition: background-color .15s steps(2), color .15s steps(2);
}
img, svg { max-width: 100%; display: block; }
a { color: inherit; }
.skip-link {
position: absolute;
left: -999px;
top: 0;
background: var(--ink);
color: var(--bg);
padding: 10px 16px;
font: 700 13px/1 var(--mono);
z-index: 999;
}
.skip-link:focus { left: 8px; top: 8px; }
:focus-visible {
outline: var(--line) solid var(--accent-2);
outline-offset: 2px;
}
/* ---------- MARQUEE ---------- */
.marquee {
background: var(--accent);
color: var(--ink);
border-bottom: var(--line-fat) solid var(--ink);
overflow: hidden;
white-space: nowrap;
}
.marquee__track {
display: inline-flex;
align-items: center;
gap: 22px;
padding: 9px 0;
font: 700 14px/1 var(--mono);
letter-spacing: .04em;
animation: scroll 22s linear infinite;
will-change: transform;
}
.marquee__track .dot { font-size: 9px; }
@keyframes scroll {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
.marquee:hover .marquee__track,
.marquee__track.paused { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) {
.marquee__track { animation: none; }
}
/* ---------- HEADER ---------- */
.site-head {
position: sticky;
top: 0;
z-index: 50;
display: flex;
align-items: stretch;
justify-content: space-between;
gap: 12px;
background: var(--bg);
border-bottom: var(--line-fat) solid var(--ink);
}
.brand {
font: 400 22px/1 var(--display);
letter-spacing: -.02em;
text-decoration: none;
padding: 16px 20px;
border-right: var(--line) solid var(--ink);
display: flex;
align-items: center;
}
.brand__caret { color: var(--accent); }
.nav {
display: flex;
align-items: stretch;
margin-left: auto;
overflow-x: auto;
}
.nav a {
font: 700 13px/1 var(--mono);
letter-spacing: .03em;
text-decoration: none;
padding: 0 16px;
display: flex;
align-items: center;
border-left: var(--line) solid var(--ink);
transition: background .1s steps(1), color .1s steps(1);
}
.nav a:hover { background: var(--accent); color: var(--ink); }
.theme-btn {
font: 700 13px/1 var(--mono);
letter-spacing: .05em;
background: var(--ink);
color: var(--bg);
border: 0;
border-left: var(--line) solid var(--ink);
padding: 0 18px;
cursor: pointer;
transition: background .1s, color .1s;
}
.theme-btn:hover { background: var(--accent); color: var(--ink); }
.theme-btn:active { transform: translate(2px, 2px); }
/* ---------- LAYOUT ---------- */
.hero,
.section {
max-width: var(--maxw);
margin: 0 auto;
padding: 64px 24px;
}
.section { border-top: var(--line) dashed var(--ink); }
.section--invert {
max-width: none;
background: var(--ink);
color: var(--bg);
border-top: var(--line-fat) solid var(--ink);
border-bottom: var(--line-fat) solid var(--ink);
}
.section--invert > * {
max-width: var(--maxw);
margin-left: auto;
margin-right: auto;
}
.section--invert .section__title { color: var(--accent); }
.section__title {
font: 400 clamp(1.6rem, 4vw, 2.6rem)/.95 var(--display);
letter-spacing: -.02em;
margin: 0 0 36px;
text-transform: uppercase;
}
.section__head {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: space-between;
gap: 18px;
margin-bottom: 36px;
}
.section__head .section__title { margin: 0; }
/* ---------- HERO ---------- */
.hero { padding-top: 72px; padding-bottom: 72px; }
.hero__grid {
display: grid;
grid-template-columns: 1.6fr 1fr;
gap: var(--gap);
align-items: start;
}
.kicker {
font: 700 12px/1.4 var(--mono);
letter-spacing: .08em;
margin: 0 0 18px;
text-transform: uppercase;
}
.hero__title {
font: 400 clamp(3.4rem, 13vw, 8.5rem)/.82 var(--display);
letter-spacing: -.03em;
margin: 0 0 26px;
text-transform: uppercase;
}
.blink { color: var(--accent); animation: blink 1.1s steps(1) infinite; }
@keyframes blink { 50% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .blink { animation: none; } }
.hero__sub {
max-width: 46ch;
font-size: 1.08rem;
margin: 0 0 28px;
border-left: var(--line) solid var(--accent);
padding-left: 16px;
}
.hero__cta { display: flex; flex-wrap: wrap; gap: 14px; }
.btn {
font: 700 14px/1 var(--mono);
letter-spacing: .04em;
text-decoration: none;
padding: 16px 22px;
border: var(--line) solid var(--ink);
background: var(--bg);
color: var(--ink);
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
transition: transform .08s steps(1), box-shadow .08s steps(1), background .1s;
}
.btn--solid { background: var(--accent); box-shadow: var(--shadow); }
.btn--ghost { box-shadow: var(--shadow); }
.btn:hover { transform: translate(-3px, -3px); box-shadow: 11px 11px 0 var(--ink); }
.btn:active { transform: translate(2px, 2px); box-shadow: 2px 2px 0 var(--ink); }
.btn--block { width: 100%; justify-content: center; }
/* HERO CARD */
.hero__card {
border: var(--line) solid var(--ink);
box-shadow: var(--shadow-accent);
background: var(--bg);
}
.stat-block {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
padding: 18px 20px;
border-bottom: var(--line) solid var(--ink);
}
.stat-block__n {
font: 400 2.6rem/1 var(--display);
letter-spacing: -.03em;
color: var(--accent);
}
.stat-block__l {
font: 700 11px/1.3 var(--mono);
letter-spacing: .05em;
text-align: right;
max-width: 12ch;
}
.hero__avail {
display: flex;
align-items: center;
gap: 10px;
padding: 16px 20px;
font: 700 12px/1 var(--mono);
letter-spacing: .05em;
background: var(--ink);
color: var(--bg);
}
.pulse {
width: 11px; height: 11px;
background: var(--accent);
display: inline-block;
animation: pulse 1.3s steps(1) infinite;
}
@keyframes pulse { 50% { opacity: .2; } }
@media (prefers-reduced-motion: reduce) { .pulse { animation: none; } }
/* ---------- FILTERS ---------- */
.filters { display: flex; flex-wrap: wrap; gap: 0; }
.chip {
font: 700 12px/1 var(--mono);
letter-spacing: .04em;
padding: 11px 16px;
border: var(--line) solid var(--ink);
border-right-width: 0;
background: var(--bg);
color: var(--ink);
cursor: pointer;
transition: background .1s steps(1);
}
.chip:last-child { border-right-width: var(--line); }
.chip:hover { background: var(--accent-2); color: var(--bg); }
.chip.is-active { background: var(--ink); color: var(--bg); }
/* ---------- WORK GRID ---------- */
.work-grid {
list-style: none;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--gap);
}
.work-card {
border: var(--line) solid var(--ink);
background: var(--bg);
display: flex;
flex-direction: column;
transition: transform .08s steps(1), box-shadow .08s steps(1);
outline-offset: 4px;
}
.work-card:hover,
.work-card:focus-visible {
transform: translate(-4px, -4px);
box-shadow: 12px 12px 0 var(--accent);
z-index: 2;
}
.work-card.is-hidden { display: none; }
.work-card__visual {
aspect-ratio: 16 / 10;
border-bottom: var(--line) solid var(--ink);
display: flex;
align-items: center;
justify-content: center;
font: 400 2.4rem/1 var(--display);
letter-spacing: .04em;
color: var(--ink);
position: relative;
overflow: hidden;
}
.work-card__visual span {
position: relative;
z-index: 1;
mix-blend-mode: difference;
color: #fff;
}
.visual--a { background:
repeating-linear-gradient(45deg, var(--accent) 0 14px, var(--bg) 14px 28px); }
.visual--b { background:
radial-gradient(circle at 30% 30%, var(--accent-2) 0 18%, transparent 18%),
radial-gradient(circle at 70% 70%, var(--accent) 0 18%, transparent 18%),
var(--bg); }
.visual--c { background:
repeating-linear-gradient(0deg, var(--ink) 0 4px, var(--bg) 4px 18px); }
.visual--d { background:
conic-gradient(from 0deg, var(--accent), var(--accent-2), var(--ink), var(--accent)); }
.visual--e { background:
linear-gradient(135deg, var(--bg) 0 50%, var(--accent) 50% 100%); }
.visual--f { background:
repeating-linear-gradient(90deg, var(--accent-2) 0 10px, var(--bg) 10px 20px); }
.work-card__body { padding: 18px 20px 22px; flex: 1; }
.work-card__num {
font: 700 12px/1 var(--mono);
color: var(--accent);
letter-spacing: .1em;
}
.work-card__body h3 {
font: 400 1.4rem/1 var(--display);
letter-spacing: -.01em;
margin: 8px 0 10px;
text-transform: uppercase;
}
.work-card__body p { margin: 0 0 16px; font-size: .95rem; }
.work-card__meta {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.work-card__meta span {
font: 700 10px/1 var(--mono);
letter-spacing: .06em;
border: 2px solid var(--ink);
padding: 5px 7px;
}
.work-empty {
grid-column: 1 / -1;
font: 700 1rem/1.4 var(--mono);
text-align: center;
border: var(--line) dashed var(--ink);
padding: 40px;
margin-top: 0;
}
/* ---------- ABOUT ---------- */
.about-grid {
display: grid;
grid-template-columns: 1.4fr .8fr;
gap: 48px;
align-items: start;
}
.about-lead {
font: 400 1.5rem/1.35 var(--sans);
font-weight: 700;
margin: 0 0 20px;
}
.about-text { font-size: 1.02rem; margin: 0 0 24px; }
.about-list { list-style: none; margin: 0; padding: 0; }
.about-list li {
font: 700 14px/1 var(--mono);
letter-spacing: .02em;
padding: 14px 0;
border-top: 2px solid currentColor;
display: flex;
gap: 12px;
}
.about-list li:last-child { border-bottom: 2px solid currentColor; }
.about-list span { color: var(--accent); }
.about-portrait { color: var(--accent); }
.about-portrait svg { width: 100%; height: auto; }
.about-portrait__cap {
font: 700 11px/1.4 var(--mono);
letter-spacing: .04em;
color: var(--bg);
margin: 14px 0 0;
border: 2px solid currentColor;
padding: 8px 10px;
}
/* ---------- TIMELINE ---------- */
.timeline { list-style: none; margin: 0; padding: 0; counter-reset: tl; }
.tl-item {
display: grid;
grid-template-columns: 160px 1fr;
gap: 24px;
padding: 26px 0;
border-top: var(--line) solid var(--ink);
align-items: start;
}
.tl-item:last-child { border-bottom: var(--line) solid var(--ink); }
.tl-year {
font: 700 14px/1.2 var(--mono);
letter-spacing: .02em;
background: var(--accent);
color: var(--ink);
padding: 8px 10px;
align-self: start;
border: 2px solid var(--ink);
}
.tl-body h3 {
font: 400 1.5rem/1 var(--display);
text-transform: uppercase;
letter-spacing: -.01em;
margin: 0 0 4px;
}
.tl-co {
font: 700 13px/1 var(--mono);
letter-spacing: .04em;
color: var(--accent);
margin: 0 0 10px;
}
.tl-body p:last-child { margin: 0; max-width: 60ch; }
/* ---------- SKILLS ---------- */
.skills-wrap {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 48px;
}
.skill-col__h {
font: 700 13px/1 var(--mono);
letter-spacing: .08em;
margin: 0 0 18px;
padding-bottom: 8px;
border-bottom: var(--line) solid var(--ink);
}
.skill-col__h + .skill-col__h { margin-top: 28px; }
.skill-bars { list-style: none; margin: 0; padding: 0; display: grid; gap: 18px; }
.skill-bars__l {
display: flex;
justify-content: space-between;
font: 700 12px/1 var(--mono);
letter-spacing: .04em;
margin-bottom: 7px;
}
.skill-bars__l b { color: var(--accent); }
.bar {
display: block;
height: 16px;
border: var(--line) solid var(--ink);
background:
repeating-linear-gradient(45deg, rgba(0,0,0,.08) 0 4px, transparent 4px 8px);
overflow: hidden;
}
.bar i {
display: block;
height: 100%;
width: 0;
background: var(--accent);
transition: width .9s cubic-bezier(.2,.9,.2,1);
}
.bar.is-filled i { width: var(--w); }
.tag-cloud {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
gap: 0;
}
.tag-cloud li {
font: 700 12px/1 var(--mono);
letter-spacing: .03em;
border: var(--line) solid var(--ink);
margin: -1.5px 0 0 -1.5px;
padding: 12px 14px;
background: var(--bg);
transition: background .1s steps(1), color .1s steps(1);
cursor: default;
}
.tag-cloud li:hover { background: var(--accent); }
.tag-cloud--alt li:hover { background: var(--accent-2); color: var(--bg); }
/* ---------- CONTACT ---------- */
.contact-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 48px;
align-items: start;
}
.contact-lead {
font: 400 1.8rem/1.2 var(--display);
text-transform: uppercase;
margin: 0 0 26px;
}
.contact-row {
display: flex;
gap: 16px;
flex-wrap: wrap;
font: 700 14px/1.6 var(--mono);
letter-spacing: .02em;
padding: 14px 0;
border-top: 2px solid currentColor;
}
.contact-row span { color: var(--accent); min-width: 70px; }
.contact-row a { text-decoration: underline; text-underline-offset: 3px; }
.contact-row a:hover { background: var(--accent); color: var(--ink); text-decoration: none; }
.contact-form {
border: var(--line) solid var(--accent);
background: var(--ink);
padding: 24px;
}
.field { margin-bottom: 18px; }
.field label {
display: block;
font: 700 12px/1 var(--mono);
letter-spacing: .06em;
color: var(--accent);
margin-bottom: 8px;
}
.field input,
.field textarea {
width: 100%;
font: 500 15px/1.4 var(--sans);
color: var(--ink);
background: var(--bg);
border: var(--line) solid var(--bg);
padding: 12px 14px;
border-radius: 0;
}
.field textarea { resize: vertical; }
.field input:focus,
.field textarea:focus {
outline: none;
border-color: var(--accent);
}
.field.has-err input,
.field.has-err textarea { border-color: var(--accent); }
.err {
display: block;
font: 700 11px/1.4 var(--mono);
color: var(--accent);
margin-top: 6px;
min-height: 1px;
}
/* ---------- FOOTER ---------- */
.site-foot {
border-top: var(--line-fat) solid var(--ink);
background: var(--accent);
color: var(--ink);
display: flex;
flex-wrap: wrap;
gap: 14px;
align-items: center;
justify-content: space-between;
padding: 22px 24px;
font: 700 12px/1.4 var(--mono);
letter-spacing: .03em;
}
.top-btn {
font: 700 12px/1 var(--mono);
letter-spacing: .04em;
background: var(--ink);
color: var(--bg);
border: var(--line) solid var(--ink);
padding: 11px 16px;
cursor: pointer;
transition: transform .08s steps(1);
}
.top-btn:hover { transform: translate(-2px, -2px); box-shadow: 4px 4px 0 var(--bg); }
.top-btn:active { transform: translate(1px, 1px); }
/* ---------- TOAST ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translateX(-50%) translateY(140%);
background: var(--ink);
color: var(--bg);
border: var(--line) solid var(--accent);
box-shadow: var(--shadow-accent);
padding: 14px 20px;
font: 700 13px/1.3 var(--mono);
letter-spacing: .02em;
max-width: min(90vw, 420px);
z-index: 200;
transition: transform .2s cubic-bezier(.2,.9,.2,1);
pointer-events: none;
}
.toast.show { transform: translateX(-50%) translateY(0); }
/* ---------- RESPONSIVE ---------- */
@media (max-width: 920px) {
.hero__grid { grid-template-columns: 1fr; }
.work-grid { grid-template-columns: repeat(2, 1fr); }
.about-grid,
.skills-wrap,
.contact-grid { grid-template-columns: 1fr; gap: 32px; }
.about-portrait { max-width: 280px; }
}
@media (max-width: 640px) {
.site-head { flex-wrap: wrap; }
.brand { border-bottom: var(--line) solid var(--ink); flex: 1 0 100%; }
.nav { width: 100%; }
.nav a { flex: 1; justify-content: center; padding: 14px 8px; }
.theme-btn { flex: 1 0 100%; border-left: 0; border-top: var(--line) solid var(--ink); padding: 14px; }
.hero, .section { padding: 48px 18px; }
.work-grid { grid-template-columns: 1fr; }
.tl-item { grid-template-columns: 1fr; gap: 12px; }
.tl-year { justify-self: start; }
.filters { width: 100%; }
.chip { flex: 1; text-align: center; }
}
@media (max-width: 360px) {
.hero__title { font-size: 3rem; }
.stat-block { flex-direction: column; align-items: flex-start; gap: 4px; }
.stat-block__l { text-align: left; }
}/* ============================================================
BRUTALIST PORTFOLIO — interactions (vanilla JS, no libs)
============================================================ */
(function () {
"use strict";
var prefersReduced = window.matchMedia(
"(prefers-reduced-motion: reduce)"
).matches;
/* ---------- toast helper ---------- */
var toastEl = document.getElementById("toast");
var toastTimer = null;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("show");
}, 2600);
}
/* ---------- marquee: duplicate track for seamless loop ---------- */
var track = document.getElementById("marqueeTrack");
if (track) {
track.innerHTML += track.innerHTML; // double content -> -50% loop is seamless
// click to pause/resume the loud marquee
var marquee = track.closest(".marquee");
if (marquee) {
marquee.style.cursor = "pointer";
marquee.addEventListener("click", function () {
track.classList.toggle("paused");
});
}
}
/* ---------- INVERT theme toggle ---------- */
var themeBtn = document.getElementById("themeBtn");
if (themeBtn) {
themeBtn.addEventListener("click", function () {
var inverted = document.body.classList.toggle("invert");
themeBtn.setAttribute("aria-pressed", String(inverted));
themeBtn.textContent = inverted ? "REVERT" : "INVERT";
toast(inverted ? "LIGHTS OUT. INVERTED." : "BACK TO PAPER.");
});
}
/* ---------- count-up stats ---------- */
function countUp(el) {
var target = parseInt(el.getAttribute("data-count"), 10) || 0;
if (prefersReduced) {
el.textContent = String(target);
return;
}
var start = null;
var dur = 1100;
function step(ts) {
if (start === null) start = ts;
var p = Math.min((ts - start) / dur, 1);
var eased = 1 - Math.pow(1 - p, 3);
el.textContent = String(Math.round(target * eased));
if (p < 1) requestAnimationFrame(step);
else el.textContent = String(target);
}
requestAnimationFrame(step);
}
/* ---------- reveal-on-scroll: stats + skill bars ---------- */
var revealed = new WeakSet();
function reveal(el) {
if (revealed.has(el)) return;
revealed.add(el);
if (el.hasAttribute("data-count")) countUp(el);
if (el.classList.contains("bar")) el.classList.add("is-filled");
}
var watch = [].slice.call(
document.querySelectorAll("[data-count], .bar")
);
if ("IntersectionObserver" in window) {
var io = new IntersectionObserver(
function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) {
reveal(e.target);
io.unobserve(e.target);
}
});
},
{ threshold: 0.4 }
);
watch.forEach(function (el) {
io.observe(el);
});
} else {
watch.forEach(reveal);
}
/* ---------- work filters ---------- */
var chips = [].slice.call(document.querySelectorAll(".chip"));
var cards = [].slice.call(document.querySelectorAll(".work-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.split(" ").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"));
});
});
/* ---------- work cards: keyboard activation -> toast ---------- */
cards.forEach(function (card) {
function open() {
var titleEl = card.querySelector("h3");
var title = titleEl ? titleEl.textContent : "PROJECT";
toast("OPENING CASE STUDY: " + title + " (DEMO)");
}
card.addEventListener("click", open);
card.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
open();
}
});
});
/* ---------- fictional social links -> toast ---------- */
[].slice.call(document.querySelectorAll("[data-toast]")).forEach(function (a) {
a.addEventListener("click", function (e) {
e.preventDefault();
toast(a.getAttribute("data-toast"));
});
});
/* ---------- contact form validation ---------- */
var form = document.getElementById("contactForm");
if (form) {
function setErr(id, msg) {
var field = document.getElementById(id);
var wrap = field ? field.closest(".field") : null;
var slot = form.querySelector('.err[data-for="' + id + '"]');
if (wrap) wrap.classList.toggle("has-err", !!msg);
if (field) field.setAttribute("aria-invalid", msg ? "true" : "false");
if (slot) slot.textContent = msg || "";
return !msg;
}
var emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
form.addEventListener("submit", function (e) {
e.preventDefault();
var name = form.elements.name.value.trim();
var email = form.elements.email.value.trim();
var msg = form.elements.message.value.trim();
var ok = true;
ok = setErr("cf-name", name ? "" : "TYPE YOUR NAME.") && ok;
ok =
setErr(
"cf-email",
!email ? "EMAIL REQUIRED." : !emailRe.test(email) ? "THAT EMAIL IS BROKEN." : ""
) && ok;
ok = setErr("cf-msg", msg ? "" : "SAY SOMETHING.") && ok;
if (!ok) {
toast("FIX THE RED BITS.");
var firstBad = form.querySelector(".field.has-err input, .field.has-err textarea");
if (firstBad) firstBad.focus();
return;
}
form.reset();
["cf-name", "cf-email", "cf-msg"].forEach(function (id) {
setErr(id, "");
});
toast("SENT. NICE. (NOT REALLY — IT'S A DEMO.)");
});
// clear an error as the user fixes it
["cf-name", "cf-email", "cf-msg"].forEach(function (id) {
var f = document.getElementById(id);
if (f)
f.addEventListener("input", function () {
var wrap = f.closest(".field");
if (wrap && wrap.classList.contains("has-err")) setErr(id, "");
});
});
}
/* ---------- back to top ---------- */
var topBtn = document.getElementById("topBtn");
if (topBtn) {
topBtn.addEventListener("click", function () {
window.scrollTo({ top: 0, behavior: prefersReduced ? "auto" : "smooth" });
});
}
})();<!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 / Brutalist 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=Archivo+Black&family=Space+Mono:ital,wght@0,400;0,700;1,400&family=Archivo:wght@400;500;700;900&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip-link" href="#work">Skip to work</a>
<!-- MARQUEE -->
<div class="marquee" aria-hidden="true">
<div class="marquee__track" id="marqueeTrack">
<span>PRODUCT DESIGN</span><span class="dot">●</span>
<span>DESIGN SYSTEMS</span><span class="dot">●</span>
<span>PROTOTYPING</span><span class="dot">●</span>
<span>USER RESEARCH</span><span class="dot">●</span>
<span>OPEN FOR WORK</span><span class="dot">●</span>
</div>
</div>
<!-- HEADER -->
<header class="site-head" role="banner">
<a href="#top" class="brand">M.OKAFOR<span class="brand__caret">_</span></a>
<nav class="nav" aria-label="Primary">
<a href="#work">WORK</a>
<a href="#about">ABOUT</a>
<a href="#experience">EXP</a>
<a href="#skills">SKILLS</a>
<a href="#contact">CONTACT</a>
</nav>
<button class="theme-btn" id="themeBtn" type="button" aria-pressed="false" aria-label="Invert colours">
INVERT
</button>
</header>
<main id="top">
<!-- HERO -->
<section class="hero" id="hero" aria-labelledby="hero-title">
<div class="hero__grid">
<div class="hero__lead">
<p class="kicker">PRODUCT DESIGNER — EST. 2014 — LAGOS / BERLIN</p>
<h1 id="hero-title" class="hero__title">
MAYA<br />OKAFOR<span class="blink">_</span>
</h1>
<p class="hero__sub">
I design loud, honest interfaces for people who hate fluff.
Systems thinking, raw edges, zero rounded corners. Currently
shipping design systems & 0→1 product.
</p>
<div class="hero__cta">
<a class="btn btn--solid" href="#work">SEE THE WORK →</a>
<a class="btn btn--ghost" href="#contact">HIRE ME</a>
</div>
</div>
<aside class="hero__card" aria-label="Status">
<div class="stat-block">
<span class="stat-block__n" data-count="11">0</span>
<span class="stat-block__l">YEARS SHIPPING</span>
</div>
<div class="stat-block">
<span class="stat-block__n" data-count="42">0</span>
<span class="stat-block__l">PRODUCTS LAUNCHED</span>
</div>
<div class="stat-block">
<span class="stat-block__n" data-count="6">0</span>
<span class="stat-block__l">DESIGN AWARDS</span>
</div>
<div class="hero__avail">
<span class="pulse" aria-hidden="true"></span>
AVAILABLE Q3 2026
</div>
</aside>
</div>
</section>
<!-- WORK -->
<section class="section" id="work" aria-labelledby="work-title">
<div class="section__head">
<h2 id="work-title" class="section__title">01 / SELECTED WORK</h2>
<div class="filters" role="group" aria-label="Filter projects">
<button class="chip is-active" type="button" data-filter="all" aria-pressed="true">ALL</button>
<button class="chip" type="button" data-filter="product" aria-pressed="false">PRODUCT</button>
<button class="chip" type="button" data-filter="system" aria-pressed="false">SYSTEMS</button>
<button class="chip" type="button" data-filter="brand" aria-pressed="false">BRAND</button>
</div>
</div>
<ul class="work-grid" id="workGrid">
<li class="work-card" data-tags="product system" tabindex="0">
<div class="work-card__visual visual--a" aria-hidden="true"><span>SVR</span></div>
<div class="work-card__body">
<span class="work-card__num">001</span>
<h3>SEVERANCE OS</h3>
<p>A retro-industrial admin console for a fictional logistics co. Built the full design system from raw primitives.</p>
<div class="work-card__meta"><span>PRODUCT</span><span>SYSTEMS</span><span>2025</span></div>
</div>
</li>
<li class="work-card" data-tags="product brand" tabindex="0">
<div class="work-card__visual visual--b" aria-hidden="true"><span>FLX</span></div>
<div class="work-card__body">
<span class="work-card__num">002</span>
<h3>FLUX BANKING</h3>
<p>Anti-fintech banking app. Mono numerals, hard contrast, zero gradients. Redesigned the onboarding flow end to end.</p>
<div class="work-card__meta"><span>PRODUCT</span><span>BRAND</span><span>2024</span></div>
</div>
</li>
<li class="work-card" data-tags="system" tabindex="0">
<div class="work-card__visual visual--c" aria-hidden="true"><span>GRD</span></div>
<div class="work-card__body">
<span class="work-card__num">003</span>
<h3>GRIDLOCK DS</h3>
<p>An open-source design system with 240 tokens and 60 components. Documented every state, no exceptions.</p>
<div class="work-card__meta"><span>SYSTEMS</span><span>OSS</span><span>2024</span></div>
</div>
</li>
<li class="work-card" data-tags="brand" tabindex="0">
<div class="work-card__visual visual--d" aria-hidden="true"><span>NSE</span></div>
<div class="work-card__body">
<span class="work-card__num">004</span>
<h3>NOISE FEST</h3>
<p>Identity & ticketing site for an experimental music festival. Marquees, glitch type, deliberately ugly-on-purpose.</p>
<div class="work-card__meta"><span>BRAND</span><span>WEB</span><span>2023</span></div>
</div>
</li>
<li class="work-card" data-tags="product" tabindex="0">
<div class="work-card__visual visual--e" aria-hidden="true"><span>CRT</span></div>
<div class="work-card__body">
<span class="work-card__num">005</span>
<h3>CARTON</h3>
<p>Inventory tooling for indie warehouses. Dense tables, keyboard-first, built to be fast not pretty.</p>
<div class="work-card__meta"><span>PRODUCT</span><span>TOOLS</span><span>2023</span></div>
</div>
</li>
<li class="work-card" data-tags="system brand" tabindex="0">
<div class="work-card__visual visual--f" aria-hidden="true"><span>BLK</span></div>
<div class="work-card__body">
<span class="work-card__num">006</span>
<h3>BLOCKHEAD</h3>
<p>Variable typeface specimen & micro-site. Showcased 9 weights in a brutal interactive grid.</p>
<div class="work-card__meta"><span>BRAND</span><span>TYPE</span><span>2022</span></div>
</div>
</li>
</ul>
<p class="work-empty" id="workEmpty" hidden>NOTHING HERE. PICK ANOTHER FILTER.</p>
</section>
<!-- ABOUT -->
<section class="section section--invert" id="about" aria-labelledby="about-title">
<div class="about-grid">
<div>
<h2 id="about-title" class="section__title">02 / ABOUT</h2>
<p class="about-lead">
I’m a product designer who treats constraints like a feature.
I’ve spent eleven years stripping interfaces down to their
load-bearing parts — then drawing a thick black box around them.
</p>
<p class="about-text">
I work fast, document everything, and ship. I believe good design
doesn’t need to whisper. I’ve led design at two startups,
mentored a dozen juniors, and given talks on systems thinking from
Berlin to Nairobi.
</p>
<ul class="about-list">
<li><span>→</span> Design systems from zero</li>
<li><span>→</span> 0→1 product strategy</li>
<li><span>→</span> Research that ships, not sits</li>
<li><span>→</span> Workshops & team facilitation</li>
</ul>
</div>
<div class="about-portrait" aria-hidden="true">
<svg viewBox="0 0 200 240" role="img" aria-label="Portrait illustration">
<rect x="0" y="0" width="200" height="240" fill="none" />
<rect x="40" y="30" width="120" height="120" fill="currentColor" />
<circle cx="100" cy="90" r="42" fill="var(--bg)" />
<rect x="78" y="78" width="14" height="14" fill="currentColor" />
<rect x="108" y="78" width="14" height="14" fill="currentColor" />
<rect x="84" y="112" width="32" height="6" fill="currentColor" />
<rect x="55" y="160" width="90" height="60" fill="currentColor" />
<rect x="0" y="0" width="200" height="240" fill="none" stroke="currentColor" stroke-width="6" />
</svg>
<p class="about-portrait__cap">MAYA, PROBABLY YELLING ABOUT GRID GAPS</p>
</div>
</div>
</section>
<!-- EXPERIENCE -->
<section class="section" id="experience" aria-labelledby="exp-title">
<h2 id="exp-title" class="section__title">03 / EXPERIENCE</h2>
<ol class="timeline">
<li class="tl-item">
<span class="tl-year">2022—NOW</span>
<div class="tl-body">
<h3>LEAD PRODUCT DESIGNER</h3>
<p class="tl-co">SEVERANCE LABS</p>
<p>Owned the design system & the admin platform. Cut onboarding time 38%. Built a 4-person design guild.</p>
</div>
</li>
<li class="tl-item">
<span class="tl-year">2019—2022</span>
<div class="tl-body">
<h3>SENIOR PRODUCT DESIGNER</h3>
<p class="tl-co">FLUX FINANCIAL</p>
<p>Redesigned core banking flows for 1.2M users. Shipped the mono-numeral design language now used company-wide.</p>
</div>
</li>
<li class="tl-item">
<span class="tl-year">2016—2019</span>
<div class="tl-body">
<h3>PRODUCT DESIGNER</h3>
<p class="tl-co">CARTON STUDIO</p>
<p>Designed inventory & logistics tooling for indie retailers. Lived in spreadsheets and dense data tables.</p>
</div>
</li>
<li class="tl-item">
<span class="tl-year">2014—2016</span>
<div class="tl-body">
<h3>JUNIOR DESIGNER</h3>
<p class="tl-co">NOISE COLLECTIVE</p>
<p>Posters, identities & event sites for the local music scene. Learned to break rules on purpose.</p>
</div>
</li>
</ol>
</section>
<!-- SKILLS -->
<section class="section" id="skills" aria-labelledby="skills-title">
<h2 id="skills-title" class="section__title">04 / SKILLS</h2>
<div class="skills-wrap">
<div class="skill-col">
<h3 class="skill-col__h">DISCIPLINES</h3>
<ul class="skill-bars">
<li><span class="skill-bars__l">PRODUCT DESIGN <b>95</b></span><span class="bar"><i style="--w:95%"></i></span></li>
<li><span class="skill-bars__l">DESIGN SYSTEMS <b>92</b></span><span class="bar"><i style="--w:92%"></i></span></li>
<li><span class="skill-bars__l">PROTOTYPING <b>88</b></span><span class="bar"><i style="--w:88%"></i></span></li>
<li><span class="skill-bars__l">USER RESEARCH <b>80</b></span><span class="bar"><i style="--w:80%"></i></span></li>
<li><span class="skill-bars__l">FRONT-END <b>72</b></span><span class="bar"><i style="--w:72%"></i></span></li>
</ul>
</div>
<div class="skill-col">
<h3 class="skill-col__h">TOOLBOX</h3>
<ul class="tag-cloud">
<li>FIGMA</li><li>FRAMER</li><li>HTML</li><li>CSS</li>
<li>VANILLA JS</li><li>TOKENS STUDIO</li><li>NOTION</li>
<li>SVG</li><li>VARIABLE FONTS</li><li>RIVE</li>
<li>STORYBOOK</li><li>GIT</li>
</ul>
<h3 class="skill-col__h">SPEAKS</h3>
<ul class="tag-cloud tag-cloud--alt">
<li>ENGLISH</li><li>YORUBA</li><li>GERMAN</li><li>CSS GRID</li>
</ul>
</div>
</div>
</section>
<!-- CONTACT -->
<section class="section section--invert" id="contact" aria-labelledby="contact-title">
<div class="contact-grid">
<div>
<h2 id="contact-title" class="section__title">05 / CONTACT</h2>
<p class="contact-lead">Got a loud idea? Let’s make it.</p>
<p class="contact-row"><span>EMAIL</span><a href="mailto:maya@okafor.design">maya@okafor.design</a></p>
<p class="contact-row"><span>SOCIAL</span>
<a href="#contact" data-toast="That link is fictional too.">DRIBBBLE</a> /
<a href="#contact" data-toast="Imagine a portfolio here.">LINKEDIN</a> /
<a href="#contact" data-toast="Pretend this scrolls infinitely.">GITHUB</a>
</p>
</div>
<form class="contact-form" id="contactForm" novalidate>
<div class="field">
<label for="cf-name">NAME</label>
<input id="cf-name" name="name" type="text" required autocomplete="name" />
<span class="err" data-for="cf-name"></span>
</div>
<div class="field">
<label for="cf-email">EMAIL</label>
<input id="cf-email" name="email" type="email" required autocomplete="email" />
<span class="err" data-for="cf-email"></span>
</div>
<div class="field">
<label for="cf-msg">MESSAGE</label>
<textarea id="cf-msg" name="message" rows="3" required></textarea>
<span class="err" data-for="cf-msg"></span>
</div>
<button class="btn btn--solid btn--block" type="submit">SEND IT →</button>
</form>
</div>
</section>
</main>
<footer class="site-foot" role="contentinfo">
<span>© 2026 MAYA OKAFOR — ALL RAW EDGES RESERVED</span>
<button class="top-btn" id="topBtn" type="button">BACK TO TOP ↑</button>
</footer>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Brutalist Portfolio
A complete single-page portfolio for the fictional product designer Maya Okafor, re-skinned in a loud brutalist style. The page composes the standard portfolio sections — hero, selected work, about, experience, skills, and contact — on a raw paper-white base with harsh black borders, a clashing electric-red accent, and a deliberate mono-plus-sans type mix. Nothing is rounded, the layout grid is visible, and hovers shift blocks with hard offset shadows instead of fades.
Interactions all genuinely work: a seamless top marquee you can click to pause, count-up hero stats and skill bars that fill on scroll via IntersectionObserver, a project grid you can filter by discipline with an empty-state fallback, project cards that respond to click and keyboard, and a contact form with inline validation and brutal error copy. An INVERT button flips the whole palette to a dark, acid-green variant. A toast helper acknowledges every action, all controls are keyboard-usable with visible focus rings, and the layout collapses gracefully down to roughly 360px.
Illustrative portfolio — fictional person and projects.