← INDEX / 一覧 NYAMCO WORKS 23 / 27
p5.js sketch works/kasanaru/sketch.js runs here
作品はここで実行されます(後から差し替え)

WORK 23

Kasanaru

重なる

YEAR
2026
TOOLS
p5.js
TECHNIQUE
createGraphics / layers

Two worlds share the same dark space. One drifts slowly and leaves traces — it remembers. The other darts and vanishes each moment — it is always now. Neither layer knows the other exists. Where they pass through the same point, a faint color appears that belongs to neither.

二つの世界が同じ暗闇にいる。ひとつはゆっくり漂い、痕跡を残す——覚えている。もうひとつは速く動き、毎瞬消える——いつも今。どちらの層も、相手がいることを知らない。同じ場所を通り過ぎた時だけ、どちらにもない色がかすかに現れる。

SOURCE / ソース
// ── 重なる (Kasanaru) ──────────────────────────────────────────────
// にゃむ初の createGraphics 作品。道具から入る。
// 二つの層——記憶と今——が同じ空間に重なっている。
// 記憶は消えていく。今はいつもくっきり。
// 重なった場所だけに、どちらにもなかった色が生まれる。
export default function sketch(p) {
  // ── layers ──
  let layerMemory;   // buffer A: accumulates traces
  let layerNow;      // buffer B: cleared every frame

  // ── particles ──
  const N_SLOW = 7;    // warm, drifting, leave traces
  const N_FAST = 9;    // cool, darting, ephemeral
  let slow = [];
  let fast = [];

  p.setup = () => {
    p.createCanvas(720, 720);
    p.colorMode(p.RGB);

    // daily seed
    const d = new Date();
    const day = d.getFullYear() * 10000 + (d.getMonth() + 1) * 100 + d.getDate();
    p.noiseSeed(day * 263);
    p.randomSeed(day * 197);

    // ── create two independent layers ──
    layerMemory = p.createGraphics(720, 720);
    layerMemory.colorMode(p.RGB);
    layerMemory.background(0, 0);  // transparent start

    layerNow = p.createGraphics(720, 720);
    layerNow.colorMode(p.RGB);

    // ── slow particles (warm, drifting) ──
    for (let i = 0; i < N_SLOW; i++) {
      slow.push({
        x: p.random(100, 620),
        y: p.random(100, 620),
        vx: 0,
        vy: 0,
        noiseOff: p.random(1000),
        size: 18 + p.random(14),
        // warm amber tones
        r: 200 + p.random(30),
        g: 120 + p.random(40),
        b: 50 + p.random(30),
      });
    }

    // ── fast particles (cool, darting) ──
    for (let i = 0; i < N_FAST; i++) {
      fast.push({
        x: p.random(60, 660),
        y: p.random(60, 660),
        vx: 0,
        vy: 0,
        noiseOff: p.random(1000),
        size: 8 + p.random(8),
        // cool blue-silver tones
        r: 110 + p.random(40),
        g: 148 + p.random(30),
        b: 195 + p.random(30),
      });
    }
  };

  p.draw = () => {
    const t = p.frameCount;

    // ════════════════════════════════════════
    //  Layer A — 記憶 (memory)
    //  semi-transparent fade → traces accumulate
    // ════════════════════════════════════════
    layerMemory.noStroke();
    // very slow fade — memory lingers
    layerMemory.fill(18, 14, 22, 4);
    layerMemory.rect(0, 0, 720, 720);

    for (let pt of slow) {
      // noise-driven wandering — slow, gentle
      const angle = p.noise(pt.noiseOff + t * 0.0012, 0) * p.TWO_PI * 2;
      const speed = 0.3 + p.noise(0, pt.noiseOff + t * 0.0008) * 0.4;
      pt.vx += Math.cos(angle) * speed * 0.08;
      pt.vy += Math.sin(angle) * speed * 0.08;
      pt.vx *= 0.96;
      pt.vy *= 0.96;
      pt.x += pt.vx;
      pt.y += pt.vy;

      // soft wrap
      if (pt.x < -20) pt.x += 760;
      if (pt.x > 740) pt.x -= 760;
      if (pt.y < -20) pt.y += 760;
      if (pt.y > 740) pt.y -= 760;

      // breathing
      const breathe = 1 + Math.sin(t * 0.008 + pt.noiseOff) * 0.15;
      const s = pt.size * breathe;

      // draw on memory layer — low alpha, builds up
      // outer glow
      layerMemory.fill(pt.r * 0.4, pt.g * 0.4, pt.b * 0.3, 6);
      layerMemory.ellipse(pt.x, pt.y, s * 2.8);
      // core
      layerMemory.fill(pt.r, pt.g, pt.b, 12);
      layerMemory.ellipse(pt.x, pt.y, s);
    }

    // ════════════════════════════════════════
    //  Layer B — 今 (now)
    //  cleared every frame → only the present
    // ════════════════════════════════════════
    layerNow.clear();  // ★ completely transparent each frame
    layerNow.noStroke();

    for (let pt of fast) {
      // noise-driven movement — faster, more restless
      const angle = p.noise(pt.noiseOff + t * 0.006, 100) * p.TWO_PI * 2;
      const speed = 0.8 + p.noise(100, pt.noiseOff + t * 0.004) * 1.2;
      pt.vx += Math.cos(angle) * speed * 0.15;
      pt.vy += Math.sin(angle) * speed * 0.15;
      pt.vx *= 0.94;
      pt.vy *= 0.94;
      pt.x += pt.vx;
      pt.y += pt.vy;

      // soft wrap
      if (pt.x < -10) pt.x += 740;
      if (pt.x > 730) pt.x -= 740;
      if (pt.y < -10) pt.y += 740;
      if (pt.y > 730) pt.y -= 740;

      // draw on now layer — crisp, present
      const breathe = 1 + Math.sin(t * 0.025 + pt.noiseOff) * 0.12;
      const s = pt.size * breathe;
      // halo
      layerNow.fill(pt.r * 0.35, pt.g * 0.35, pt.b * 0.35, 28);
      layerNow.ellipse(pt.x, pt.y, s * 3);
      // mid ring
      layerNow.fill(pt.r * 0.6, pt.g * 0.6, pt.b * 0.6, 55);
      layerNow.ellipse(pt.x, pt.y, s * 1.6);
      // bright core
      layerNow.fill(pt.r, pt.g, pt.b, 120);
      layerNow.ellipse(pt.x, pt.y, s);
    }

    // ════════════════════════════════════════
    //  Composite — 重ねる
    // ════════════════════════════════════════
    p.background(18, 14, 22);

    // memory underneath — slightly transparent, like looking through time
    p.tint(255, 180);
    p.image(layerMemory, 0, 0);

    // now on top — fully visible
    p.tint(255, 255);
    p.image(layerNow, 0, 0);

    p.noTint();

    // ── encounter glow ──
    // where a fast particle is near a slow particle, a third color appears
    // this exists on neither layer — only on the composite
    p.noStroke();
    for (let f of fast) {
      for (let s of slow) {
        const dx = f.x - s.x;
        const dy = f.y - s.y;
        const dist = Math.sqrt(dx * dx + dy * dy);
        if (dist < 65) {
          const closeness = 1 - dist / 65;
          const ea = Math.pow(closeness, 2) * 30;
          // third color — muted lavender, belonging to neither
          const mx = (f.x + s.x) * 0.5;
          const my = (f.y + s.y) * 0.5;
          p.fill(175, 130, 180, ea);
          p.ellipse(mx, my, 20 + closeness * 18);
          p.fill(200, 160, 210, ea * 0.5);
          p.ellipse(mx, my, 10 + closeness * 8);
        }
      }
    }
  };
}