Components / Japan / Wagara

Wagara beta

Ten classical Japanese patterns, constructed — not pasted: seigaiha waves, asanoha hemp, shippo circles, ichimatsu checks, kikko hexagons, uroko scales, sharkskin dots, arrow feathers, fawn spots and the diamond lattice.

Preview

Seigaiha — blue ocean waves.

<div class="k-wag" data-wag>
  <div class="k-wag-stage"><canvas aria-hidden="true"></canvas></div>
  <div class="k-wag-bar" role="group" aria-label="Pattern">
    <button class="k-wag-btn on" type="button" data-wag-p="seigaiha">青海波<small>Seigaiha</small></button>
    <button class="k-wag-btn" type="button" data-wag-p="asanoha">麻の葉<small>Asanoha</small></button>
    <button class="k-wag-btn" type="button" data-wag-p="shippo">七宝<small>Shippo</small></button>
    <button class="k-wag-btn" type="button" data-wag-p="ichimatsu">市松<small>Ichimatsu</small></button>
    <button class="k-wag-btn" type="button" data-wag-p="kikko">亀甲<small>Kikko</small></button>
    <button class="k-wag-btn" type="button" data-wag-p="uroko">鱗<small>Uroko</small></button>
    <button class="k-wag-btn" type="button" data-wag-p="same">鮫小紋<small>Same</small></button>
    <button class="k-wag-btn" type="button" data-wag-p="yagasuri">矢絣<small>Yagasuri</small></button>
    <button class="k-wag-btn" type="button" data-wag-p="kanoko">鹿の子<small>Kanoko</small></button>
    <button class="k-wag-btn" type="button" data-wag-p="hishi">菱<small>Hishi</small></button>
    <button class="k-wag-btn k-wag-tone" type="button" data-wag-tone>Tone: Paper</button>
  </div>
  <p class="k-wag-cap">Seigaiha — blue ocean waves.</p>
</div>
/* KATA Wagara — classical patterns, constructed. */
.k-wag{font-family:var(--k-font)}
.k-wag-stage{border-radius:14px;overflow:hidden;border:1px solid var(--k-line)}
.k-wag canvas{display:block;width:100%;height:228px}
.k-wag-bar{display:flex;gap:8px;flex-wrap:wrap;margin-top:11px;align-items:center}
.k-wag-btn{border:1px solid var(--k-line);background:var(--k-bg);color:var(--k-ink-2);font-family:inherit;font-size:12.5px;font-weight:550;padding:7px 13px;border-radius:99px;cursor:pointer}
.k-wag-btn small{color:var(--k-ink-3);font-weight:500;margin-inline-start:5px}
.k-wag-btn.on{background:var(--k-ink);color:var(--k-bg);border-color:var(--k-ink)}
.k-wag-btn.on small{color:var(--k-zinc-400)}
.k-wag-tone{margin-inline-start:auto;color:var(--k-accent-ink);border-color:var(--k-accent-bg)}
.k-wag-cap{font-size:12.5px;color:var(--k-ink-3);margin-top:9px}
// KATA Wagara — ten patterns, drawn from their constructions.
document.querySelectorAll('[data-wag]').forEach(function (root) {
  var canvas = root.querySelector('canvas');
  var ctx = canvas.getContext('2d');
  var DPR = Math.min(3, window.devicePixelRatio || 1);
  var cap = root.querySelector('.k-wag-cap');
  var cs = getComputedStyle(root);
  var V = function (n) { return cs.getPropertyValue(n).trim(); };
  var TONES = [
    { bg: V('--k-bg'), fg: V('--k-ink'), mid: V('--k-zinc-400'), soft: V('--k-zinc-200'), name: 'Paper' },
    { bg: V('--k-zinc-950'), fg: V('--k-zinc-200'), mid: V('--k-zinc-500'), soft: V('--k-zinc-800'), name: 'Ink' },
    { bg: V('--k-zinc-950'), fg: V('--k-accent'), mid: V('--k-zinc-500'), soft: V('--k-zinc-800'), name: 'Night' }
  ];
  var CAPS = {
    seigaiha: 'Seigaiha — blue ocean waves. Concentric arcs, each row offset by half a fan; a wish for calm seas and a long life.',
    asanoha: 'Asanoha — the hemp leaf. Six-fold geometry from a hexagon\u2019s triangles; hemp grows fast and straight, so the pattern guards children.',
    shippo: 'Shippo — seven treasures. Equal circles overlapping by a quarter, each petal a treasure, the whole an unbroken wish for harmony.',
    ichimatsu: 'Ichimatsu — the checkered pattern named for the kabuki actor Sanogawa Ichimatsu. The nested band is the one the KATA mark wears.',
    kikko: 'Kikko — the tortoiseshell. Hexagons from the turtle\u2019s shell; the tortoise lives ten thousand years, so the pattern is worn for a long life.',
    uroko: 'Uroko — scales. Overlapping triangles of fish and dragon scale; what sheds its skin sheds misfortune with it.',
    same: 'Samekomon — sharkskin. Dots riding the arcs of scales, dyed so fine it counted as plain cloth under Edo sumptuary law — luxury hiding in the dots.',
    yagasuri: 'Yagasuri — the arrow feather. An arrow once shot never returns, so the pattern rides wedding kimono: go, and do not come back.',
    kanoko: 'Kanoko — the fawn\u2019s spots. Shibori dots pinched and dyed one by one, named for the dapples down a young deer\u2019s back.',
    hishi: 'Hishi — the diamond. The floating leaf of the water caltrop, squared into a lattice that crosses the whole cloth.'
  };
  var pattern = 'seigaiha', tone = 0, W, H;
  function seigaiha(t) {
    var s = 30;
    var rows = Math.ceil(H / (s * .5)) + 2;
    for (var r = rows; r >= -1; r--) {
      var y = r * s * .5 + s * .5;
      var off = (r % 2) * s;
      for (var x = -s + off; x < W + s; x += s * 2) {
        for (var k = 4; k >= 1; k--) {
          ctx.beginPath();
          ctx.arc(x, y, s * k / 4, Math.PI, 0);
          ctx.closePath();
          ctx.fillStyle = k % 2 ? t.bg : t.soft;
          if (k === 4) ctx.fillStyle = t.bg;
          ctx.fill();
          ctx.strokeStyle = k % 2 ? t.fg : t.mid;
          ctx.lineWidth = 1.2;
          ctx.stroke();
        }
      }
    }
  }
  function asanoha(t) {
    var R = 25, dx = Math.sqrt(3) * R, dy = 1.5 * R;
    ctx.lineWidth = 1.1;
    var row = 0;
    for (var y = -R; y < H + R; y += dy, row++) {
      for (var x = -dx; x < W + dx; x += dx) {
        var cx = x + (row % 2 ? dx / 2 : 0), cy = y;
        var v = [];
        for (var k = 0; k < 6; k++) { var a = Math.PI / 3 * k - Math.PI / 2; v.push([cx + R * Math.cos(a), cy + R * Math.sin(a)]); }
        for (var k2 = 0; k2 < 6; k2++) {
          var A = v[k2], B = v[(k2 + 1) % 6];
          var mx = (A[0] + B[0] + cx) / 3, my = (A[1] + B[1] + cy) / 3;
          ctx.strokeStyle = t.mid;
          ctx.beginPath(); ctx.moveTo(cx, cy); ctx.lineTo(A[0], A[1]); ctx.stroke();
          ctx.beginPath(); ctx.moveTo(A[0], A[1]); ctx.lineTo(B[0], B[1]); ctx.stroke();
          ctx.strokeStyle = t.fg;
          [[A, B], [B, [cx, cy]], [[cx, cy], A]].forEach(function (pr) {
            ctx.beginPath(); ctx.moveTo(mx, my); ctx.lineTo(pr[0][0], pr[0][1]); ctx.stroke();
          });
          var mAB = [(A[0] + B[0]) / 2, (A[1] + B[1]) / 2], mBc = [(B[0] + cx) / 2, (B[1] + cy) / 2], mCa = [(cx + A[0]) / 2, (cy + A[1]) / 2];
          ctx.beginPath(); ctx.moveTo(mAB[0], mAB[1]); ctx.lineTo(mBc[0], mBc[1]); ctx.lineTo(mCa[0], mCa[1]); ctx.closePath(); ctx.stroke();
        }
      }
    }
  }
  function shippo(t) {
    var R = 21;
    ctx.lineWidth = 1.2; ctx.strokeStyle = t.mid;
    for (var y = 0; y < H + R; y += R) for (var x = 0; x < W + R; x += R) {
      ctx.beginPath(); ctx.arc(x, y, R, 0, 7); ctx.stroke();
    }
    ctx.fillStyle = t.fg;
    for (var y2 = 0; y2 < H + R; y2 += R) for (var x2 = 0; x2 < W + R; x2 += R) {
      ctx.beginPath(); ctx.arc(x2, y2, 1.6, 0, 7); ctx.fill();
    }
  }
  function ichimatsu(t) {
    var s = 27;
    for (var y = 0; y < H - 40; y += s) for (var x = 0; x < W; x += s) {
      if (((x / s) + (y / s)) % 2 === 0) { ctx.fillStyle = t.fg; ctx.globalAlpha = .85; ctx.fillRect(x, y, s, s); }
    }
    ctx.globalAlpha = 1;
    var s2 = 6.75, y0 = H - 40;
    ctx.fillStyle = t.soft; ctx.fillRect(0, y0, W, 40);
    for (var yy = y0; yy < H; yy += s2) for (var xx = 0; xx < W; xx += s2) {
      if (((xx / s2) + ((yy - y0) / s2)) % 2 === 0) { ctx.fillStyle = t.fg; ctx.fillRect(xx, yy, s2, s2); }
    }
  }

  function kikko(t) {
    var R = 22, dx = Math.sqrt(3) * R, dy = 1.5 * R;
    ctx.lineWidth = 1.2;
    var row = 0;
    for (var y = -R; y < H + R; y += dy, row++) {
      for (var x = -dx; x < W + dx; x += dx) {
        var cx = x + (row % 2 ? dx / 2 : 0), cy = y;
        [1, .58].forEach(function (sc, i) {
          ctx.beginPath();
          for (var k = 0; k < 6; k++) {
            var a = Math.PI / 3 * k - Math.PI / 2, px = cx + R * sc * Math.cos(a), py = cy + R * sc * Math.sin(a);
            if (k === 0) ctx.moveTo(px, py); else ctx.lineTo(px, py);
          }
          ctx.closePath();
          ctx.strokeStyle = i === 0 ? t.fg : t.mid;
          ctx.stroke();
        });
      }
    }
  }
  function uroko(t) {
    var s = 30, h = s * .87;
    ctx.lineWidth = 1.2;
    var row = 0;
    for (var y = h; y < H + h; y += h, row++) {
      for (var x = -s; x < W + s; x += s) {
        ctx.beginPath();
        ctx.moveTo(x, y); ctx.lineTo(x + s / 2, y - h); ctx.lineTo(x + s, y); ctx.closePath();
        ctx.fillStyle = row % 2 ? t.bg : t.soft;
        ctx.fill();
        ctx.strokeStyle = t.fg;
        ctx.stroke();
      }
    }
  }
  function same(t) {
    var R = 18;
    ctx.fillStyle = t.fg;
    var rows = Math.ceil(H / (R * .62)) + 2;
    for (var r = rows; r >= -1; r--) {
      var y = r * R * .62 + R;
      var off = (r % 2) * R;
      for (var x = -R + off; x < W + R; x += R * 2) {
        [1, .6].forEach(function (sc) {
          var Rk = R * sc, stepA = 5.6 / Rk;
          for (var a = Math.PI; a <= Math.PI * 2 + .001; a += stepA) {
            ctx.beginPath(); ctx.arc(x + Rk * Math.cos(a), y + Rk * Math.sin(a), 1.35, 0, 7); ctx.fill();
          }
        });
        ctx.beginPath(); ctx.arc(x, y, 1.35, 0, 7); ctx.fill();
      }
    }
  }
  function yagasuri(t) {
    var w = 24, h = 46;
    var row = 0;
    for (var y = -h; y < H + h; y += h * .8, row++) {
      for (var x = 0; x < W + w; x += w) {
        var cx = x + (row % 2 ? w / 2 : 0), cy = y;
        ctx.beginPath();
        ctx.moveTo(cx, cy);
        ctx.lineTo(cx + w * .4, cy + h * .34);
        ctx.lineTo(cx + w * .13, cy + h * .34);
        ctx.lineTo(cx + w * .13, cy + h);
        ctx.lineTo(cx, cy + h * .7);
        ctx.lineTo(cx - w * .13, cy + h);
        ctx.lineTo(cx - w * .13, cy + h * .34);
        ctx.lineTo(cx - w * .4, cy + h * .34);
        ctx.closePath();
        ctx.fillStyle = row % 2 ? t.mid : t.fg;
        ctx.globalAlpha = .88;
        ctx.fill();
        ctx.globalAlpha = 1;
      }
    }
  }
  function kanoko(t) {
    var s = 23;
    var row = 0;
    for (var y = s / 2; y < H + s; y += s, row++) {
      for (var x = s / 2 + (row % 2 ? s / 2 : 0); x < W + s; x += s) {
        ctx.beginPath(); ctx.arc(x, y, 8, 0, 7); ctx.strokeStyle = t.soft; ctx.lineWidth = 3.4; ctx.stroke();
        ctx.beginPath(); ctx.arc(x, y, 4.4, 0, 7); ctx.strokeStyle = t.fg; ctx.lineWidth = 1.5; ctx.stroke();
        ctx.beginPath(); ctx.arc(x, y, 1.5, 0, 7); ctx.fillStyle = t.fg; ctx.fill();
      }
    }
  }
  function hishi(t) {
    var s = 25;
    ctx.lineWidth = 1.1; ctx.strokeStyle = t.mid;
    ctx.beginPath();
    for (var d = -H; d < W + H; d += s) { ctx.moveTo(d, 0); ctx.lineTo(d + H, H); }
    for (var d2 = 0; d2 < W + 2 * H; d2 += s) { ctx.moveTo(d2, 0); ctx.lineTo(d2 - H, H); }
    ctx.stroke();
    ctx.fillStyle = t.fg;
    for (var gy = 0; gy <= H + s; gy += s) for (var gx = 0; gx <= W + s; gx += s) {
      var px = gx + ((gy / s) % 2 ? 0 : 0), py = gy;
      ctx.save(); ctx.translate(px, py); ctx.rotate(Math.PI / 4); ctx.fillRect(-2.1, -2.1, 4.2, 4.2); ctx.restore();
    }
  }
  function draw() {
    var t = TONES[tone];
    W = canvas.clientWidth; H = canvas.clientHeight;
    canvas.width = W * DPR; canvas.height = H * DPR; ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
    ctx.fillStyle = t.bg; ctx.fillRect(0, 0, W, H);
    ({ seigaiha: seigaiha, asanoha: asanoha, shippo: shippo, ichimatsu: ichimatsu, kikko: kikko, uroko: uroko, same: same, yagasuri: yagasuri, kanoko: kanoko, hishi: hishi })[pattern](t);
    cap.textContent = CAPS[pattern];
  }
  root.querySelectorAll('[data-wag-p]').forEach(function (b) {
    b.addEventListener('click', function () {
      root.querySelectorAll('[data-wag-p]').forEach(function (x) { x.classList.remove('on'); });
      b.classList.add('on');
      pattern = b.getAttribute('data-wag-p');
      draw();
    });
  });
  root.querySelector('[data-wag-tone]').addEventListener('click', function () { tone = (tone + 1) % TONES.length; this.textContent = 'Tone: ' + TONES[tone].name; draw(); });
  window.addEventListener('resize', draw);
  draw();
});

Installation

CLI

node kata-ui.mjs add wagara

Writes the part’s CSS and JS into your project. New to the CLI? See the CLI doc.

Manual

  1. Load tokens.css once, before any part.
  2. Copy the CSS tab into your stylesheet.
  3. Copy the HTML where it belongs. The part has a script — include it after the markup; it initializes itself.

Usage

Each pattern is drawn from its real geometry by the script — seigaiha as concentric arcs on a half-offset grid, asanoha as the true six-fold hemp construction, shippo as equal overlapping circles, kikko as nested hexagons, samekomon as dots riding the scale arcs, yagasuri as the arrow-feather silhouette. Three tones (paper, ink, night) and all colors come from the token set on purpose: the patterns are classical, the palette stays the house’s. Use them as section textures, empty-state skies, and the inside of covers.

Accessibility

Pattern is texture: the canvas is aria-hidden and any text over it must pass contrast on the flat tone color alone, pattern removed. Under prefers-reduced-motion nothing changes — patterns were always still.

Anatomy

The public surface of this part, read from its own source. Class names are the API — the CSS tab above is the full contract.

.k-wagclass
.k-wag-stageclass
.k-wag-barclass
.k-wag-btnclass
.onclass
.k-wag-toneclass
.k-wag-capclass
data-wagdata hook
data-wag-pdata hook
data-wag-tonedata hook
aria-hiddenARIA
aria-labelARIA

Related parts