{"name":"dev_PARTY #012","description":"Interactive NFT by @andriibakulin\n\n<code>\nconst ITEMS_COUNT = 48;\n\nlet gEntities = [];\nlet gSizeArea;\nlet gSizePoints, gSizeDots, gSizeLines, gBrakingLimit;\nlet gDistance, gDistanceSqr;\n\n// MagicLogic\n\nclass Entity\n{\n    constructor(index, count, xOffset, yOffset, direction)\n    {\n        const seq01 = index / count;\n\n        this.xOffset = xOffset;\n        this.yOffset = yOffset;\n        this.direction = direction;\n\n        this.color  = seq01 * 255;\n        this.offset = seq01 * Math.PI;\n        this.angel  = seq01 * Math.PI * 2;\n\n        this.point = new Point();\n    }\n\n    next(dt)\n    {\n        this.color  += dt * 100;\n        this.offset += dt;\n        this.angel  -= dt/3;\n    }\n\n    render()\n    {\n        const radius = gSizeArea * 0.15;\n\n        const circleX = Math.sin(this.angel * this.direction) * radius;\n        const circleY = Math.cos(this.angel * this.direction) * radius;\n\n        const colorV = normalizeColorComp(this.color);\n\n        const etalonX = gSizeArea * this.xOffset + circleX;\n        const etalonY = gSizeArea * this.yOffset + circleY;\n\n        let offsetX = circleX * 0.2;\n        let offsetY = circleY * 0.2;\n\n        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n        if (mouseIsPressed)\n        {\n            const dx = etalonX - mouseX;\n            const dy = etalonY - mouseY;\n\n            const distSqr = dx**2 + dy**2;\n\n            if (distSqr < gDistanceSqr)\n            {\n                const dist = Math.sqrt(distSqr);\n                const power = 1 - dist / gDistance;\n\n                const newX = mouseX + dx/dist * gDistance;\n                const newY = mouseY + dy/dist * gDistance;\n\n                offsetX = newX - etalonX;\n                offsetY = newY - etalonY;\n\n                offsetX *= power;\n                offsetY *= power;\n            }\n        }\n\n        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n        resetMatrix();\n        translate(etalonX, etalonY);\n\n        this.point.move(offsetX, offsetY, gBrakingLimit);\n\n        if (!(this.point.x == 0 && this.point.y == 0))\n        {\n            const brightness = (this.point.x**2 + this.point.y**2) / gDistanceSqr;\n            const innerOffset = Math.abs(Math.sin(this.offset)) * 1.5 - 0.125;\n\n            strokeWeight(gSizeLines);\n            stroke(colorV, 255, 15+240*brightness);\n            line(this.point.x, this.point.y, 0, 0);\n\n            stroke(colorV, 255, 255);\n            strokeWeight(gSizePoints * innerOffset);\n            point(this.point.x*0.1, this.point.y*0.1);\n\n            strokeWeight(gSizeDots);\n            point(this.point.x*innerOffset, this.point.y*innerOffset);\n        }\n\n        strokeWeight(gSizePoints);\n        stroke(colorV, 255, 255);\n        point(this.point.x, this.point.y);\n    }\n}\n\nclass Point\n{\n    constructor(x=null, y=null)\n    {\n        this.x = x;\n        this.y = y;\n    }\n\n    move(x, y, delta)\n    {\n        if (this.x === null || this.y === null)\n        {\n            this.x = x;\n            this.y = y;\n            return;\n        }\n\n        const dx = x - this.x;\n        const dy = y - this.y;\n\n        this.x = Math.abs(dx) <= delta ? x\n            : (dx < 0 ? this.x - delta : this.x + delta);\n\n        this.y = Math.abs(dy) <= delta ? y\n            : (dy < 0 ? this.y - delta : this.y + delta);\n    }\n}\n\n// Lifecycle\n\nfunction setup()\n{\n    updateConsts();\n    createCanvas(gSizeArea, gSizeArea);\n\n    pixelDensity(1);\n    frameRate(60);\n\n    colorMode(HSB, 255);\n    angleMode(DEGREES);\n\n    background(0);\n\n    for (let index=0; index<ITEMS_COUNT; index++)\n    {\n        gEntities.push(new Entity(index, ITEMS_COUNT, 0.28, 0.5, +1));\n        gEntities.push(new Entity(index, ITEMS_COUNT, 0.72, 0.5, -1));\n    }\n}\n\nfunction windowResized()\n{\n    updateConsts();\n    resizeCanvas(gSizeArea, gSizeArea);\n}\n\nfunction updateConsts()\n{\n    gSizeArea     = getAreaSize();\n\n    gDistance     = gSizeArea / 1.5;\n    gDistanceSqr  = gDistance ** 2;\n\n    gSizePoints   = clamp(gSizeArea   / 66, 5, 12);\n    gSizeDots     = clamp(gSizePoints /  2, 3, 6);\n    gSizeLines    = clamp(gSizePoints /  4, 2, 3);\n\n    gBrakingLimit = gSizeArea / 66;\n}\n\nfunction draw()\n{\n    const dt = deltaTime/1000;\n\n    background(0);\n\n    for (const idx in gEntities)\n    {\n        const ent = gEntities[idx];\n        ent.render();\n        ent.next(dt);\n    }\n}\n\n// Helpers\n\nfunction normalizeColorComp(v)\n{\n    v %= 256;\n    if (v < 0) v+= 256;\n    return v;\n}\n\nfunction getAreaSize()\n{\n    return Math.min(window.innerWidth, window.innerHeight);\n}\n\nfunction clamp(value, min, max)\n{\n    if (value < min) return min;\n    if (value > max) return max;\n    return value;\n}\n</code>","tags":["codeart","interactive","procedural","generative","generativeart","realtime","p5js","p5func"],"symbol":"OBJKT","artifactUri":"ipfs://QmbdzgnDskYcHw9BCJ7RiPiXybXzHG62na2irURShL5EGt","displayUri":"ipfs://QmcoP3qd7RFj5w4ct94BrzU9qCD244tB1tMrX9uTidjjmv","thumbnailUri":"ipfs://QmNrhZHUaEqxhyLfqoq1mtHSipkWHeT31LNHb1QEbDHgnc","creators":["tz1Ys42frYhgxHxXtqoY42GiuRPZ9ykbP86Y"],"formats":[{"uri":"ipfs://QmbdzgnDskYcHw9BCJ7RiPiXybXzHG62na2irURShL5EGt","mimeType":"application/x-directory"}],"decimals":0,"isBooleanAmount":false,"shouldPreferSymbol":false}