← kiku-kissaten.pages.dev · design guide
A build log for the most restrained site of the collection: what was decided, what was generated, what was coded, and what three rounds of ruthless self-review changed.
The brief: a Kyoto kissaten — a slow-coffee house — rendered in wabi-sabi. Silence, paper, steam, one perfect cup. The design bet is that restraint executed precisely reads as luxury: near-empty layouts, one accent color used like a hanko seal (rarely, and with intent), and motion so slow it feels like breathing.
Every section keeps a vertical Japanese side-label (real writing-mode: vertical-rl, not rotated text), bilingual headings, and dashed rules that echo hand-sewn book binding.
Shippori Mincho for display — a mincho with genuine Japanese glyph coverage, so 一期一会 and "KIKU" share one voice. Zen Kaku Gothic New (300/400) for Latin body: geometric but soft, wide letter-spacing, line-height 1.9. The pairing mirrors the site's bilingualism: brush heritage for headlines, quiet modernity for reading.
Three images, generated with GPT Image 2 (model: gpt-image-2, quality high) and art-directed to feel like Kinfolk-magazine film photography. Actual prompts:



Five wisps rise from the hero cup on a <canvas> in mix-blend-mode: screen. Instead of shipping a noise library, the drift is layered incommensurate sines — perlin-ish, three octaves, four lines of math:
function noise(x, t) {
return Math.sin(x*1.7 + t*0.9) * 0.55
+ Math.sin(x*3.1 - t*0.6 +1.3) * 0.28
+ Math.sin(x*5.3 + t*1.7 +4.1) * 0.17;
}
Each wisp is a 26-segment polyline whose sway amplitude grows with height, stroked with a vertical alpha gradient and ctx.filter: blur(6px). An IntersectionObserver pauses the rAF loop when the hero scrolls away; prefers-reduced-motion removes it entirely.
The ensō circle and the Gion map are SVG paths drawn in on scroll. JS measures each path with getTotalLength(), writes it to a --len custom property, and a class toggle runs stroke-dashoffset: var(--len) → 0 with staggered delays — streets first, lanes after, the vermillion shop-dot popping last.
The seal starts at scale(2.2) rotate(-14deg), invisible — hovering above the paper — then drops with an overshoot curve (cubic-bezier(0.34, 1.4, 0.44, 1)): squash to 0.94, rebound to 1.05, settle at 1 with a −5° skew like a real hand-stamp. Box-shadow insets fake the carved border.
Sections breathe in over 1.8 s — deliberately slower than web-default — with cubic-bezier(0.22, 1, 0.36, 1) and 36 px of rise. Section padding is clamp(7rem, 18vh, 13rem): the emptiness is the layout.
A fixed full-viewport overlay of SVG feTurbulence fractal noise encoded as a data URI — zero requests, ~500 bytes — at 3% effective opacity. It kills the "flat hex background" feel and makes the washi read as fiber.
The two photographs in The Space drift a few pixels against scroll (rAF-throttled, ±26px, 5–8% overscale inside overflow:hidden frames), and the hero cup breathes through a 26-second ken-burns cycle — slow enough to read as light moving rather than motion.
Every animation's hidden initial-state is gated behind an html.js class added by a pre-paint inline script. No JS → the full site renders statically. The same mechanism powers the QA hooks used while iterating: ?static=1 (end-states) and ?solo=<section> (renders one section in isolation).
html.js class) and added the ?static/?solo QA hooks.--roast-text #6E5F51 (5.5:1) for captions, notes and footer lines.Static files, no build step. Deploy from the site directory:
npx wrangler pages deploy . --project-name kiku-kissaten
/ the site · /guide this page.