Efua Mensah Build Guide

Build Guide — the recipe for the site itself

How this menu was plated

A chef's portfolio served as a seven-course tasting menu — every plate drawn in SVG, every course assembling itself as you scroll. This page is the recipe card: honest, specific, and written so another designer could cook it.

Concept & creative direction

The brief asked for a personal portfolio for a fictional Accra chef who cooks modern Ghanaian tasting menus. The founding decision: don't build a website about a tasting menu — build the tasting menu. The information architecture is the menu's own dramaturgy: amuse-bouche (hero), seven courses (the work), the chef (the person), private dining (the offer), notices (social proof), reservations (the ask).

Fine-dining print menus supplied the whole visual language: EB Garamond italics for dish names, small-caps course numerals, dot leaders running from dish to detail, hairline double-rule borders on the menu card. Charcoal #191817 is the dining room at night; rice paper #FBF6EE is the plate; saffron, palm green and pepper red only ever appear as food and accent — the palette is literally the plating palette.

Toolchain

Why every plate is drawn in code

The collection this site belongs to has a hard rule: no photographs, no AI-generated rasters — every visual authored in code. For a restaurant that constraint sounds fatal; it became the thesis. Food photography on chef sites is interchangeable. A top-view plate built from an SVG circle, thick round-capped sauce strokes, filled protein shapes and staggered garnish dots is not trying to imitate a photo — it is plating as diagram, the way chefs actually sketch dishes on paper during development.

Each dish keeps a strict grammar so the abstraction stays appetizing rather than clip-arty: one plate (radial-gradient circle plus two hairline rings), one sauce gesture, one protein mass, garnish as punctuation. Waakye risotto is a rust disc with rice-grain ellipses and a gari-tuile crescent; the octopus is three tapering round-capped strokes curling into a spiral with paper-coloured sucker dots. Nothing on any plate is more literal than it needs to be.

The signature interaction: scroll-plating

Each course is a tall section with a sticky, full-viewport scene pinned inside it. Scrolling through the section "cooks" the dish in stages.

1 — Progress per course

// section is 205vh tall; the pinned scene is 100vh
const travel = section.offsetHeight - innerHeight;   // scroll budget
const p = clamp(-rect.top / travel, 0, 1);           // 0 → 1 through the course
section.style.setProperty('--p', p);

One passive scroll listener, one rAF-throttled pass over all seven sections. The value of p also drives a subtle parallax — the plate drifts up 5vh as it plates, via translateY(calc((1 - var(--p)) * 5vh)).

2 — Assembly states, not tweens

Progress maps to five cumulative classes at fixed thresholds — plate .s1 (2%), sauce .s2 (18%), protein .s3 (40%), garnish .s4 (62%), steam .s5 (80%). All actual motion lives in CSS transitions keyed to those classes, so scrolling backwards un-plates the dish for free, and the browser compositor does the easing.

3 — The sauce sweep

<path class="draw" pathLength="1" d="M210,164 C148,198 …"
      stroke-width="30" stroke-linecap="round"/>

.plate .draw { stroke-dasharray: 1; stroke-dashoffset: 1;
               transition: stroke-dashoffset 1.3s cubic-bezier(.45,.05,.25,1); }
.course.s2 .sauce .draw { stroke-dashoffset: 0; }

pathLength="1" normalises every path to unit length, so one dash rule draws any sauce, any shape. A thick round-capped stroke drawing on looks exactly like a spoon dragging sauce across porcelain — the single most food-like trick on the site.

4 — Stagger through a custom property

<circle style="--i:3" …/>   /* third garnish dot */

.plate .garnish > * { transform: scale(0);
  transition: transform .6s cubic-bezier(.3,1.5,.45,1);  /* overshoot = "pop" */
  transition-delay: calc(var(--i, 0) * .07s); }

Every protein shape and garnish dot carries an index; garnish gets an overshoot curve so items land like they were placed with tweezers.

5 — Steam and chill

Hot courses (II–V) carry three wavy stroked paths that loop on a keyframe — rise 52px, fade in to 34% opacity, fade out — with per-path duration and delay via --sd/--so so the wisps never sync. The cold course (VI) inverts the idea: frost glints that twinkle instead of rise. Both are animation-play-state: paused whenever the course is off-screen, and both freeze to a faint static frame under reduced motion.

Menu typography as fine-dining art

The dot leader — the row of dots between a dish and its price — is done with a repeating radial gradient on a flex-grow spacer, so it stretches to any width without a single extra DOM node:

.leader .dots { flex: 1;
  background-image: radial-gradient(circle, rgba(251,246,238,.42) 1px, transparent 1.4px);
  background-size: 9px 3px; background-repeat: repeat-x;
  background-position: 0 100%; height: .9em; }

The same component set the whole site's rhythm: menu-card rows, per-course ingredient lines, the chef's residency list, private-dining cards. Course numerals are letterspaced Instrument Sans small caps in saffron; dish names are EB Garamond italic at display size — the italic is a real cut, not a slant.

Provenance flip cards

Each course carries a small menu card that flips on hover, focus, or tap to reveal where the hero ingredient comes from. It is a real <button> with aria-expanded, two absolutely-stacked faces with backface-visibility: hidden, and a rotateY(180deg) on a perspective parent. The back face inverts to rice paper with charcoal text — turning the card literally turns the lights on. Under reduced motion the rotation is replaced by a 140ms crossfade.

Accessibility & reduced motion

Performance

Iteration log

Deploy pipeline

The site is fully static — index.html, one stylesheet, one script, three fonts — deployed on Netlify from the collection's repository, CI-driven on push. netlify.toml publishes the site root, sets immutable cache headers on /assets/* and conservative security headers everywhere. The reservations form is Netlify Forms with a honeypot field; no backend exists or is needed.

Chef Efua Mensah is a fictional persona and the Test Kitchen a fictional restaurant. The design thinking — and what was deliberately rejected — is documented in the design process.