Making-of notes · 08

A landscape with breathing room.

This small tourist-board site is an exercise in making depth feel tactile: an invented fjord, rendered as a stack of shifting paper rather than a picture of one.

A destination told through its pace.

Mør Fjord is deliberately specific: not a list of superlatives, but a place whose useful details are a late boat, a wet trail, a soup waiting at the office. The page gives that mood a horizon. It begins peach and open; farther down the water and copy settle into evening indigo.

The layout keeps the first two viewports mostly clear, so the paper-cut scene gets a chance to do its quiet work before the practical field notes begin.

Six pieces of paper, never one image.

Every ridge is an inline SVG path in its own group. Each group has a real feDropShadow filter, so its edge softly lands on the paper behind it. A directional, low-amplitude noise filter gives every sheet a faint fibre without turning the scene into a photograph. The paths begin as a cohesive landscape, then their SVG transforms pull them apart at six different rates as the document scrolls.

The movement is eased toward the actual scroll position rather than jumping to it. In the same animation frame, the sun travels across the viewBox and the sky mixes from dawn peach to a dusk-leaning indigo. A smaller three-layer threshold borrows that same progress at the handoff to the field notes. Farther down, the Light Keeper’s Dial uses the same rules as a directly controlled, four-layer study. With reduced motion enabled, the automatic landscapes stay still.

function draw(progress) {
  layers.forEach(([layer, x, y]) =>
    layer.setAttribute('transform', `translate(${x * progress} ${y * progress})`)
  );
  thresholdLayers.forEach(([layer, x, y]) =>
    layer.setAttribute('transform', `translate(${x * progress} ${y * progress})`)
  );
  sun.setAttribute('cx', String(mix(1110, 365, progress)));
  sun.setAttribute('cy', String(mix(188, 308, progress)));
  sun.setAttribute('fill', `rgb(${mix(255, 244, progress)}, ${mix(240, 190, progress)}, ${mix(203, 116, progress)})`);
}

function setLight(value) {
  const numericValue = Number(value);
  const progress = clamp(Number.isFinite(numericValue) ? numericValue / 100 : 0);
  range.value = String(Math.round(progress * 100));
  layers.forEach(([layer, x, y]) =>
    layer.setAttribute('transform', `translate(${x * progress} ${y * progress})`)
  );
  sun.setAttribute('cx', String(mix(700, 190, progress)));
  const top = `rgb(${mix(255, 43, progress)}, ${mix(201, 58, progress)}, ${mix(163, 103, progress)})`;
  const middle = `rgb(${mix(246, 58, progress)}, ${mix(185, 74, progress)}, ${mix(149, 121, progress)})`;
  const base = `rgb(${mix(217, 42, progress)}, ${mix(167, 57, progress)}, ${mix(161, 93, progress)})`;
  view.style.background = `linear-gradient(180deg, ${top} 0%, ${middle} 58%, ${base} 100%)`;
}

<filter id="shadow-near">
  <feDropShadow dx="0" dy="24" stdDeviation="22"
    flood-color="#18233f" flood-opacity=".3" />
</filter>

Warm paper, cool distance.

The two anchors are dawn peach and dusk indigo. Cream is the physical paper of the foreground; muted blue-greens let the middle ridges recede without turning photographic.

Dawn peach#FFC9A3
Paper cream#FAF5EC
Ridge blue#6F8B99
Dusk indigo#2B3A67
Poppins for the horizon.
Poppins, Arial, sans-serif for directions, details, and generous readable line-height.

Three small corrections.

Pass 1 — correctness

  • Reworked the hero scroll controller so it draws a correct initial state, pauses while the page is hidden or the hero is off-screen, resumes safely, and reacts if the reduced-motion setting changes. The direct frame interpolation now controls the sky without a competing CSS background transition.
  • Added stable cream supports behind navigation and guide heading copy, strengthened small muted labels, and added high-visibility keyboard focus rings plus 44px minimum targets for navigation and footer links.
  • Made the guide’s grid child shrinkable and its code sample explicitly width-bounded, so only the code itself may scroll at narrow widths. Added a guide skip link and kept both required gallery/site links visible.
  • Known limitation: source-level checks were completed, but live responsive screenshots and browser-console inspection could not run in this session because no in-app browser was available.

Pass 2 — design depth

  • Weak moment: the paper-cut effect stopped at the hero. Added a second, overlapping three-layer paper threshold where the landscape hands off to the field notes. It inherits the same scroll progress, depth shadows, and staggered separation, so the technique has a real after-image rather than a hard stop.
  • Weak moment: type had a restrained, serif-led hierarchy. Moved display type to the stated Poppins voice, increased the hero and section scale contrast, tightened display tracking, and set intentional headline breaks so the final words do not become accidental orphans.
  • Weak moment: the middle settled into evenly divided panels. Established a shared fluid spacing scale, made the itinerary columns intentionally uneven with alternating beats, pushed the seasonal wheel against a peach field, and let the visitor-office card cantilever into the dusk section.

Pass 3 — complexify

  • Weak moment: after the scroll hero, the visitor had nothing personal to discover. Added the Light Keeper’s Dial below the seasonal notes: an accessible range control with three keyboard-focusable presets. It moves a fresh four-layer paper study, crosses the sky from peach to indigo, and changes the local advice as the hour changes.
  • Weak moment: the cut layers read clean, but a little too clean. Rebuilt the grain as directional, low-amplitude SVG fibre and repeated it in the dial’s miniature landscape. The shadows still make the depth, while the paper now has just enough material memory to be noticed at rest.
  • Finished the first-paint details by keeping the initial SVG and sky values identical to the initial JavaScript state and asking the font to load opportunistically rather than swap late. Added matching Open Graph metadata, preserved visible focus treatments, and gave both ends of the dial a specific edge-of-day note instead of an empty readout.
  • Final source review covered the 390px, 768px, and 1440px layout rules, overflow guards, and reduced-motion paths. The in-app browser was unavailable in this session, so live console and viewport inspection could not be repeated here.