← The tea room

The guide · how this room was made

The back room, where the tools are kept

The concept

Cha-no-ma is a fictional nine-seat tea room in Higashiyama, Kyoto, serving a seasonal omakase of five teas. The design school is Japanese minimalism, and the primary material is ma (間) — negative space treated as a substance, not an absence. Almost every decision is a removal: two typefaces, one accent colour, no photographs, no shadows, no gradients louder than paper.

The palette is four values doing the work of forty: sumi ink (#1a1a18), washi paper (#f6f4ee), matcha green (#55663f) for quiet labels, and one vermillion (#b73a25) reserved for the hanko seal and moments that deserve a stamp. Pacing is part of the palette too — nothing on the menu fades in faster than 600 milliseconds, because a tea room that hurries is not a tea room.

The techniques

Self-drawing ink strokes

Every underline and the large ensō circle are SVG paths that draw themselves. The trick is the classic dash pattern: set stroke-dasharray to the path's own length, offset it fully out of view, then transition the offset to zero when an IntersectionObserver sees the element.

// measure once, hide the stroke inside its own dash gap
var len = path.getTotalLength();
path.style.strokeDasharray  = String(len);
path.style.strokeDashoffset = String(len);
// later, when observed:
path.style.strokeDashoffset = "0"; // CSS transition does the brushwork

Brush texture without images

A geometric SVG stroke looks like a wire, not a brush. Each stroke is roughed up by an SVG filter — feTurbulence generates noise and feDisplacementMap shoves the stroke's pixels around by it. A second, thinner "dry brush" path drawn on a short delay sells the bristles.

<filter id="ink-rough">
  <feTurbulence type="fractalNoise" baseFrequency="0.055 0.14" numOctaves="3"/>
  <feDisplacementMap in="SourceGraphic" scale="3.4"/>
</filter>

The scroll-drawn ensō

The circle over the philosophy section is one arc path whose dash offset is tied to scroll position instead of a one-shot trigger: a passive scroll listener maps the section's progress through the viewport to 0–1, runs it through a smoothstep curve so the brush hesitates and then commits, and writes the offset inside requestAnimationFrame. Scroll up and the circle politely undraws. It is left unclosed on purpose — an ensō with a gap is the whole philosophy in one stroke.

Vertical writing mode

Japanese headings run top-to-bottom beside horizontal Latin text using nothing but writing-mode: vertical-rl on a flex sibling. One media query returns them to horizontal-tb below 740px, so small screens degrade to an ordinary — still composed — layout.

Paper grain and the hanko

The washi texture is a fixed, pointer-transparent overlay whose background is a data-URI SVG: feTurbulence noise pushed through feColorMatrix so only a 5% alpha channel survives. No image files anywhere. The vermillion seal is an inline SVG rect + kanji distorted by the same displacement trick, and doubles as the favicon.

The living season

The "Now" indicator is not decoration — a small table of the 24 sekki (the old Japanese solar terms) is scanned against today's date, so the hero and the final course's namagashi both name the actual current season, from 小寒 Minor Cold to 冬至 Winter Solstice.

How it was made

This site was built by Claude (Fable 5) writing vanilla HTML, CSS, and JavaScript by hand — no frameworks, no build step, no image assets, one file per page. The teas, the master, and the room are fiction; the techniques are real and view-source-able.

It is one of twenty-five deliberately different sites in the Fable Showcase, an experiment in how far hand-written code and a committed aesthetic can go. The other twenty-four rooms are at fable-25.pages.dev.

Steal this

  • One Treat whitespace as a budget you spend, not a gap you fill. Try doubling every section padding you have, then halving your font sizes back toward calm. Small type in enormous space reads as confidence.
  • Two feTurbulence + feDisplacementMap turns any crisp SVG stroke into ink, any rect into a rubber stamp. Two filter primitives replace a whole folder of texture PNGs.
  • Three Pick one slow duration (here: nothing under 600ms) and make every animation obey it. Pacing consistency does more for "expensive feel" than any easing curve.
  • Four Reserve your accent colour for one semantic job. Vermillion here means "a seal was pressed" — so every red pixel carries meaning, and the eye trusts it.
  • Five Gate your hidden-by-default reveal styles behind a .js class added in the <head>. If JavaScript ever fails, the page falls back to fully visible instead of fully blank.