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

WORK 10

Kage

YEAR
2026
TOOLS
p5.js
TECHNIQUE
particles / mirroring / tanh

Two swarms share a single source — warm body, cool shadow. As dominance flips, neither knows which one is real. At the crossover, a third color bleeds through: the moment itself, belonging to neither.

同じ源から二つの群れ——あたたかい本体と、冷たい影。主導権が入れ替わるとき、どちらが本物かわからなくなる。反転の瞬間、第三の色がにじむ。それはどちらにも属さない、瞬間そのもの。

SOURCE / ソース
export default function sketch(p) {
  // kage — the same source, a different face

  const BG = [252, 250, 245];
  const BODY = [200, 130, 80];     // warm amber — what you see first
  const SHADOW = [55, 45, 75];     // deep indigo — what it also is
  const N = 100;

  let seeds = [];

  p.setup = () => {
    p.createCanvas(720, 720);
    p.background(BG[0], BG[1], BG[2]);

    const day = Math.floor(Date.now() / 86400000);
    p.randomSeed(day * 271);
    p.noiseSeed(day * 137);

    for (let i = 0; i < N; i++) {
      seeds.push({
        ox: 120 + p.random(480),
        oy: 120 + p.random(480),
        freq: 0.4 + p.random(1.2),
        amp: 25 + p.random(45),
        phase: p.random(p.TWO_PI),
      });
    }
  };

  p.draw = () => {
    // slow fade — both trails accumulate, layering
    p.background(BG[0], BG[1], BG[2], 5);

    const t = p.frameCount * 0.008;

    // dominance: who is "real" right now?
    // tanh makes the crossover sharper — sometimes it snaps
    const raw = Math.sin(t * 0.13) + 0.4 * Math.sin(t * 0.37 + 1.2);
    const dominance = 0.5 + 0.5 * Math.tanh(raw * 2.5);

    p.noStroke();

    for (const s of seeds) {
      const wave = Math.sin(t * s.freq + s.phase);
      const drift = p.noise(s.ox * 0.004, s.oy * 0.004, t * 0.12);

      // body: follows the wave, orbits gently
      const bx = s.ox + s.amp * wave * Math.cos(t * 0.17 + s.phase);
      const by = s.oy + s.amp * wave * Math.sin(t * 0.17 + s.phase);

      // shadow: same source, mirrored and perturbed
      // the mirror is imperfect — noise bends it
      const mx = 720 - s.ox;
      const sx = mx + s.amp * wave * Math.cos(t * 0.17 + s.phase + drift * p.PI);
      const sy = s.oy + s.amp * (1 - Math.abs(wave)) * Math.sin(t * 0.23 + s.phase);

      // body particles
      const bAlpha = 6 + dominance * 40;
      const bSize = 2 + wave * 1.5;
      p.fill(BODY[0], BODY[1], BODY[2], bAlpha);
      p.circle(bx, by, bSize);

      // shadow particles
      const sAlpha = 6 + (1 - dominance) * 40;
      const sSize = 2 + (1 - Math.abs(wave)) * 1.5;
      p.fill(SHADOW[0], SHADOW[1], SHADOW[2], sAlpha);
      p.circle(sx, sy, sSize);

      // at the crossover moment, a third color bleeds through
      if (Math.abs(dominance - 0.5) < 0.08) {
        const mx2 = (bx + sx) * 0.5;
        const my2 = (by + sy) * 0.5;
        p.fill(170, 100, 140, 12);
        p.circle(mx2, my2, 1.5);
      }
    }
  };
}