Fernweh is a booking site for cabins that don’t exist, built to move the way its product feels: slow, layered, a little far away. These are the working notes — the idea, the stack, and the two techniques that carry it.
Slow-travel brands usually oversell with photography. Fernweh undersells on purpose: one misty photo, one whispered line (“The signal fades. You won’t miss it.”), and then a world drawn from layered ridge silhouettes that drift as you scroll. The product’s promise is the absence of things — wifi, signal, hurry — so the design’s job was restraint: minimal chrome, a nav that stays out of the way, and “no wifi” typeset as an amenity, not a warning.
The interaction patterns are borrowed from shipped booking products (researched on Mobbin): Airbnb’s price-first booking card with visible arithmetic and its “you won’t be charged yet” reassurance line, Booking.com’s name-→-distance tables, Tripadvisor’s availability-as-a-calm-fact. Each one got de-alarmed and rewritten in Fernweh’s voice.
<CabinArt kind="ember" />
and <Ridges /> are server-rendered SVG components — reusable like React
components, weightless like markup.cubic-bezier(.19,1,.22,1) — a long,
lazy settle. Reveals take 1.15s, the hero photo takes 3.2s. Nothing on this site is in a hurry.The backdrop is a position:fixed stack: a sky gradient, a box-shadow starfield,
five SVG ridge silhouettes and two blurred mist bands. Depth comes from two cheap tricks —
atmospheric perspective (farther ridges are paler and more transparent) and differential
motion (farther ridges move less):
<svg class="ridge r5 px" data-amp="22" ...> <!-- farthest, palest -->
<svg class="ridge r3 px" data-amp="68" ...>
<svg class="ridge r1 px" data-amp="140" ...> <!-- nearest, darkest --> Instead of multiplying scrollY by a rate (which drifts unbounded on a long page),
each layer’s data-amp is the total distance it may rise across the whole
document. Scroll progress eases it there:
const p = Math.min(1, scrollY / (docHeight - innerHeight));
for (const el of layers)
el.style.transform =
`translate3d(0, ${-p * el.dataset.amp}px, 0)`; One rAF-throttled scroll handler paints every layer plus a slower-than-scroll drift on the
hero photograph. Each layer is anchored with a negative bottom equal to its
amplitude, so no seam ever shows. The mist bands add a second, time-based motion — a 34–46s
CSS translateX loop — so the landscape breathes even when you stop scrolling.
And because the whole thing reads through the transparent “slow manifesto” section, the ridges
become the page’s quietest moment instead of a background you scroll past.
Reduced motion: if prefers-reduced-motion is set, the scroll
listener is never attached, every CSS animation is killed with one media query, and the layers
hold their designed positions. The site is fully legible as a still image.
update() function that
recomputes $rate × nights, swaps the availability line (“Free. Bring the thick socks.”), and
warns — gently — when four people pick a two-sleeper. Submitting admits the truth:
nothing was sent, nobody was charged..js to
<html>; every reveal and hero animation is gated on it, so the page is
complete with scripts disabled.Astro builds into docs/ so Pages can serve the main branch directly — no Actions
workflow, no second branch. Three lines of config do all the path work:
// astro.config.mjs
export default defineConfig({
site: 'https://bswxyz.github.io',
base: '/fernweh-cabins',
outDir: './docs',
}); Internal links use import.meta.env.BASE_URL so nothing 404s under the
/fernweh-cabins/ prefix, and an empty .nojekyll in
public/ keeps Pages from mangling the output. Then:
npm run build
git add -A && git commit
gh repo create bswxyz/fernweh-cabins --public --source . --push
gh api --method POST /repos/bswxyz/fernweh-cabins/pages \
-f 'source[branch]=main' -f 'source[path]=/docs'
# live at https://bswxyz.github.io/fernweh-cabins/ in ~a minute Fernweh is a design-showcase concept — the cabins are fictional, the booking card is a teaser, and the only photograph is AI-generated. See the repository README for the full demo-vs-real map.
← Back to Fernweh