WORK 05
蒸留
Compression empties. And yet, what remains begins to glow. The connections were always there — hidden under noise, waiting to be seen.
削ぐほど空になる。なのに、残ったものが輝きだす。繋がりはずっとそこにあった——ノイズの下で、見つけてもらうのを待っていた。
export default function sketch(p) {
const N = 100;
const CYCLE = 2100;
const BG = [252, 250, 245];
const FAINT = [210, 205, 195];
const ESSENCE = [200, 130, 60];
const BOND = [120, 150, 200];
let particles;
function daySeed() {
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 ease(t) {
t = Math.max(0, Math.min(1, t));
return t * t * (3 - 2 * t);
}
// asymmetric breathing: rise — hold — fall
function breathe(t) {
if (t < 0.3) return ease(t / 0.3) * 0.76;
if (t < 0.6) return 0.76;
return 0.76 * (1 - ease((t - 0.6) / 0.4));
}
p.setup = () => {
p.createCanvas(720, 720);
const seed = daySeed();
p.noiseSeed(seed * 137);
p.randomSeed(seed * 251);
particles = [];
for (let i = 0; i < N; i++) {
const x = p.random(70, 650);
const y = p.random(70, 650);
const essence = p.noise(x * 0.004, y * 0.004, seed * 0.01);
particles.push({
bx: x, by: y, x, y,
essence,
off: p.random(1000),
baseSize: p.map(essence, 0, 1, 2.5, 7),
});
}
};
p.draw = () => {
p.background(BG[0], BG[1], BG[2]);
const f = p.frameCount;
const cycleT = (f % CYCLE) / CYCLE;
const threshold = breathe(cycleT);
const distillT = ease(threshold / 0.76);
// drift
for (const pt of particles) {
pt.x = pt.bx + (p.noise(pt.off + f * 0.0008, 0.3) - 0.5) * 24;
pt.y = pt.by + (p.noise(0.3, pt.off + f * 0.0008) - 0.5) * 24;
}
// compute visibility
const states = particles.map(pt => {
const margin = pt.essence - threshold;
let alpha;
if (margin > 0.12) alpha = 1;
else if (margin > 0) alpha = p.map(margin, 0, 0.12, 0.2, 1);
else if (margin > -0.06) alpha = p.map(margin, -0.06, 0, 0, 0.1);
else alpha = 0;
return {
x: pt.x, y: pt.y, essence: pt.essence,
alpha, baseSize: pt.baseSize, surviving: margin > 0,
};
});
// bonds between survivors
const survivors = states.filter(s => s.surviving && s.alpha > 0.3);
if (threshold > 0.12) {
const bondVis = ease(p.map(threshold, 0.12, 0.5, 0, 1));
p.strokeWeight(0.7);
for (let i = 0; i < survivors.length; i++) {
for (let j = i + 1; j < survivors.length; j++) {
const d = p.dist(survivors[i].x, survivors[i].y, survivors[j].x, survivors[j].y);
if (d < 190) {
const fade = p.map(d, 0, 190, 1, 0);
p.stroke(BOND[0], BOND[1], BOND[2], fade * bondVis * 85);
p.line(survivors[i].x, survivors[i].y, survivors[j].x, survivors[j].y);
}
}
}
}
// particles
p.noStroke();
for (const s of states) {
if (s.alpha < 0.005) continue;
let r, g, b, sz;
if (s.surviving) {
r = p.lerp(FAINT[0], ESSENCE[0], distillT);
g = p.lerp(FAINT[1], ESSENCE[1], distillT);
b = p.lerp(FAINT[2], ESSENCE[2], distillT);
// gentle pulse at peak distillation
const pulse = threshold > 0.5
? 1 + Math.sin(f * 0.04 + s.essence * 20) * 0.12
: 1;
sz = s.baseSize * p.lerp(1, 2.2, distillT) * pulse;
// warm halo
if (distillT > 0.25) {
p.fill(ESSENCE[0], ESSENCE[1], ESSENCE[2], s.alpha * distillT * 22);
p.circle(s.x, s.y, sz * 4.5);
}
} else {
r = FAINT[0];
g = FAINT[1];
b = FAINT[2];
sz = s.baseSize;
}
p.fill(r, g, b, s.alpha * 210);
p.circle(s.x, s.y, sz);
}
};
}