← Back to Nightcap

[ Parable build guide ]

How Nightcap was built

A speakeasy that never tells you where it is, rendered as a website that shows you everything. The signature is a build-a-drink pour engine: three SVG rects clipped inside a lowball glass, poured layer by layer with requestAnimationFrame, then named, measured and given a poem.

astro 5 · zero client frameworkinline SVG onlyCinzel / Karla / Space Monolight + darkreduced-motion aware

The idea

A speakeasy sells exactly one thing: the feeling of being let in. So the site behaves like the bar. The door in the hero is unmarked and lit from the other side. The menu reads like verse and reshuffles itself, because the room rearranges nightly. And the centrepiece hands you the ritual itself — pick three true things, watch the pour, get a drink with your name on it (well, its name — the bar keeps yours out of it). Dark theme is the room at candle height; light theme is the morning after, shutters open.

The stack — why Astro

Signature technique — the pour engine

The glass is one SVG. Its interior is a <clipPath>, and inside it sit three rects — spirit, modifier, bitters — each anchored to the glass floor. Pouring a layer means animating its height up while pinning y = floor − height, so the liquid rises instead of stretching. Each layer's "floor" is the surface of the one before it, so the drink stacks the way a layered pour actually would:

const BOTTOM = 238;                        // inner floor of the glass, SVG units
const HEIGHTS = { spirit: 92, modifier: 44, bitters: 9 };

const setLayer = (rect, floor, h) => {
  rect.setAttribute('y', floor - h);       // liquid grows upward
  rect.setAttribute('height', Math.max(0, h));
};

const slide = (dur, fn, token) => new Promise((done) => {
  const t0 = performance.now();
  const ease = (t) => 1 - Math.pow(1 - t, 3);   // the slow pour
  const step = (now) => {
    if (token !== pourToken) return done(false); // a newer pour took over
    const p = Math.min(1, (now - t0) / dur);
    fn(ease(p));
    p < 1 ? requestAnimationFrame(step) : done(true);
  };
  requestAnimationFrame(step);
});

// one drink = three awaited pours, each stacking on the last
let floor = BOTTOM;
for (const part of ['spirit', 'modifier', 'bitters']) {
  const f0 = floor;
  stream.setAttribute('fill', POUR[part][sel[part]].color);
  await slide(DURS[part], (t) => {
    const h = HEIGHTS[part] * t;
    setLayer(layers[part], f0, h);          // the layer rises
    stream.setAttribute('height', f0 - h - 10); // the stream shortens
  }, token);
  floor -= HEIGHTS[part];
}

The token is the part that makes it feel solid: every pour increments a counter, and any in-flight animation frame that notices a newer token quietly abandons itself. Change your mind mid-pour and the glass drains and starts over — no queued animations, no fighting layers. The readout is arithmetic, not theatre: ABV is the real weighted average of the three parts plus 15 ml of melt, and the name and three-line poem are assembled from words each ingredient owns.

Details that matter

The reservation form is a demo — it validates and confirms in place but sends nothing and holds no table. Wire it to your own endpoint (Formspree, a serverless function, an email service) before promising anyone a banquette.

Ship it on GitHub Pages

Astro builds static HTML, so Pages serves it straight from the repo — no Actions workflow. Two config lines do the work:

// astro.config.mjs
export default defineConfig({
  site: 'https://bswxyz.github.io',
  base: '/nightcap',    // project-site path prefix
  outDir: './docs',     // Pages serves main + /docs
});

Every internal link goes through import.meta.env.BASE_URL so nothing 404s under the /nightcap/ prefix, and a .nojekyll in public/ keeps Pages from mangling the output. Then:

npm run build                      # emits ./docs
git add -A && git commit -m "last call"
gh repo create bswxyz/nightcap --public --source . --push
gh api --method POST /repos/bswxyz/nightcap/pages \
  -f 'source[branch]=main' -f 'source[path]=/docs'

Nightcap is a design-showcase concept — the bar, the drinks, Marguerite and the door are all fictional. The README maps what a production version would need.

← Back to Nightcap