Adom & Co.

Build Guide — How this site was made

Lighting a house in code

Adom & Co. is the collection's motion-and-light study: a canvas of rising embers, a wax seal that opens like an invitation, and galleries of hand-drawn candlelit scenes you pan through like a memory. This page explains how — generously enough that you could rebuild it.

Concept & creative direction

The brief was the invitation: arrive at a luxury events house the way you'd arrive at an actual wedding or gala — through a sealed envelope, into a candlelit room. Three decisions carried that idea through every route:

  • Four inks, one job each. Noir #0C0A09 is the room; champagne #E7D3A1 is the line and the reading text; candle #F0B95C is every flame, seam and call to action; velvet #3B2A45 is reserved for the one full-bleed "grand" chapter (Voices) so it lands as a mood shift, not a repeated pattern.
  • Two voices. Cormorant Garamond italic speaks for the house — headlines, testimonial quotes, the RSVP card's script moments. Jost, tracked and uppercase, is the caption voice: kickers, labels, folios, buttons.
  • Three chapters, one craft. Weddings, Galas and Cultural ceremonies each get a scene gallery and a narrative line; a single candle-lit path afterwards explains how any of the three actually gets built, from first letter to last flame.

Nothing on the site is a photograph or a stock render. Every arch, table and chandelier is single-stroke SVG lit by a radial gradient standing in for candlelight — the collection's founding rule (everything code-drawn) is, here, the entire visual premise of a luxury brand that would normally live and die by photography.

Toolchain

Designed and engineered by Fable 5 (Anthropic's model) working as designer-engineer, directed by Hannah Kwakye — art direction, taste and final cut are hers. The stack stays deliberately small:

  • Hand-authored static HTML, CSS and JavaScript. No framework, no build step, no bundler.
  • Zero libraries. The embers are a hand-rolled canvas particle loop; the galleries are pointer events plus native scroll; the craft path is stroke-dashoffset; the stagelight is IntersectionObserver toggling a CSS custom property. About 260 lines of vanilla JS in total.
  • Three self-hosted fonts as woff2, preloaded, font-display: swap: Jost (a true variable file, weight 400–700 in one download) plus Cormorant Garamond regular and italic as static faces — the collection's 3-file budget, used in full.
  • First-view transfer is roughly 90 KB before compression, fonts included. The heaviest asset on the page is the ember canvas loop's JavaScript, not an image, because there are no images.

The wax seal preloader

Arrival is staged in three beats, all inside one fixed overlay: the seal fades in (data-state="in"), a hairline crack draws across it with the same stroke-dashoffset trick used for the croquis technique elsewhere in the collection, then a radial glow scales from the seal's center to fill the screen (data-state="opening") before the whole overlay fades out.

const len = crack.getTotalLength();
crack.style.strokeDasharray = len;
crack.style.strokeDashoffset = len;
// later, on the "opening" beat:
crack.style.transition = 'stroke-dashoffset .7s ease';
crack.style.strokeDashoffset = '0';

The whole sequence takes under two seconds and is skippable at every instant — a click, a keypress, or a visible "Enter — skip intro" button all jump straight to the final state. Under prefers-reduced-motion the overlay never animates: it appears and is dismissed within 180ms, satisfying both the letter and the spirit of "skippable." The overlay is aria-hidden and never traps focus, since the real page underneath is already fully rendered and reachable.

The ember canvas

The hero's embers are a small particle system, not a video or a gif: ~46 points drift upward with a slow sinusoidal sway, redrawn every frame with globalCompositeOperation = 'lighter' so overlapping embers brighten instead of muddying into grey.

Cursor-warm means literal: each ember measures its squared distance to the pointer, and inside a warmth radius it brightens, grows, and accelerates upward — the closer an ember drifts to your hand, the more it behaves like it's caught real heat:

const dist2 = dx*dx + dy*dy;
let warm = dist2 < 26000 ? 1 - dist2/26000 : 0;
e.y -= e.vy * (1 + warm * 1.4);
fillStyle = `rgba(240,185,92,${alpha + warm*.35})`;

The canvas is scoped to the hero only, cost-conscious by design: an IntersectionObserver stops the animation loop the instant the hero scrolls out of view, and a visibilitychange listener stops it again if the tab is hidden — so the loop never runs where nobody can see it. Device pixel ratio is capped at 2. Under reduced motion, the loop never starts at all; instead a single static frame is drawn once — a soft gradient glow and a scatter of still embers — so the hero still reads as candlelit without any motion.

Drag-to-pan scene galleries

Each chapter's gallery is a native horizontally-scrolling flex row (overflow-x: auto, scroll-snap-type: x proximity) enhanced with pointer-driven drag and momentum — not a from-scratch scrollbar reimplementation, so trackpads, touch, mouse wheel and keyboard all keep working for free.

gallery.addEventListener('pointermove', e => {
  const dx = e.clientX - lastX; lastX = e.clientX;
  gallery.scrollLeft -= dx;
  vel = vel*0.7 + (-dx)*0.3;   // low-pass velocity
});
// on release: scrollBy(0? no — horizontal) gallery.scrollLeft -= vel; vel *= 0.93;

Nine scenes total (three per chapter) are drawn as flat SVG line art — arches, tablescapes, chandeliers and drum circles — each sitting on a radial-gradient "glow" rectangle standing in for the candlelight a photograph would otherwise capture. A drag longer than 6px suppresses the following click, so dragging never accidentally activates a link.

Dimming lights between chapters

A single fixed layer, #stagelight, sits behind every section as a soft radial glow. Each chapter carries a data-glow value drawn from the same four-color palette — candle for Weddings, a lighter velvet for Galas, champagne for Cultural ceremonies — so the room's mood shifts hue as you scroll, using only colors already in the system.

An IntersectionObserver watches all three chapters. When the active chapter changes, the stagelight briefly dims (opacity drops to near-zero for ~340ms) before the new color is set and it relights — the theatre trick of houselights dropping between acts, done in two CSS transitions and one setTimeout. Under reduced motion the color changes immediately, no dim pulse.

The craft: a candle-lit path

Planning, Design and Production are drawn as three candles along a single wandering SVG path. The path itself draws in once via stroke-dashoffset as it enters view; each candle's flame is a separate path with opacity: 0 that an IntersectionObserver reveals (and, motion allowed, sets flickering with a small scale/skew keyframe) as its stage scrolls into frame — the timeline lights itself up stage by stage rather than all at once.

Iteration log

Three passes, screenshots read like a creative director, findings acted on. What actually changed:

Pass 1 — Design critique

  • Found a stray malformed path fragment in the Galas "Grand Entrance" scene (a leftover editing artifact) — the SVG still rendered, but it was dead weight. Removed it and cleaned the pillar-flourish curves.
  • Full-page screenshots showed apparently blank chapters; scroll-position verification showed the content was in fact rendering correctly — the gap was a known tall-page capture artifact, not a real bug, and was set aside per the QA guidance for pages beyond ~8,000px.
  • A real bug did turn up while investigating: content-visibility: auto on the chapter/craft/voices sections, meant as a performance hint, was giving the browser a placeholder height that didn't match reality, so clicking "The Craft" in the nav could land mid-heading instead of at the section start. Removed the hint — correctness over a marginal performance gain on a page this light.
  • Fixing that surfaced a second, subtler issue: adding generous scroll-margin-top to clear the fixed header revealed a sliver of the previous section on arrival at each chapter, which — for the very first chapter — collided with the hero's "Begin" scroll cue. The sections already carry 5rem+ of their own top padding, so scroll-margin-top was trimmed to a token 1rem; clearance now comes from the section's own rhythm, not from stealing space from its neighbor.

Pass 2 — Elevation

  • Gave the galleries a hand: on hover (pointer-fine only) a scene's frame brightens and its candle-colored strokes thicken, so the drag affordance reads before you've touched anything. Added prev/next buttons alongside the drag gesture, so the cinematic strip is fully usable by keyboard and by anyone who prefers a click to a drag.
  • Caught dead code in the ember loop: an old fade expression always evaluated to "no fade" regardless of an ember's position. Replaced it with a real one — embers now genuinely dim and extinguish as they rise into the top fifth of the hero, reading as heat cooling rather than particles just vanishing off-screen. A visible, not just theoretical, deepening of the signature interaction.
  • Gave the craft path a second layer of interactivity: hovering a lit candle (pointer-fine only) blooms its flame with a soft drop-shadow glow and warms its stage heading to candle color — a small reward for the one part of the page that invites a lingering look.

Pass 3 — Ship quality

  • All routes screenshotted at mobile/tablet/desktop, plus targeted anchor screenshots for every in-page section, with zero console errors throughout.
  • Every nav, footer and in-page link resolves; the RSVP form posts to Netlify with honeypot and inline ?success handling verified.
  • Reduced-motion pass re-verified in emulation: the seal skips near-instantly, the ember canvas renders one static glow frame, the craft path is fully drawn with no flicker, and reveals appear without travel.
  • Fonts confirmed self-hosted and loading (three woff2 files, Jost's variable file included, ~73 KB total, each over 10 KB); every word proofread against the four-ink, two-voice system.

Deploy

The site is static files on Netlify, deployed CI-style from the collection's repository. netlify.toml publishes the site root, sets immutable year-long caching on /assets/*, and adds the standard hardening headers. The enquiry form is Netlify Forms — a plain HTML <form data-netlify="true"> with a honeypot field, no JavaScript required to submit.

This is one of 26 sites in Hannah Kwakye's collection — the shared thesis being that AI doesn't replace taste; it amplifies the designer who has it. See the design process for the thinking, or return to the invitation.