B.1Concept & toolchain
Designed and built by Hannah Kwakye, engineered with Fable 5 as the designer-engineer under her direction. The whole site is hand-authored static HTML, CSS and vanilla JavaScript — zero frameworks, zero libraries, zero external requests. Motion runs on one requestAnimationFrame loop plus IntersectionObserver; typography is Literata and Public Sans, self-hosted as woff2.
Every visual is code-drawn — an environment constraint the collection turned into a thesis. Here that meant: an SVG map plate, stroke-icon programme plates, an aged-paper grain made from an SVG turbulence filter in a data-URI, and a hero built from nothing but type.
B.2An alphabet the fonts don't ship
Google's latin subset of Literata omits Ɛ, Ŋ and ɔ — the very letters this site is about. The fix is a second, 2 KB font slice containing only those glyphs, declared last with a narrow unicode-range so the browser reaches for it only when a West African letterform appears:
/* Ŋ ŋ Ɔ Ɛ ɔ ɛ — subset separately; declared last so it wins */
@font-face {
font-family: 'Literata';
src: url('literata-glyphs.woff2') format('woff2');
unicode-range: U+14A-14B, U+186, U+190, U+254, U+25B;
}
B.3How the letters fill
Each hero glyph is two stacked copies of the same character: an outlined one (-webkit-text-stroke, transparent fill) and a terracotta one clipped by a polygon whose top edge a scroll-driven custom property lowers, releasing the ink from the bottom up:
.glyph .fill {
clip-path: polygon(-6% calc((1 - var(--p, 0)) * 100%),
106% calc((1 - var(--p, 0)) * 100%),
106% 106%, -6% 106%);
}
// per frame: ink rises as the letter travels — 0 as its top clears
// the fold, 1 as its centre reaches the viewport's centre
const p = clamp((vh - rect.top) / (vh * 0.5 + rect.height * 0.5), 0, 1);
glyph.style.setProperty('--p', p);
Two hard-won details: the polygon's -6% overshoot keeps serifs and the Ŋ's descender hook from being shaved during the fill — and it is a polygon rather than inset() because a calc()-driven inset with negative side values silently fails to clip in some Chromium compositors. Under prefers-reduced-motion the clip is removed entirely; the letters simply stand, already full.
B.4Drawing West Africa in 19 kilobytes
The map is an equirectangular projection done by hand: x = 24 + (lon + 18) × 34, y = 16 + (17.2 − lat) × 34. Forty-two real coastline coordinates from Saint-Louis to the Niger Delta are smoothed into quadratic curves through segment midpoints; national borders are dashed and deliberately suggestive; the Niger's long arc through the Sahel is drawn from its actual course. All 46 chapter nodes sit at real city coordinates.
Each node carries a pulsing halo staggered by a per-node animation-delay, so the network breathes rather than blinks. An IntersectionObserver removes the .live class off-screen — 46 infinite animations run only while you're watching them. The country legend filters by dimming non-matches; nodes are keyboard-focusable and announce themselves to a live-region caption.
B.5The iteration log
- Pass 1 · Design critique
- The cover's scroll cue crowded the ornament rule on mobile — the cue now sits on its own line with more air, and the hero measure tightened.
- Glyph captions read as floating debris at desktop widths; anchoring them to the row's baseline zone with an index line ("letterform ii · of iv") made each row a museum label.
- Map sea labels and the Niger's label needed letterspacing and rotation tuning to read as engraving rather than UI text.
- Pass 2 · Elevation
- Deepened the map from decoration to instrument: added the 13-button country legend with dimming filter, keyboard-focusable nodes, and a live plate caption that names every chapter.
- Gave the goals band its own indigo register — apricot numerals, oldstyle figures, a "goals not victories" preface — so the one data moment lands as a promise, not a dashboard.
- Accra, as head chapter, now carries a larger node; Tema and Banjul were nudged apart from their neighbours so the Gulf coast reads at a glance.
- Pass 3 · Ship quality
- All three viewports screenshotted full-length and read: zero console errors, fonts loading (Ɛ Ŋ ɔ render from the 2 KB subset), every anchor and both redirect stubs verified.
- Reduced-motion pass: glyphs stand pre-filled, halos hold a static ring, reveals appear without translation; scroll-behavior drops to auto.
- Proofread end to end; the sixty-word mission recounted at exactly sixty words.
B.6Deploy
Netlify, straight from a Git push: publish = ".", immutable one-year caching on /assets/*, conservative security headers, and Netlify Forms picking up the pledge form at build time. The /guide and /process routes exist as tiny meta-refresh stubs pointing back to these appendices, so the collection's route contract holds on a one-page site.