Portfolio — Experience / Résumé Timeline
A scannable vertical résumé timeline for a single-person portfolio, with role entries showing company, title, dates, achievement bullets, and stack chips. A segmented control swaps between Work and Education datasets, each entry expands and collapses to reveal details, an expand-all toggle opens or closes everything at once, and the current role is clearly marked with a live badge and a green connector dot.
MCP
Code
/* ============================================================
Maya Okafor — Experience / Résumé Timeline
Clean neutral portfolio base: white/ink + one accent.
============================================================ */
:root {
--ink: #14161a;
--ink-soft: #41464f;
--ink-mute: #6b7280;
--paper: #ffffff;
--paper-2: #f6f7f9;
--line: #e4e7ec;
--line-strong: #cdd2da;
--accent: #4a45e0;
--accent-soft: #ecebfc;
--accent-ink: #2e2ab0;
--current: #0f9d6b;
--current-soft: #e3f6ee;
--ring: #4a45e0;
--shadow-sm: 0 1px 2px rgba(16, 18, 24, .06), 0 1px 3px rgba(16, 18, 24, .05);
--shadow-md: 0 8px 24px rgba(16, 18, 24, .10);
--radius: 14px;
--radius-sm: 9px;
--dot: 15px;
--rail: 52px;
--font: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
--font-display: "Fraunces", Georgia, serif;
}
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
font-family: var(--font);
line-height: 1.5;
color: var(--ink);
background:
radial-gradient(120% 90% at 100% 0%, #eef0f6 0%, transparent 55%),
radial-gradient(100% 80% at 0% 100%, #eef3f1 0%, transparent 50%),
var(--paper-2);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
min-height: 100vh;
}
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden; clip: rect(0 0 0 0);
white-space: nowrap; border: 0;
}
.skip-link {
position: absolute;
left: 12px; top: -48px;
z-index: 50;
background: var(--accent);
color: #fff;
padding: 10px 16px;
border-radius: 0 0 10px 10px;
font-weight: 600;
text-decoration: none;
transition: top .18s ease;
}
.skip-link:focus { top: 0; }
:focus-visible {
outline: 2.5px solid var(--ring);
outline-offset: 2px;
border-radius: 6px;
}
/* ---------- Page shell ---------- */
.page {
max-width: 760px;
margin: 0 auto;
padding: clamp(20px, 5vw, 56px) clamp(16px, 4vw, 32px) 64px;
}
/* ---------- Header ---------- */
.resume-head {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: space-between;
gap: 24px;
padding-bottom: 28px;
border-bottom: 1px solid var(--line);
}
.head-id { display: flex; align-items: center; gap: 18px; min-width: 0; }
.avatar {
flex: none;
width: 64px; height: 64px;
border-radius: 18px;
display: grid;
place-items: center;
font-weight: 800;
font-size: 22px;
letter-spacing: .02em;
color: #fff;
background: linear-gradient(135deg, #5b56ef 0%, #8a52e6 100%);
box-shadow: var(--shadow-sm), inset 0 1px 0 rgba(255,255,255,.25);
}
.kicker {
margin: 0 0 4px;
font-size: 12px;
font-weight: 600;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--accent-ink);
}
.head-text h1 {
margin: 0;
font-family: var(--font-display);
font-weight: 600;
font-size: clamp(28px, 6vw, 38px);
line-height: 1.05;
letter-spacing: -.01em;
}
.role-line {
margin: 6px 0 0;
color: var(--ink-soft);
font-size: 15px;
max-width: 42ch;
}
.head-stats {
display: flex;
gap: 10px;
margin: 0;
}
.stat {
background: var(--paper);
border: 1px solid var(--line);
border-radius: var(--radius-sm);
padding: 8px 14px;
text-align: center;
box-shadow: var(--shadow-sm);
}
.stat dt {
font-size: 10.5px;
font-weight: 600;
letter-spacing: .07em;
text-transform: uppercase;
color: var(--ink-mute);
}
.stat dd {
margin: 2px 0 0;
font-weight: 700;
font-size: 18px;
color: var(--ink);
}
/* ---------- Controls ---------- */
.controls {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 14px;
margin: 26px 0 8px;
}
.seg {
display: inline-flex;
background: var(--paper);
border: 1px solid var(--line);
border-radius: 11px;
padding: 4px;
box-shadow: var(--shadow-sm);
gap: 2px;
}
.seg-btn {
appearance: none;
border: 0;
background: transparent;
cursor: pointer;
font: inherit;
font-weight: 600;
font-size: 14px;
color: var(--ink-soft);
padding: 8px 16px;
border-radius: 8px;
display: inline-flex;
align-items: center;
gap: 8px;
transition: background .16s ease, color .16s ease, box-shadow .16s ease;
}
.seg-btn:hover { color: var(--ink); }
.seg-btn.is-active {
background: var(--accent);
color: #fff;
box-shadow: 0 2px 8px rgba(74, 69, 224, .35);
}
.seg-count {
font-size: 11.5px;
font-weight: 700;
min-width: 20px;
padding: 1px 6px;
border-radius: 999px;
background: var(--accent-soft);
color: var(--accent-ink);
}
.seg-btn.is-active .seg-count {
background: rgba(255, 255, 255, .22);
color: #fff;
}
.ghost-btn {
appearance: none;
cursor: pointer;
font: inherit;
font-weight: 600;
font-size: 13.5px;
color: var(--ink-soft);
background: var(--paper);
border: 1px solid var(--line-strong);
border-radius: 9px;
padding: 9px 14px;
display: inline-flex;
align-items: center;
gap: 8px;
transition: border-color .16s ease, color .16s ease, background .16s ease;
}
.ghost-btn:hover {
color: var(--ink);
border-color: var(--ink-mute);
background: var(--paper-2);
}
.expand-icon {
display: inline-grid;
place-items: center;
width: 18px; height: 18px;
border-radius: 5px;
background: var(--accent-soft);
color: var(--accent-ink);
font-weight: 800;
font-size: 14px;
line-height: 1;
transition: transform .2s ease;
}
.ghost-btn[aria-expanded="true"] .expand-icon { transform: rotate(45deg); }
/* ---------- Timeline ---------- */
.timeline-wrap { margin-top: 18px; }
.timeline {
list-style: none;
margin: 0;
padding: 0;
position: relative;
}
/* the rail line */
.timeline::before {
content: "";
position: absolute;
top: 12px;
bottom: 12px;
left: calc(var(--rail) / 2);
width: 2px;
background: linear-gradient(var(--line-strong), var(--line));
}
.entry {
position: relative;
padding-left: var(--rail);
padding-bottom: 18px;
animation: rise .32s cubic-bezier(.22, .61, .36, 1) both;
}
.entry:last-child { padding-bottom: 0; }
@keyframes rise {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: none; }
}
/* the dot */
.entry-dot {
position: absolute;
left: calc(var(--rail) / 2 - var(--dot) / 2);
top: 18px;
width: var(--dot); height: var(--dot);
border-radius: 50%;
background: var(--paper);
border: 3px solid var(--line-strong);
z-index: 2;
transition: border-color .18s ease, box-shadow .18s ease;
}
.entry.is-open .entry-dot { border-color: var(--accent); }
.entry.is-current .entry-dot {
border-color: var(--current);
background: var(--current);
box-shadow: 0 0 0 4px var(--current-soft);
}
.entry-card {
background: var(--paper);
border: 1px solid var(--line);
border-radius: var(--radius);
box-shadow: var(--shadow-sm);
overflow: hidden;
transition: border-color .18s ease, box-shadow .18s ease, transform .18s ease;
}
.entry.is-open .entry-card {
border-color: var(--line-strong);
box-shadow: var(--shadow-md);
}
/* header button (the clickable row) */
.entry-head {
appearance: none;
width: 100%;
text-align: left;
background: transparent;
border: 0;
cursor: pointer;
font: inherit;
color: inherit;
padding: 16px 18px;
display: grid;
grid-template-columns: 1fr auto;
grid-template-areas:
"title chevron"
"meta chevron";
align-items: center;
gap: 2px 12px;
}
.entry-head:hover { background: var(--paper-2); }
.entry-head:focus-visible { outline-offset: -3px; }
.entry-title-row {
grid-area: title;
display: flex;
align-items: baseline;
flex-wrap: wrap;
gap: 8px;
}
.entry-title {
margin: 0;
font-size: 16.5px;
font-weight: 700;
letter-spacing: -.005em;
}
.entry-company { color: var(--accent-ink); }
.entry-sep { color: var(--line-strong); font-weight: 400; }
.badge-current {
font-size: 10.5px;
font-weight: 700;
letter-spacing: .05em;
text-transform: uppercase;
color: var(--current);
background: var(--current-soft);
padding: 2px 8px;
border-radius: 999px;
display: inline-flex;
align-items: center;
gap: 5px;
}
.badge-current::before {
content: "";
width: 6px; height: 6px;
border-radius: 50%;
background: var(--current);
}
.entry-meta {
grid-area: meta;
display: flex;
flex-wrap: wrap;
gap: 4px 12px;
margin-top: 3px;
font-size: 13px;
color: var(--ink-mute);
}
.entry-meta .dates { font-variant-numeric: tabular-nums; font-weight: 500; color: var(--ink-soft); }
.entry-meta .place::before { content: "·"; margin-right: 9px; color: var(--line-strong); }
.chevron {
grid-area: chevron;
width: 28px; height: 28px;
border-radius: 8px;
display: grid;
place-items: center;
color: var(--ink-mute);
background: var(--paper-2);
transition: transform .22s ease, background .16s ease, color .16s ease;
}
.entry-head:hover .chevron { background: var(--accent-soft); color: var(--accent-ink); }
.entry.is-open .chevron { transform: rotate(180deg); }
.chevron svg { width: 15px; height: 15px; display: block; }
/* collapsible body */
.entry-body {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows .26s ease;
}
.entry.is-open .entry-body { grid-template-rows: 1fr; }
.entry-body-inner {
overflow: hidden;
min-height: 0;
}
.entry-body-pad {
padding: 0 18px 18px;
border-top: 1px dashed var(--line);
margin-top: 2px;
padding-top: 14px;
}
.bullets {
margin: 0 0 14px;
padding: 0;
list-style: none;
display: grid;
gap: 9px;
}
.bullets li {
position: relative;
padding-left: 22px;
font-size: 14.5px;
color: var(--ink-soft);
}
.bullets li::before {
content: "";
position: absolute;
left: 4px; top: 8px;
width: 7px; height: 7px;
border-radius: 2px;
background: var(--accent);
transform: rotate(45deg);
}
.bullets li b { color: var(--ink); font-weight: 700; }
.chips {
display: flex;
flex-wrap: wrap;
gap: 7px;
}
.chip {
font-size: 12px;
font-weight: 600;
color: var(--ink-soft);
background: var(--paper-2);
border: 1px solid var(--line);
border-radius: 7px;
padding: 4px 10px;
transition: border-color .14s ease, color .14s ease;
}
.chip:hover { border-color: var(--line-strong); color: var(--ink); }
/* education-specific flourish */
.entry[data-kind="education"] .entry-dot { border-radius: 4px; }
.entry[data-kind="education"] .bullets li::before { border-radius: 50%; transform: none; }
/* ---------- Footer ---------- */
.resume-foot {
margin-top: 38px;
padding-top: 20px;
border-top: 1px solid var(--line);
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 12px;
font-size: 13px;
color: var(--ink-mute);
}
.resume-foot p { margin: 0; }
.foot-link {
font-weight: 600;
color: var(--accent-ink);
text-decoration: none;
border-bottom: 1.5px solid var(--accent-soft);
padding-bottom: 1px;
transition: border-color .15s ease;
}
.foot-link:hover { border-color: var(--accent); }
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translateX(-50%) translateY(12px);
background: var(--ink);
color: #fff;
padding: 11px 18px;
border-radius: 10px;
font-size: 13.5px;
font-weight: 500;
box-shadow: var(--shadow-md);
opacity: 0;
pointer-events: none;
transition: opacity .22s ease, transform .22s ease;
z-index: 60;
}
.toast.is-show {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
/* ---------- Responsive ---------- */
@media (max-width: 560px) {
:root { --rail: 40px; --dot: 13px; }
.resume-head { align-items: flex-start; flex-direction: column; }
.head-stats { width: 100%; }
.stat { flex: 1; }
.avatar { width: 56px; height: 56px; border-radius: 15px; font-size: 19px; }
.controls { flex-direction: column; align-items: stretch; }
.seg { justify-content: center; }
.ghost-btn { justify-content: center; }
.entry-head { padding: 14px 14px; }
.entry-body-pad { padding-left: 14px; padding-right: 14px; }
}
@media (max-width: 380px) {
.seg-btn { padding: 8px 11px; }
.entry-title { font-size: 15.5px; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: .001ms !important;
transition-duration: .001ms !important;
}
}/* ============================================================
Maya Okafor — Experience / Résumé Timeline
Vanilla JS: dataset toggle, expand/collapse, expand-all.
============================================================ */
(function () {
"use strict";
/* ---------- Data (fictional but believable) ---------- */
var DATA = {
work: [
{
company: "Northwind Health",
title: "Senior Product Designer",
dates: "2023 — Present",
place: "Remote · Berlin",
current: true,
bullets: [
["Lead designer for the clinician scheduling suite — shipped a unified booking flow that cut <b>double-bookings by 41%</b> across 200+ clinics."],
["Built and govern <b>Aurora</b>, a 90-component design system adopted by 6 product teams; reduced new-screen build time from days to hours."],
["Run weekly design critiques and mentor 3 mid-level designers."]
],
stack: ["Figma", "Design Systems", "Tokens", "Accessibility", "Storybook"]
},
{
company: "Cartograph",
title: "Product Designer, Platform",
dates: "2021 — 2023",
place: "Amsterdam",
current: false,
bullets: [
["Redesigned the map-editing canvas; the new layer model raised <b>task completion from 62% to 88%</b> in usability testing."],
["Partnered with engineering on a tokenized theming layer powering light, dark, and high-contrast modes."],
["Owned end-to-end research for the onboarding revamp — 5 studies, 40+ participants."]
],
stack: ["Figma", "User Research", "Prototyping", "Theming", "Maps"]
},
{
company: "Loft & Co.",
title: "Product Designer",
dates: "2019 — 2021",
place: "London",
current: false,
bullets: [
["Designed the checkout and saved-payments experience for a marketplace serving <b>1.2M monthly buyers</b>."],
["Introduced a component library in Figma that became the team's single source of truth."],
["Shipped an accessibility pass bringing key flows to <b>WCAG AA</b>."]
],
stack: ["Figma", "Sketch", "Interaction Design", "A/B Testing"]
},
{
company: "Bright Labs",
title: "UX Designer",
dates: "2017 — 2019",
place: "London",
current: false,
bullets: [
["First designer on a B2B analytics dashboard — defined the visual language from scratch."],
["Built reusable charting patterns adopted across 4 client products."],
["Facilitated discovery workshops translating stakeholder goals into testable flows."]
],
stack: ["Sketch", "Data Viz", "Wireframing", "Workshops"]
},
{
company: "Freelance",
title: "UI / Visual Designer",
dates: "2016 — 2017",
place: "Remote",
current: false,
bullets: [
["Delivered marketing sites and brand kits for <b>11 early-stage startups</b>."],
["Set up lightweight design-to-handoff workflows for small dev teams."]
],
stack: ["Figma", "Branding", "Landing Pages", "Webflow"]
}
],
education: [
{
company: "Royal College of Art",
title: "MA, Interaction Design",
dates: "2014 — 2016",
place: "London",
current: false,
bullets: [
["Thesis on <b>accessible data visualization</b> for low-vision users — shortlisted for the graduate showcase."],
["Built tangible interaction prototypes blending hardware and screen."]
],
stack: ["Interaction Design", "Research", "Prototyping"]
},
{
company: "University of Leeds",
title: "BA (Hons), Graphic & Communication Design",
dates: "2011 — 2014",
place: "Leeds",
current: false,
bullets: [
["Graduated <b>First Class</b>; specialised in typography and editorial systems."],
["President of the student design society; ran a print-zine collective."]
],
stack: ["Typography", "Editorial", "Print"]
},
{
company: "Interaction Design Foundation",
title: "Certificate, Design Systems",
dates: "2022",
place: "Online",
current: false,
bullets: [
["Completed the design-systems track covering tokens, governance, and contribution models."]
],
stack: ["Design Systems", "Governance"]
}
]
};
/* ---------- Elements ---------- */
var listEl = document.getElementById("timeline");
var toggleAllBtn = document.getElementById("toggle-all");
var toastEl = document.getElementById("toast");
var segButtons = Array.prototype.slice.call(document.querySelectorAll(".seg-btn"));
var downloadLink = document.querySelector("[data-download]");
var currentView = "work";
var idCounter = 0;
/* ---------- Helpers ---------- */
var chevronSVG =
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" ' +
'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' +
'<path d="M6 9l6 6 6-6"/></svg>';
function el(tag, cls, html) {
var node = document.createElement(tag);
if (cls) node.className = cls;
if (html != null) node.innerHTML = html;
return node;
}
var toastTimer;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.hidden = false;
// force reflow so the transition replays
void toastEl.offsetWidth;
toastEl.classList.add("is-show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-show");
setTimeout(function () { toastEl.hidden = true; }, 240);
}, 2200);
}
/* ---------- Build one entry ---------- */
function buildEntry(item, view) {
var id = "entry-" + (++idCounter);
var li = el("li", "entry");
li.dataset.kind = view;
if (item.current) li.classList.add("is-current");
li.appendChild(el("span", "entry-dot"));
var card = el("div", "entry-card");
/* header button */
var head = el("button", "entry-head");
head.type = "button";
head.setAttribute("aria-expanded", "false");
head.setAttribute("aria-controls", id);
var titleRow = el("div", "entry-title-row");
var titleVerb = view === "education" ? " at " : " · ";
var h3 = el("h3", "entry-title",
item.title + '<span class="entry-sep">' + titleVerb + '</span>' +
'<span class="entry-company">' + item.company + "</span>");
titleRow.appendChild(h3);
if (item.current) {
titleRow.appendChild(el("span", "badge-current", "Current"));
}
head.appendChild(titleRow);
var meta = el("div", "entry-meta");
meta.appendChild(el("span", "dates", item.dates));
meta.appendChild(el("span", "place", item.place));
head.appendChild(meta);
head.appendChild(el("span", "chevron", chevronSVG));
card.appendChild(head);
/* collapsible body */
var body = el("div", "entry-body");
body.id = id;
var inner = el("div", "entry-body-inner");
var pad = el("div", "entry-body-pad");
var ul = el("ul", "bullets");
item.bullets.forEach(function (b) {
ul.appendChild(el("li", null, b[0]));
});
pad.appendChild(ul);
var chips = el("div", "chips");
chips.setAttribute("aria-label", "Tools and skills");
item.stack.forEach(function (s) {
chips.appendChild(el("span", "chip", s));
});
pad.appendChild(chips);
inner.appendChild(pad);
body.appendChild(inner);
card.appendChild(body);
li.appendChild(card);
/* interaction */
head.addEventListener("click", function () {
toggleEntry(li, head);
});
return li;
}
function toggleEntry(li, head, force) {
var open = typeof force === "boolean" ? force : !li.classList.contains("is-open");
li.classList.toggle("is-open", open);
head.setAttribute("aria-expanded", open ? "true" : "false");
syncToggleAll();
}
/* ---------- Render a dataset ---------- */
function render(view) {
currentView = view;
listEl.innerHTML = "";
DATA[view].forEach(function (item, i) {
var entry = buildEntry(item, view);
entry.style.animationDelay = Math.min(i * 45, 260) + "ms";
listEl.appendChild(entry);
});
// Open the most recent / current role by default for a useful first view.
var firstEntry = listEl.querySelector(".entry");
if (firstEntry) {
toggleEntry(firstEntry, firstEntry.querySelector(".entry-head"), true);
}
syncToggleAll();
}
/* ---------- Segmented control ---------- */
function selectView(view, btn) {
if (view === currentView && btn.classList.contains("is-active")) return;
segButtons.forEach(function (b) {
var active = b === btn;
b.classList.toggle("is-active", active);
b.setAttribute("aria-selected", active ? "true" : "false");
});
render(view);
toast(view === "work" ? "Showing work experience" : "Showing education");
}
segButtons.forEach(function (btn) {
btn.addEventListener("click", function () {
selectView(btn.dataset.view, btn);
});
// arrow-key navigation within the tablist
btn.addEventListener("keydown", function (e) {
if (e.key !== "ArrowRight" && e.key !== "ArrowLeft") return;
e.preventDefault();
var idx = segButtons.indexOf(btn);
var next = e.key === "ArrowRight"
? (idx + 1) % segButtons.length
: (idx - 1 + segButtons.length) % segButtons.length;
segButtons[next].focus();
selectView(segButtons[next].dataset.view, segButtons[next]);
});
});
/* ---------- Expand all / collapse all ---------- */
function entriesOpenState() {
var entries = Array.prototype.slice.call(listEl.querySelectorAll(".entry"));
var openCount = entries.filter(function (en) {
return en.classList.contains("is-open");
}).length;
return { total: entries.length, open: openCount, entries: entries };
}
function syncToggleAll() {
var s = entriesOpenState();
var allOpen = s.total > 0 && s.open === s.total;
toggleAllBtn.setAttribute("aria-expanded", allOpen ? "true" : "false");
toggleAllBtn.querySelector(".toggle-label").textContent =
allOpen ? "Collapse all" : "Expand all";
}
toggleAllBtn.addEventListener("click", function () {
var s = entriesOpenState();
var shouldOpen = !(s.total > 0 && s.open === s.total);
s.entries.forEach(function (en) {
toggleEntry(en, en.querySelector(".entry-head"), shouldOpen);
});
toast(shouldOpen ? "Expanded all entries" : "Collapsed all entries");
});
/* ---------- Footer download (demo) ---------- */
if (downloadLink) {
downloadLink.addEventListener("click", function (e) {
e.preventDefault();
toast("Résumé download is disabled in this demo");
});
}
/* ---------- Init ---------- */
render("work");
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Maya Okafor — Experience Timeline</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Fraunces:opsz,wght@9..144,500;9..144,600&display=swap" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip-link" href="#timeline">Skip to timeline</a>
<main class="page" role="main">
<header class="resume-head">
<div class="head-id">
<div class="avatar" aria-hidden="true">MO</div>
<div class="head-text">
<p class="kicker">Résumé · Updated June 2026</p>
<h1>Maya Okafor</h1>
<p class="role-line">Senior Product Designer — design systems & complex workflows</p>
</div>
</div>
<dl class="head-stats" aria-label="Career summary">
<div class="stat">
<dt>Experience</dt>
<dd><span data-stat="years">9</span> yrs</dd>
</div>
<div class="stat">
<dt>Roles</dt>
<dd><span data-stat="roles">5</span></dd>
</div>
<div class="stat">
<dt>Focus</dt>
<dd>Systems</dd>
</div>
</dl>
</header>
<section class="controls" aria-label="Timeline controls">
<div class="seg" role="tablist" aria-label="Choose dataset">
<button class="seg-btn is-active" role="tab" id="tab-work" aria-selected="true" aria-controls="timeline" data-view="work">
Work
<span class="seg-count" data-count="work">5</span>
</button>
<button class="seg-btn" role="tab" id="tab-education" aria-selected="false" aria-controls="timeline" data-view="education">
Education
<span class="seg-count" data-count="education">3</span>
</button>
</div>
<button class="ghost-btn" id="toggle-all" type="button" aria-expanded="false">
<span class="expand-icon" aria-hidden="true">+</span>
<span class="toggle-label">Expand all</span>
</button>
</section>
<section class="timeline-wrap" aria-labelledby="timeline-title">
<h2 id="timeline-title" class="sr-only">Experience timeline</h2>
<ol class="timeline" id="timeline" aria-live="polite"></ol>
</section>
<footer class="resume-foot">
<p>Illustrative portfolio — fictional person and projects.</p>
<a class="foot-link" href="#" data-download>Download résumé (PDF)</a>
</footer>
</main>
<div class="toast" id="toast" role="status" aria-live="polite" hidden></div>
<script src="script.js"></script>
</body>
</html>Experience / Résumé Timeline
A clean, drop-in experience timeline for a résumé or portfolio page. A vertical rail connects a stack of role cards, each one showing the company, title, dates, and location at a glance. Click any card to expand it and reveal two or three achievement bullets plus a row of stack chips; click again to collapse. The most recent position is highlighted with a green dot and a pulsing Current badge so the eye lands on it first.
A segmented control at the top swaps the dataset between Work and Education, re-rendering the same timeline with matching connectors and counts. The Expand all button opens every entry at once and flips to Collapse all when everything is open, with its state staying in sync if you toggle cards individually. The tablist supports arrow-key navigation, every control has a visible focus ring, and the body region is wired up with aria-expanded / aria-controls for assistive tech.
Built with vanilla JS and a single accent color on a neutral white/ink base, it collapses gracefully to a narrower rail on small screens and respects prefers-reduced-motion. Swap the DATA object in script.js for your own history and it is ready to ship.
Illustrative portfolio — fictional person and projects.