Design Lounge
Install

Nº 278 of 520

Ink wash background

Sumi ink blooms that spread and dry on rice paper where you click, drawn on canvas with layered translucent washes, tide-line edges and pigment grain.

Preview

Open full size
1280 × 800 · 13.5 KB · 204 lines

Use it

Free to use · credit appreciated

1 Once, in your project

npx skills add SusanAcharya/Design-Lounge

2 Then ask

Using the Design Lounge skill, build "Ink wash background" (piece background-ink-wash) in my stack. Match its brief; restyle it onto my product's theme.

No skill? Paste the brief instead. It carries the numbers, tokens and behaviour.

Brief

216 lines

Build brief for a coding agent. Rebuild this piece in the reader’s stack. If they haven’t said which stack, ask once, then default to semantic HTML + CSS + a little vanilla JS. Match the numbers below; don’t “improve” them.

What it is

A full-frame sumi-e background behind the landing section of a fictional gallery, “Kasumi Hall”, announcing an exhibition of ink scrolls. The ground is warm rice paper with visible kozo fibres. Clicking the paper drops ink: a bloom spreads out over 2.6 seconds, darkest where it landed and pale at its reach, then dries with a faint darker tide line at its edge and a scatter of pigment grains. One or two smaller satellite blooms bleed out from it a moment later. The detail worth copying is that each bloom is painted, not tweened: every frame adds one more translucent, slightly wobbling layer at a growing radius into a canvas that is never cleared, so the gradient, the soft edge and the tonal pooling all come from accumulation. A floating panel picks the ink (sumi black, indigo, vermilion), sets how much water (bloom size), drops ink by keyboard, and rinses the paper.

Reference behaviour

  1. First frame: rice paper, four dried blooms on the right half: a large sumi wash near (76%, 36%) with a satellite, an indigo wash at (92%, 72%) with a satellite, a small sumi bloom at (62%, 80%), and a small vermilion spot at (68%, 17%). The left half, under the copy, is clear paper.
  2. Click (pointerdown) anywhere that is not a link, button, input, label or the panel: a bloom of radius water × (0.85…1.15) starts at the pointer, plus 1–2 satellites at 55–95% of that radius away, sized 22–40% of it, starting 0.5–1.1s later.
  3. A bloom grows with a cubic ease-out from 10% to 100% of its radius over 2.6s. Its edge wobbles a little as it spreads.
  4. When a bloom finishes, draw its tide line (two soft strokes just inside the edge) and a pigment grain of radius × 1.4 specks.
  5. The loop runs only while blooms are spreading; when the last one dries, requestAnimationFrame stops.
  6. The ink canvas is masked: ink is held at 28% strength over the left 40% of the frame and reaches full strength by 58%. Blooms can cross under the copy without hurting legibility.
  7. Swatches: Sumi #1b1a17 (default), Indigo #2e3f5c, Vermilion #b5332a. The pressed swatch has a 1.5px ink ring.
  8. Water slider: 70–280px, step 10, default 150. It sets the radius of the next bloom.
  9. “Drop ink” places a bloom at a random point in the clear right side (60–92% x, 20–80% y), so keyboard users get the effect. A live region says “Ink dropped”.
  10. “Rinse” fades the ink canvas to 0 over 700ms, clears it and restores opacity. A live region says “Paper rinsed”.
  11. Resize: the paper is regenerated from the same seed, and every stored bloom (the last 24) is repainted dry, so a resize never loses the painting.
  12. Reduced motion: clicks and Drop ink paint the finished bloom in one frame (all layers drawn synchronously). Rinse is instant.

Structure

1280 × 800   two fixed canvases: #paper (bottom), #ink (multiply, masked)
┌──────────────────────────────────────────────────────────────────────┐
│ [霞] Kasumi Hall                       Exhibitions  Visit  Membership │ header 88
│                                                         ░░░░▓▓▓▓░  墨 │
│ Exhibition · 12 Nov – 8 Dec 2026                       ░▓▓████▓▓░  は │ vertical text
│                                                        ░▓██████▓░  、 │ right 84 top 118
│ Ink that keeps                         68px serif       ░▓▓███▓▓░   水 │
│ moving after                                             ░░▓▓▓░░    を │
│ the brush lifts.                                     ░▓▓░     ░▓▓▓▓░ │
│ sub 16px, max 440                                   ░▓██▓░   ░▓████▓░│
│ [Reserve a time →] [Read the catalogue]              ░▓▓░     ░▓▓▓▓░ │
│ ─────────────────────────────────                   Click the paper… │ hint
│ ROOM            OPEN             ENTRY        ┌ ● ● ●  Water ──●── [Drop ink] [Rinse] ┐
│ Hall 3, east…   Tue–Sun, 10–18   ¥1,200       └──────────────── panel right 40 bottom 40 ┘
└──────────────────────────────────────────────────────────────────────┘
 page padding 0 80px; copy max-width 520, top margin 76
  • #paper: drawn once per resize at CSS pixel size (texture does not need DPR).
  • #ink: backing store × min(devicePixelRatio, 2), mix-blend-mode: multiply, linear mask.
  • .page has pointer-events: none; links and buttons re-enable them so clicks on paper reach window.
  • <header> with a vermilion seal (aria-hidden) and the name link, plus <nav aria-label="Main">.
  • <main class="copy">: kicker, <h1>, sub, two links, <dl class="facts">.
  • .tate: vertical Japanese line, writing-mode: vertical-rl, lang="ja", decorative.
  • .panel role="group": swatch group, labelled range, two tool buttons. A visually hidden aria-live paragraph.

Tokens

:root {
  --paper: #eee7d8;        /* rice paper ground */
  --paper-2: #f6f1e6;      /* panel, text on dark */
  --ink: #1b1a17;          /* type, primary button */
  --ink-2: #4b463d;        /* sub-copy, nav */
  --ink-3: #6f685b;        /* labels, hint */
  --line: rgba(27, 26, 23, .18);

  --sumi: #1b1a17;         /* inks */
  --ai: #2e3f5c;
  --shu: #b5332a;          /* seal, kicker date, focus ring; never body text */

  --serif: "Shippori Mincho", Georgia, serif;
  --sans: "Zen Kaku Gothic New", system-ui, sans-serif;

  --bloom-dur: 2.6s;
  --bloom-points: 64;      /* vertices per blob outline */
  --water-min: 70px; --water-default: 150px; --water-max: 280px;
  --blooms-kept: 24;
  --mask-quiet: .28;       /* ink strength over the copy */

  --ease: cubic-bezier(.2, .7, .2, 1);
  --t-fast: 160ms;
  --t-rinse: 700ms;
}

Typography

RoleFamilySizeWeightLine-heightTrackingCase
HeadlineShippori Mincho68px5001.12−0.01emsentence
Vertical lineShippori Mincho22px5001.60.3emJapanese
Fact valueShippori Mincho17px5001.40as written
NameShippori Mincho17px70010.04emTitle
Sub-copyZen Kaku Gothic New16px4001.70sentence
KickerZen Kaku Gothic New13px4001.70.12emsentence
ButtonsZen Kaku Gothic New14px50010.04emsentence
Fact labelZen Kaku Gothic New11px4001.40.14emUPPER
PanelZen Kaku Gothic New12px40010.06emsentence

Motion

ThingTriggerPropertyFrom → ToDurationEasingReduced motion
Bloom spreadclick / Drop inklayer radius10% → 100% of R2.6scubic ease-out 1−(1−p)³drawn complete in one frame
Layer opacityeach frameglobalAlpha0.014·(1−p)^1.5 + 0.003per layer—same total
Edge wobbleeach frameharmonic phase+0.35·t2.6slinear phasebaked
Satelliteswith a bloomstartdelay 0.5–1.1s2.6s eachsamecomplete at once
Tide line + grainbloom endstrokes, specks—one frame—same
Rinseclickink canvas opacity1 → 0, clear, → 1700ms--easeinstant
Swatch hoverhoverscale1 → 1.08160ms--ease1ms

States

  • Swatch pressed: inset 2px paper ring plus an outer 1.5px ink ring, aria-pressed="true". One pressed at a time.
  • Tool hover: background --paper, border --ink-3.
  • Primary hover: --ink → --ai. Outline hover: fills with --ink.
  • Nav hover: colour to --ink and a 1px underline.
  • Focus-visible: 2px --shu outline, offset 3px, on every link, button, swatch and the slider.
  • Spreading: loop running. Dry: loop stopped, canvas static.
  • Rinsed: clean paper, bloom list empty.

Accessibility

  • Both canvases are decorative (aria-hidden="true"). The seal glyph and the vertical line are aria-hidden too.
  • Clicking the paper is pointer-only; Drop ink is the keyboard equivalent and announces “Ink dropped” through a polite live region. Rinse announces “Paper rinsed”.
  • Swatches are buttons with names (“Sumi black”, “Indigo”, “Vermilion”) and aria-pressed. The slider has a visible “Water” label wrapping it.
  • Tab order: name → three nav links → Reserve → Catalogue → three swatches → Water → Drop ink → Rinse.
  • Contrast: #1b1a17 on #eee7d8 is 14.6:1; #4b463d sub-copy is 7.9:1. The 28% mask keeps any bloom under the copy below 30% darkening, so sub-copy stays above 4.5:1 even over the darkest sumi.
  • Vermilion is a seal and an accent on large text and the date, never for body copy.
  • Blooms stop on their own after 2.6s, so no pause control is needed.

Responsive rules

  • ≥ 1280: as specified.
  • < 1000: the vertical Japanese line hides.
  • < 640: 24px page padding, nav hides, headline 42px, sub 15px, facts wrap, buttons wrap without breaking their labels. The ink mask turns vertical: 30% strength down to 60% of the height, full by 75%, so blooms live under the copy at the bottom. Drop ink picks points at 20–80% x and 72–92% y. The panel spans the bottom with 16px insets; the tool buttons become icon-only (they keep their accessible names) and the hint hides.
  • Never scale the paper texture with the viewport; regenerate it at the new size from the same seed.

Acceptance checklist

Always

  • The ink canvas is never cleared between frames during a bloom; each frame adds one translucent layer.
  • Each layer is an irregular closed path of 64 vertices from summed sine harmonics, not a circle.
  • Bloom radius follows a cubic ease-out over 2.6s; layer opacity falls as the bloom grows, so the landing point is darkest.
  • A dried bloom gets a soft tide line just inside its edge and a scatter of grain specks.
  • One or two satellite blooms follow each click after a short delay.
  • The loop stops when no bloom is spreading.
  • The ink layer uses multiply so paper fibres show through the ink.
  • The ink is held at 28% strength behind the copy.
  • A keyboard-reachable Drop ink control exists and announces itself.
  • Resize repaints stored blooms; nothing is lost.
  • Reduced motion paints finished blooms in one frame.

This demo

  • Headline “Ink that keeps moving after the brush lifts.” in Shippori Mincho 68px.
  • Four pre-dried blooms on the right half on first paint, including one small vermilion spot.
  • Inks: Sumi #1b1a17, Indigo #2e3f5c, Vermilion #b5332a; Water default 150px.
  • Facts: Hall 3, east wing · Tue–Sun, 10–18 · ¥1,200.

Implementation notes

One layer per frame. The whole look comes from these few lines; tune the alpha before anything else:

function layer(b, p) {                       // p: 0 → 1 over 2.6s
  const e = 1 - Math.pow(1 - p, 3);
  const r = b.R * (.1 + .9 * e);
  ctx.fillStyle = b.col;
  ctx.globalAlpha = .014 * Math.pow(1 - p, 1.5) + .003;
  shape(b, r, .05 + .12 * e, p * 2.6);       // amplitude grows: the edge gets ragged as it spreads
  ctx.fill();
}
function shape(b, r, amp, t) {
  ctx.beginPath();
  for (let i = 0; i <= 64; i++) {
    const a = i / 64 * Math.PI * 2;
    let n = 0;
    for (let j = 0; j < b.h.length; j += 3) n += Math.sin(b.h[j] * a + b.h[j + 1] + t * .35) * b.h[j + 2];
    const rr = r * (1 + amp * n);
    ctx[i ? 'lineTo' : 'moveTo'](b.x + Math.cos(a) * rr, b.y + Math.sin(a) * rr);
  }
  ctx.closePath();
}

Harmonics per bloom: for k = 2…9, store [k, random phase, (0.4 + random) / (0.8k)]. Low k gives the lobes, high k the fibre-catching ragged edge.

Tide line and grain when the bloom dries:

ctx.strokeStyle = b.col;
ctx.globalAlpha = .06; ctx.lineWidth = 7;   shape(b, b.R * .975, .17, 2.6); ctx.stroke();
ctx.globalAlpha = .07; ctx.lineWidth = 2.5; shape(b, b.R * .995, .17, 2.6); ctx.stroke();
for (let i = 0; i < b.R * 1.4; i++) {        // seeded, so a repaint matches
  const a = g() * Math.PI * 2, d = Math.sqrt(g()) * b.R * .9, z = .5 + g() * 1.3;
  ctx.globalAlpha = .05 + g() * .12;
  ctx.fillRect(b.x + Math.cos(a) * d, b.y + Math.sin(a) * d, z, z);
}

Rice paper, once. Fill #eee7d8; add 14 large radial blotches (warm white at 35% or brown at 7%); one 1px speck per 700px² (brown 7% or white 40%); one fibre per 2600px²: a quadratic curve 18–98px long, 0.3–1px wide, 65% brown at 9% and 35% warm white at 40%. Use a seeded generator so resizes look the same.

Common mistakes:

  • Opacity per layer that is too high: after 156 frames the bloom is a solid black disc. The total should top out around 75% at the centre.
  • Clearing the ink canvas every frame and redrawing blooms as gradients. It looks like a vector circle, not a wash.
  • A crisp 1px outline as the tide line. Keep it soft and wide.
  • Ink drawn at full strength under the headline.
  • Forgetting the keyboard path; the click is not the only way in.
  • Re-randomising grain on resize, so the painting changes when the window moves.

Details

Palette
Type
Shippori Mincho · Zen Kaku Gothic New
Motion
Subtle motion
Build
Multi-step
Designed bySusan AcharyaKathmandu

Studied

Susan Acharya

Draw one scene and let it move a little. The words stay calm on top.

All sources