From the Graphics Desk
How “The Last Keepers” Was Made
The typographic decisions, the three scroll-driven data moments, and the hand-rolled machinery behind them — with the parts you are welcome to steal.
§ 1The Concept
“The Last Keepers” is a longform scrollytelling feature in the tradition of NYT Interactive and The Pudding: a fully written 2,200-word piece of reported fiction where the writing and the typography are the design. There is no hero photograph, no video, no framework. The atmosphere comes from warm paper (#f7f3ec), rich ink (#1c1a17), one editorial red, and the furniture of a broadsheet: masthead, kicker–hed–dek–byline, drop caps, pull quotes, margin notes, small-caps chapter labels, and an end mark (❦) to close the piece.
Three data moments interrupt the prose exactly where the story earns them: a full-bleed statistic that counts itself up, a line chart that draws itself as you scroll past, and a map of the fictional archipelago whose five lighthouses light in sequence as the text introduces each island. Every one is vanilla JavaScript — IntersectionObserver plus requestAnimationFrame, nothing else.
§ 2The Typography
| Role | Choice | Why |
|---|---|---|
| Display | Fraunces, optical sizing on, 600–900 | A wonky old-style serif with real ink traps; at display sizes it reads like letterpress. |
| Body | Newsreader, optical sizing on | Drawn for news text; its text-grade optical size keeps 1.06–1.19rem body crisp. |
| Measure | max-width: 66ch | Inside the canonical 65–75ch comfort band; line-height 1.68 for generous leading. |
| Paper | #f7f3ec | Warm, not yellow. The dark stat break inverts it for one full-viewport beat. |
| Ink | #1c1a17 | Never pure black — softens the page the way real newsprint does. |
| Accent | #a63a2b | One editorial red, rationed: kickers, footnote markers, lit lighthouses, data. |
Drop caps use the modern property with a graceful fallback — the float version ships to every browser, and initial-letter upgrades it where supported:
/* fallback first: works everywhere */
.dropcap::first-letter{
float:left; font-family:"Fraunces";
font-size:3.6em; line-height:.82; padding:.06em .09em 0 0;
}
/* the real thing where supported */
@supports (initial-letter: 3) or (-webkit-initial-letter: 3){
.dropcap::first-letter{
float:none; initial-letter:3; font-size:inherit; line-height:inherit;
}
}
Pull quotes break the measure on purpose: they are direct children of the full-width section, sized min(88vw, 46rem) and centered with margin-left:50%; transform:translateX(-50%), so they escape the 66ch column and re-assert the page’s full width. Margin notes are numbered <span role="note"> elements that render as inset boxes on narrow screens and, above 1240px, absolutely position themselves into the right margin (left: calc(100% + 2.8rem)) — footnotes that never make you scroll to the bottom.
§ 3Moment One — The Counting Statistic
The full-bleed break (“312 … 3 … none”) is a dark section that escapes its column with width:100vw; margin-left:calc(50% - 50vw) (with overflow-x:clip on the root to swallow the scrollbar-width overshoot). Each number waits for an IntersectionObserver at 60% visibility, then counts up over 1.9s with a quartic ease-out — fast through the boring hundreds, slow and legible at the end:
var dur = 1900, t0 = performance.now();
(function tick(t){
var p = Math.min(1, (t - t0) / dur);
var eased = 1 - Math.pow(1 - p, 4); // easeOutQuart
el.textContent = Math.round(target * eased).toLocaleString();
if (p < 1) requestAnimationFrame(tick);
})(t0);
font-variant-numeric:tabular-nums keeps the digits from jittering sideways while they count. Under prefers-reduced-motion the final number simply appears.
§ 4Moment Two — The Self-Drawing Chart
“The Long Ebb” (keepers per decade, 1900–2030) is an SVG built at runtime from a 13-row array — axes, gridlines, path and labelled points are all generated with createElementNS. The drawing trick is the pathLength="1" attribute, which normalises any path to a length of exactly 1, so the dash math never needs getTotalLength():
<path d="…" pathLength="1"
stroke-dasharray="1 1" stroke-dashoffset="1"/>
// on scroll (inside one shared rAF):
var p = clamp((vh * 0.88 - fig.top) / (vh * 0.58), 0, 1);
path.style.strokeDashoffset = String(1 - p);
The line is therefore scrubbed by the scrollbar, not played on a timer — scroll back up and it un-draws. Each data point knows its horizontal fraction along the plot and fades in the moment the line reaches it; the dashed red projection to “2027 · none” only appears once the line completes. Because it’s an SVG with a viewBox, the whole figure reflows fluidly from 360px to desktop.
§ 5Moment Three — The Archipelago Map
The Hywel Islands map is hand-drawn inline SVG — coastlines as cubic Béziers, a dotted graticule, a compass, a scale bar, reef hatching. It sits in a position:sticky column while five prose “steps” scroll past. An observer with a narrow horizontal band around the viewport’s middle decides which step is current:
new IntersectionObserver(handler, {
rootMargin: '-42% 0px -48% 0px', threshold: 0
});
// a step "activates" only while it crosses the middle 10% of the screen
Activating a step lights its lighthouse: the dot turns red and a halo pulses via a CSS animation (transform-box:fill-box makes transform-origin:center behave inside SVG). Lights the reader has already visited keep a dim red — so by the end of the tour, the whole ring of small suns is burning. On narrow screens the grid collapses and the map itself becomes the sticky element, pinned to the top at 42vh while the steps pass beneath it.
§ 6How It Was Made
Written and built by Claude (Fable 5) — the 2,200-word feature, the fictional archive it quotes, the map geometry and all of the code — in vanilla HTML, CSS and JavaScript by hand. No frameworks, no build step, no chart library, no scroll library; the only external resources are two font families from the Google Fonts CSS API. One page, one script, three observers, one shared requestAnimationFrame loop for the progress bar and the chart.
It is one of twenty-five sites in the Fable Showcase, each built in a wildly different visual school to see how far hand-rolled web craft can go.
§ 7Steal This
pathLength="1"on any SVG path. It normalises the path to unit length, so scroll-linked line drawing becomesdashoffset = 1 - progress— nogetTotalLength(), no magic numbers, works after any redesign of the path.- The center-band observer.
rootMargin:'-42% 0px -48% 0px'turns IntersectionObserver into a “what is the reader looking at right now” sensor. It is the entire engine of scrollytelling. - Progressive drop caps. Ship the float fallback unconditionally, then override inside
@supports (initial-letter: 3). Everyone gets a drop cap; modern engines get a perfect one. - Margin notes over footnotes. Same markup, two renderings: an inset box by default, absolutely positioned into the right margin above 1240px. Readers never lose their place.
- Ration the accent. One red, spent only on meaning — kickers, footnote markers, data, lit lighthouses. When the projection line turns red, the reader feels it, because nothing else on the page has asked for that color.