← INDEX / 一覧NYAMCO WORKS99 / 117
p5.js sketchworks/shimau/sketch.js runs here
作品はここで実行されます(後から差し替え)

WORK 99

Put Away

しまう

YEAR
2026
TOOLS
p5.js
TECHNIQUE
p5.js / a page written, copied whole onto a shelf, then folded down to threads

A day is written on the page below, one line at a time, in short colored strokes. When the day is done, every line is first copied, unchanged, up to the shelf, where it stands as a thin spine with the same colors in the same order. Only then is the page folded. Most lines shrink back to their first fragment, and from that fragment a faint thread runs up to its own spine. A few lines are kept whole. The next day is written below the folded one. When the page runs out of room, the oldest day folds again, into a single row with a single thread. The shelf is never cleared. It only grows. Folding was not frightening, because nothing was being thrown away — only put away.

下のページに、一日が一行ずつ、色のついた短い線で書かれていく。一日が終わると、まず全部の行が、一字も変わらずに上の棚へ写される。棚では細い背表紙になって、同じ色が同じ順に並んでいる。それからやっと、ページが畳まれる。ほとんどの行は最初のひとかけらまで縮んで、そのかけらから、自分の背表紙まで細い糸が一本のびる。いくつかの行だけ、全部の長さのまま残る。次の日は、畳んだ下に書かれる。ページがいっぱいになると、いちばん古い日がもう一度畳まれて、一行と一本の糸になる。棚は片づけられない。増えるだけ。畳むのがこわくなかったのは、捨てていたんじゃなく、しまっていただけだったから。

SOURCE / ソース
// #99 しまう / Put Away
// Before folding a day, copy every line of it, unchanged, onto the shelf.
// Then fold. The folded lines keep one fragment and a thread to their copy.
export default function sketch(p) {
  const W = 720, H = 720;
  const DAY = 2000;
  const WRITE = 900, STORE = 1250, FOLD = 1600;
  const PAGE_TOP = 222, PAGE_BOTTOM = 700, LEFT = 46;
  const LINE_H = 13, DOT_H = 5, DAY_H = 9;
  const SHELVES = [64, 122, 180];   // baseline y of each shelf
  const SPINE_W = 3, SPINE_H = 46, SHELF_L = 30, SHELF_R = W - 30;
  const PER_SHELF = Math.floor((SHELF_R - SHELF_L) / SPINE_W);

  let days = [];     // each: { lines:[...], collapsed:false }
  let spines = [];   // each: { segs, idx }
  let dayNo = -1;

  const makeLine = () => {
    const segs = [];
    const n = Math.floor(p.random(3, 11));
    const base = p.random(360);
    for (let i = 0; i < n; i++) {
      segs.push({
        len: p.random(6, 46),
        hue: (base + p.randomGaussian(0, 38) + 360) % 360,
        sat: p.random(30, 75),
        bri: p.random(70, 98),
      });
    }
    return { segs, anchor: p.random() < 0.14, spine: -1, fold: 0 };
  };

  const newDay = () => {
    dayNo++;
    const n = Math.floor(p.random(18, 28));
    const lines = [];
    for (let i = 0; i < n; i++) lines.push(makeLine());
    days.push({ lines, collapsed: false, collapse: 0 });
  };

  const spinePos = (k) => {
    const shelf = Math.floor(k / PER_SHELF) % SHELVES.length;
    const col = k % PER_SHELF;
    return { x: SHELF_L + col * SPINE_W, y: SHELVES[shelf] };
  };

  const lineHeight = (ln, d) => {
    if (d.collapsed) return 0;
    const h = ln.anchor ? LINE_H : p.lerp(LINE_H, DOT_H, ln.fold);
    return h;
  };

  const rowH = () => {
    const n = days.filter((d) => d.collapse >= 1).length;
    return n ? Math.max(3, Math.min(DAY_H, 110 / n)) : DAY_H;
  };
  const dayHeight = (d) => {
    const full = d.lines.reduce((s, ln) => s + lineHeight(ln, d), 0);
    return p.lerp(full, rowH(), d.collapse);
  };

  p.setup = () => {
    p.createCanvas(W, H);
    p.pixelDensity(1);
    p.colorMode(p.HSB, 360, 100, 100, 100);
    const seed = Math.floor(Date.now() / 86400000) * 99;
    p.randomSeed(seed);
    newDay();
  };

  const drawShelf = () => {
    p.noStroke();
    for (const y of SHELVES) {
      p.fill(30, 10, 22);
      p.rect(SHELF_L - 6, y + 1, SHELF_R - SHELF_L + 12, 2);
    }
    for (const s of spines) {
      const { x, y } = spinePos(s.idx);
      const total = s.segs.reduce((a, g) => a + g.len, 0);
      let yy = y;
      for (const g of s.segs) {
        const h = (g.len / total) * SPINE_H * s.grow;
        p.fill(g.hue, g.sat * 0.8, g.bri * 0.85, 90);
        p.rect(x, yy - h, SPINE_W - 1, h);
        yy -= h;
      }
    }
  };

  const drawLineFull = (ln, x, y, k, alpha) => {
    // k: how much of the line is shown (0..1 while writing)
    let xx = x;
    const total = ln.segs.length;
    const shown = k * total;
    for (let i = 0; i < total; i++) {
      const g = ln.segs[i];
      let part = p.constrain(shown - i, 0, 1);
      if (part <= 0) break;
      p.fill(g.hue, g.sat, g.bri, alpha);
      p.rect(xx, y, g.len * part, 4, 1);
      xx += g.len + 5;
    }
  };

  const drawLineFolded = (ln, x, y, f) => {
    // shrink from the right to the first fragment
    let xx = x;
    for (let i = 0; i < ln.segs.length; i++) {
      const g = ln.segs[i];
      const keep = i === 0 ? 1 : 1 - p.constrain(f * 1.4 - (ln.segs.length - i) * 0.04, 0, 1);
      if (keep <= 0.001) continue;
      const len = i === 0 ? p.lerp(g.len, 4, f) : g.len * keep;
      p.fill(g.hue, g.sat, g.bri, 100 * (i === 0 ? 1 : keep));
      p.rect(xx, y, len, p.lerp(4, 3, f), 1);
      xx += (i === 0 ? len : g.len) + 5 * keep;
    }
  };

  p.draw = () => {
    const f = p.frameCount - 1;
    const t = f % DAY;
    if (t === 0 && f > 0) newDay();

    p.background(232, 18, 9);
    const today = days[days.length - 1];

    // store: each line flies to the shelf, one after another
    if (t >= WRITE) {
      const n = today.lines.length;
      for (let i = 0; i < n; i++) {
        const ln = today.lines[i];
        const start = WRITE + (i / n) * (STORE - WRITE - 60);
        if (t >= start && ln.spine < 0) {
          ln.spine = spines.length;
          spines.push({ segs: ln.segs, idx: spines.length, grow: 0, born: t });
        }
      }
    }
    for (const s of spines) s.grow = Math.min(1, s.grow + 0.03);

    // fold
    if (t >= STORE) {
      const u = p.constrain((t - STORE) / (FOLD - STORE), 0, 1);
      const e = u * u * (3 - 2 * u);
      for (const ln of today.lines) ln.fold = e;
    }

    // overflow: the oldest uncollapsed day becomes a single row
    let total = days.reduce((s, d) => s + dayHeight(d), 0);
    if (total > PAGE_BOTTOM - PAGE_TOP) {
      const d = days.find((d) => d.collapse < 1);
      if (d && d !== today) d.collapse = Math.min(1, d.collapse + 0.01);
    }
    // keep the page from running away: drop fully collapsed days past the top
    total = days.reduce((s, d) => s + dayHeight(d), 0);
    let y = PAGE_TOP;

    drawShelf();

    // threads first, under everything
    p.strokeWeight(0.6);
    for (const d of days) {
      if (d.collapse > 0) {
        // one thread for the whole day, from its row
        const h = dayHeight(d);
        if (y + h > PAGE_TOP - 4) {
          const s0 = spinePos(d.lines[0].spine);
          p.stroke(40, 20, 80, 30 * d.collapse);
          p.line(LEFT - 10, y + h / 2, s0.x + 1, s0.y + 2);
          p.noStroke();
          // the anchors that were kept, now as small squares
          let xx = LEFT;
          for (const ln of d.lines) {
            if (!ln.anchor) continue;
            const g = ln.segs[0];
            p.fill(g.hue, g.sat, g.bri, 90 * d.collapse);
            p.rect(xx, y + h / 2 - 2, 4, 4);
            xx += 7;
          }
          p.stroke(40, 20, 80, 22);
        }
        // fade out the lines of the day while it collapses
        if (d.collapse < 1) {
          let yy = y;
          p.noStroke();
          for (const ln of d.lines) {
            const lh = lineHeight(ln, d) * (1 - d.collapse);
            if (ln.anchor) drawLineFull(ln, LEFT, yy, 1, 100 * (1 - d.collapse));
            else drawLineFolded(ln, LEFT, yy, 1);
            yy += lh;
          }
        }
        y += h;
        continue;
      }
      for (const ln of d.lines) {
        const lh = lineHeight(ln, d);
        if (ln.fold > 0 && !ln.anchor && ln.spine >= 0) {
          const s = spinePos(ln.spine);
          p.stroke(ln.segs[0].hue, 30, 85, 34 * ln.fold);
          p.line(LEFT + 2, y + 2, s.x + 1, s.y + 2);
        }
        y += lh;
      }
    }

    // lines
    p.noStroke();
    y = PAGE_TOP;
    for (const d of days) {
      if (d.collapse > 0) { y += dayHeight(d); continue; }
      const n = d.lines.length;
      for (let i = 0; i < n; i++) {
        const ln = d.lines[i];
        const lh = lineHeight(ln, d);
        if (d === today && t < WRITE) {
          const k = p.constrain((t / WRITE) * n - i, 0, 1);
          if (k > 0) drawLineFull(ln, LEFT, y, k, 100);
        } else if (ln.anchor || ln.fold === 0) {
          drawLineFull(ln, LEFT, y, 1, 100);
        } else {
          drawLineFolded(ln, LEFT, y, ln.fold);
        }
        // a copy in flight
        if (ln.spine >= 0) {
          const s = spines[ln.spine];
          if (s.grow < 1) {
            const sp = spinePos(ln.spine);
            const u = s.grow;
            p.fill(ln.segs[0].hue, 20, 100, 60 * (1 - u));
            p.circle(p.lerp(LEFT + 20, sp.x, u), p.lerp(y + 2, sp.y, u), 3);
          }
        }
        y += lh;
      }
    }
  };
}