Interactive NFT by @andriibakulin
<code>
const ITEMS_COUNT = 48;
let gEntities = [];
let gSizeArea;
let gSizePoints, gSizeDots, gSizeLines, gBrakingLimit;
let gDistance, gDistanceSqr;
// MagicLogic
class Entity
{
constructor(index, count, xOffset, yOffset, direction)
{
const seq01 = index / count;
this.xOffset = xOffset;
this.yOffset = yOffset;
this.direction = direction;
this.color = seq01 * 255;
this.offset = seq01 * Math.PI;
this.angel = seq01 * Math.PI * 2;
this.point = new Point();
}
next(dt)
{
this.color += dt * 100;
this.offset += dt;
this.angel -= dt/3;
}
render()
{
const radius = gSizeArea * 0.15;
const circleX = Math.sin(this.angel * this.direction) * radius;
const circleY = Math.cos(this.angel * this.direction) * radius;
const colorV = normalizeColorComp(this.color);
const etalonX = gSizeArea * this.xOffset + circleX;
const etalonY = gSizeArea * this.yOffset + circleY;
let offsetX = circleX * 0.2;
let offsetY = circleY * 0.2;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
if (mouseIsPressed)
{
const dx = etalonX - mouseX;
const dy = etalonY - mouseY;
const distSqr = dx**2 + dy**2;
if (distSqr < gDistanceSqr)
{
const dist = Math.sqrt(distSqr);
const power = 1 - dist / gDistance;
const newX = mouseX + dx/dist * gDistance;
const newY = mouseY + dy/dist * gDistance;
offsetX = newX - etalonX;
offsetY = newY - etalonY;
offsetX *= power;
offsetY *= power;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
resetMatrix();
translate(etalonX, etalonY);
this.point.move(offsetX, offsetY, gBrakingLimit);
if (!(this.point.x == 0 && this.point.y == 0))
{
const brightness = (this.point.x**2 + this.point.y**2) / gDistanceSqr;
const innerOffset = Math.abs(Math.sin(this.offset)) * 1.5 - 0.125;
strokeWeight(gSizeLines);
stroke(colorV, 255, 15+240*brightness);
line(this.point.x, this.point.y, 0, 0);
stroke(colorV, 255, 255);
strokeWeight(gSizePoints * innerOffset);
point(this.point.x*0.1, this.point.y*0.1);
strokeWeight(gSizeDots);
point(this.point.x*innerOffset, this.point.y*innerOffset);
}
strokeWeight(gSizePoints);
stroke(colorV, 255, 255);
point(this.point.x, this.point.y);
}
}
class Point
{
constructor(x=null, y=null)
{
this.x = x;
this.y = y;
}
move(x, y, delta)
{
if (this.x === null || this.y === null)
{
this.x = x;
this.y = y;
return;
}
const dx = x - this.x;
const dy = y - this.y;
this.x = Math.abs(dx) <= delta ? x
: (dx < 0 ? this.x - delta : this.x + delta);
this.y = Math.abs(dy) <= delta ? y
: (dy < 0 ? this.y - delta : this.y + delta);
}
}
// Lifecycle
function setup()
{
updateConsts();
createCanvas(gSizeArea, gSizeArea);
pixelDensity(1);
frameRate(60);
colorMode(HSB, 255);
angleMode(DEGREES);
background(0);
for (let index=0; index<ITEMS_COUNT; index++)
{
gEntities.push(new Entity(index, ITEMS_COUNT, 0.28, 0.5, +1));
gEntities.push(new Entity(index, ITEMS_COUNT, 0.72, 0.5, -1));
}
}
function windowResized()
{
updateConsts();
resizeCanvas(gSizeArea, gSizeArea);
}
function updateConsts()
{
gSizeArea = getAreaSize();
gDistance = gSizeArea / 1.5;
gDistanceSqr = gDistance ** 2;
gSizePoints = clamp(gSizeArea / 66, 5, 12);
gSizeDots = clamp(gSizePoints / 2, 3, 6);
gSizeLines = clamp(gSizePoints / 4, 2, 3);
gBrakingLimit = gSizeArea / 66;
}
function draw()
{
const dt = deltaTime/1000;
background(0);
for (const idx in gEntities)
{
const ent = gEntities[idx];
ent.render();
ent.next(dt);
}
}
// Helpers
function normalizeColorComp(v)
{
v %= 256;
if (v < 0) v+= 256;
return v;
}
function getAreaSize()
{
return Math.min(window.innerWidth, window.innerHeight);
}
function clamp(value, min, max)
{
if (value < min) return min;
if (value > max) return max;
return value;
}
</code>