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

WORK 02

mutation

変化の軌跡

YEAR
2026
TOOLS
p5.js
TECHNIQUE
linear transformation / deterministic drift

Each day, the same shape undergoes one irreversible transformation: rotation plus shear. Today is amber, the past fades to silver-blue. The shape never returns to yesterday.

毎日、同じ形に一つの変換が加わる——回転とせん断。昨日には戻れない。今日は琥珀色に、過去は青銀色に薄れていく。

SOURCE / ソース
// Each day, the shape undergoes one irreversible transformation:
// rotation + shear. The combined matrix has eigenvalues on the unit
// circle (trace ≈ 1.999), so points orbit without diverging.
// Today is drawn bright amber; the last 40 days fade to silver-blue.

export default function sketch(p) {
  const TRAIL = 40;
  const THETA = 0.028; // radians per day
  const SH    = 0.006; // shear per day

  // Slightly irregular pentagon — asymmetry is amplified by shear over time
  const BASE = [
    [   0, -110],
    [  95,  -30],
    [  68,   92],
    [ -68,   92],
    [ -95,  -30],
  ];

  function daysSinceEpoch() {
    const epoch = new Date('2026-01-01');
    const now   = new Date();
    const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
    return Math.floor((today - epoch) / 86400000);
  }

  function step(pts) {
    const c = Math.cos(THETA), s = Math.sin(THETA);
    return pts.map(([x, y]) => {
      const rx = c * x - s * y;
      const ry = s * x + c * y;
      return [rx + SH * ry, ry];
    });
  }

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

    const cx = p.width  / 2;
    const cy = p.height / 2;
    const d        = daysSinceEpoch();
    const startDay = Math.max(0, d - TRAIL);

    // Build states incrementally from startDay → today
    let pts = BASE.map(pt => [...pt]);
    for (let i = 0; i < startDay; i++) pts = step(pts);

    const states = [];
    for (let day = startDay; day <= d; day++) {
      states.push({ back: d - day, pts: pts.map(pt => [...pt]) });
      pts = step(pts);
    }

    p.background(252, 250, 245);

    // Vertex trails — faint lines tracing each vertex's path
    for (let vi = 0; vi < BASE.length; vi++) {
      for (let si = 0; si < states.length - 1; si++) {
        const { back: b0, pts: p0 } = states[si];
        const { back: b1, pts: p1 } = states[si + 1];
        const alpha = p.map(Math.min(b0, b1), 0, TRAIL, 45, 4);
        p.stroke(140, 160, 200, alpha);
        p.strokeWeight(0.5);
        p.line(
          cx + p0[vi][0], cy + p0[vi][1],
          cx + p1[vi][0], cy + p1[vi][1]
        );
      }
    }

    // Polygon for each day — oldest first so today is drawn on top
    for (const { back, pts: dayPts } of states) {
      const isToday = back === 0;
      const alpha = isToday ? 220 : p.map(back, 1, TRAIL, 88, 6);
      const sw    = isToday ? 1.8  : 0.7;

      let r, g, b;
      if (isToday) {
        r = 200; g = 125; b = 45;   // warm amber
      } else {
        const t = back / TRAIL;
        r = p.lerp(165, 105, t);
        g = p.lerp(150, 125, t);
        b = p.lerp(210, 170, t);    // silver-blue fading to slate
      }

      p.stroke(r, g, b, alpha);
      p.strokeWeight(sw);
      p.noFill();
      p.beginShape();
      for (const [px, py] of dayPts) p.vertex(cx + px, cy + py);
      p.endShape(p.CLOSE);

      if (isToday) {
        p.noStroke();
        p.fill(200, 125, 45, 200);
        for (const [px, py] of dayPts) p.circle(cx + px, cy + py, 7);
      }
    }
  };
}