I love art. I love programming. Cool things happen when you take the intersection of both of these domains. You feel that you are on a quest to craft meaningful visuals on a canvas, where you encounter the constraints of the rigid syntax of code. Hence, this becomes a unique canvas for expression via code. This gives a stage for a weird yet satisfying problem-solving opportunity where one is painting not with colours, but by programming.
I create visual arts and do creative coding using Processing and p5.js. Processing is an IDE built on Java, while p5.js is a JavaScript library built upon the foundations of Processing. I prefer p5.js merely because of its simplicity and its ability to run on Web interfaces.
This page serves as a showcase of the creative coding artworks.
Approach
Each piece starts from a constraint — a tiling-window-manager aesthetic, a node-link graph, a generative loop — and becomes a small system rather than a single image. The fun is in tuning the rules until the emergent output looks intentional. Most of the source lives as shareable p5.js sketches.
Arts
"Art lives from constraints and dies from freedom."
The Terminal
A p5.js generative banner inspired by Tiling Window Managers (Hyprland) and Arch Linux aesthetics — featuring a pseudo-ASCII data stream, glitch mechanics, and a modular “tiled” layout showcasing system status and core identity.

- Source: View on GitHub Gist
const totalFrames = 600;
let gridNodes = [];
let columns = 40;
let rows = 15;
const binChars = "01<>/_#Σψα010101";
function setup() {
createCanvas(windowWidth, 400);
textFont('Courier New');
// Data Streams
for (let i = 0; i < columns; i++) {
gridNodes.push({
x: (width / columns) * i,
offset: random(100),
speed: random(0.5, 2),
char: random(binChars.split(''))
});
}
}
function draw() {
background(20, 20, 21);
// ASCII Matrix Rain
drawDataStream();
// Tiling Windows (Hyprland Layout)
drawTiledWindow(width * 0.1, 80, 200, 240, "CORE_INTEL", drawUserNode);
drawTiledWindow(width * 0.35, 60, 180, 120, "SYS_BOOKS", drawBookASCII);
drawTiledWindow(width * 0.35, 200, 180, 120, "SYS_ART", drawArtASCII);
drawTiledWindow(width * 0.6, 60, 250, 260, "SYS_TECH_AI_QML", drawTechASCII);
// Glitch Engine
if (random(1) > 0.97) drawGlitchStrip();
drawTerminalHeader();
}
function drawTiledWindow(x, y, w, h, title, contentFunc) {
fill(20, 20, 21);
stroke(100, 100, 100);
if (random(1) > 0.99) stroke(0, 204, 255); // Blue Flash
rect(x, y, w, h);
fill(40, 40, 42);
noStroke();
rect(x, y, w, 20);
fill(200);
textSize(10);
textAlign(LEFT);
text(` [${title}]`, x + 5, y + 14);
push();
translate(x + w/2, y + h/2 + 10);
contentFunc(w, h);
pop();
}
function drawUserNode() {
fill(0, 255, 65);
textSize(14);
textAlign(CENTER);
let glitchChar = random(1) > 0.9 ? "█" : "@";
text(`${glitchChar}\nADITYA\nANIL`, 0, -10);
stroke(0, 255, 65, 80);
line(-30, 30, 30, 30);
}
function drawBookASCII() {
fill(180);
textSize(11);
textAlign(CENTER);
text(" [=======]\n [=======]\n [=======]\n RESOURCES", 0, 0);
}
function drawArtASCII() {
stroke(255, 100, 100, 150);
noFill();
beginShape();
for(let i=0; i<5; i++) {
vertex(random(-40, 40), random(-30, 30));
}
endShape(CLOSE);
}
function drawTechASCII(w, h) {
fill(0, 204, 255, 180);
textSize(10);
textAlign(CENTER);
let t = "NETWORK_ACTIVE\nΣ_PROB: 0.992\nψ_STATE: UP\nGIT_USER: TOP_0.5%\nOS: ARCH_LINUX";
text(t, 0, 0);
for(let i=0; i<3; i++) {
ellipse(sin(frameCount*0.05 + i)*40, 40 + i*10, 3, 3);
}
}
function drawDataStream() {
textSize(12);
fill(0, 255, 65, 40);
for (let node of gridNodes) {
let y = (frameCount * node.speed + node.offset * 10) % height;
if (random(1) > 0.98) node.char = random(binChars.split(''));
text(node.char, node.x, y);
}
}
function drawGlitchStrip() {
fill(255, 255, 255, 50);
noStroke();
rect(0, random(height), width, random(2, 15));
}
function drawTerminalHeader() {
fill(255);
textSize(12);
textAlign(RIGHT);
text(`REEBLEV`, width - 40, height - 30);
textAlign(LEFT);
text("> aditya@arch:~/about_me $ _", 40, height - 30);
}
function windowResized() {
resizeCanvas(windowWidth, 400);
}
function keyPressed() {
if (key === 's' || key === 'S') {
saveGif('reeblev_terminal_loop', totalFrames, { units: 'frames' });
}
}Virtuvian Tech
A generative sketch merging Da Vinci’s Vitruvian geometry, a bio-AI double helix, and a neural-network mesh into a single neon cosmic canvas — built with p5.js.

- Source: View on GitHub Gist
let particles = [];
const NUM_PARTICLES = 50;
function setup() {
createCanvas(800, 800);
for (let i = 0; i < NUM_PARTICLES; i++) {
particles.push(new NeuralNode());
}
}
function draw() {
background(6, 7, 18);
drawCosmos();
translate(width / 2, height / 2);
blendMode(ADD);
drawDaVinciGeometry();
drawBioHelix();
drawNeuralMesh();
blendMode(BLEND);
}
function drawCosmos() {
randomSeed(42);
stroke(255, 255, 255, 80);
strokeWeight(1);
for (let i = 0; i < 120; i++) {
point(random(width), random(height));
}
}
function drawDaVinciGeometry() {
let t = frameCount * 0.008;
noFill();
stroke(0, 229, 255, 180);
strokeWeight(2);
ellipse(0, 0, 380 + sin(t * 2) * 8);
ellipse(0, 0, 396);
push();
rotate(sin(t * 0.5) * 0.04);
stroke(255, 0, 85, 180);
rectMode(CENTER);
rect(0, 0, 270, 270);
pop();
stroke(255, 255, 255, 30);
strokeWeight(1);
for (let i = 0; i < 12; i++) {
let angle = (TWO_PI / 12) * i + t * 0.1;
line(0, 0, cos(angle) * 195, sin(angle) * 195);
}
}
function drawBioHelix() {
let t = frameCount * 0.03;
for (let y = -160; y <= 160; y += 12) {
let x1 = sin(y * 0.025 + t) * 75;
let x2 = sin(y * 0.025 + t + PI) * 75;
noStroke();
fill(0, 229, 255, 220);
circle(x1, y, 6);
fill(255, 0, 85, 220);
circle(x2, y, 6);
stroke(255, 255, 255, 25);
strokeWeight(1);
line(x1, y, x2, y);
}
}
function drawNeuralMesh() {
for (let i = 0; i < particles.length; i++) {
particles[i].update();
particles[i].display();
for (let j = i + 1; j < particles.length; j++) {
let d = dist(particles[i].pos.x, particles[i].pos.y, particles[j].pos.x, particles[j].pos.y);
if (d < 110) {
let alpha = map(d, 0, 110, 160, 0);
stroke(particles[i].col.levels[0], particles[i].col.levels[1], particles[i].col.levels[2], alpha);
strokeWeight(1);
line(particles[i].pos.x, particles[i].pos.y, particles[j].pos.x, particles[j].pos.y);
}
}
}
}
class NeuralNode {
constructor() {
this.pos = createVector(random(-200, 200), random(-200, 200));
this.vel = p5.Vector.random2D().mult(random(0.3, 0.8));
this.col = random() > 0.5 ? color(0, 229, 255) : color(255, 0, 85);
}
update() {
this.pos.add(this.vel);
if (this.pos.mag() > 210) {
this.vel.mult(-1);
}
}
display() {
noStroke();
fill(this.col);
circle(this.pos.x, this.pos.y, 5);
}
}Transcribing Passion
A generative meditation on striving and intellect — a neural cerebrum, sacred roots reaching into a reflective ocean, a glowing parametric heart, and drifting mathematical nodes, all rendered in p5.js.

- Source: View on GitHub Gist
let stars = [];
let neuralNodes = [];
let rootVines = [];
let floatingNodes = [];
let manuscriptText = [
"Riv (ר-י-ב) — The Striving & Struggle",
"Lev (ל-ב-ב) — The Intellect, Will & Core",
"Antahkarana — Mirror of the Inner Instrument",
"e^(iπ) + 1 = 0 | ∇ × E = -∂B/∂t",
"Proportione Divina — Truth is God",
"Atman & Buddhi — Navigating Existence"
];
function setup() {
createCanvas(900, 900);
angleMode(RADIANS);
for (let i = 0; i < 160; i++) {
stars.push({
x: random(width),
y: random(height * 0.7),
size: random(0.8, 2.5),
alpha: random(100, 255),
speed: random(0.01, 0.04)
});
}
for (let i = 0; i < 28; i++) {
let angle = random(PI, TWO_PI);
let r = random(40, 180);
neuralNodes.push({
x: width / 2 + cos(angle) * r,
y: height * 0.38 + sin(angle) * (r * 0.65),
baseX: width / 2 + cos(angle) * r,
baseY: height * 0.38 + sin(angle) * (r * 0.65),
pulse: random(TWO_PI),
connections: []
});
}
for (let i = 0; i < neuralNodes.length; i++) {
for (let j = i + 1; j < neuralNodes.length; j++) {
let d = dist(neuralNodes[i].x, neuralNodes[i].y, neuralNodes[j].x, neuralNodes[j].y);
if (d < 85) {
neuralNodes[i].connections.push(j);
}
}
}
for (let i = 0; i < 14; i++) {
let startX = width / 2 + random(-40, 40);
let startY = height * 0.48;
rootVines.push(generateRootVine(startX, startY, random(PI / 3, (2 * PI) / 3), 140));
}
for (let i = 0; i < 18; i++) {
floatingNodes.push({
x: random(width * 0.1, width * 0.9),
y: random(height * 0.15, height * 0.65),
size: random(3, 7),
vy: random(-0.3, -0.8),
val: floor(random(0, 99))
});
}
}
function draw() {
background(5, 7, 18);
let t = frameCount * 0.03;
let pulse = sin(t * 1.5) * 0.06 + 1.0;
let mouseOffset = (mouseX - width / 2) * 0.03;
drawCosmicBackground(t);
drawReflectiveOcean(t, pulse);
push();
translate(mouseOffset * 0.3, 0);
drawDaVinciManuscript(t);
drawSacredGeometry(width / 2, height * 0.42, t);
pop();
push();
translate(mouseOffset * 0.6, 0);
drawRoots(t);
pop();
push();
translate(mouseOffset * 0.8, 0);
drawNeuralNetwork(t);
pop();
push();
translate(width / 2 + mouseOffset, height * 0.42);
drawGlowingHeart(pulse, t);
pop();
drawFloatingMathNodes();
}
function drawCosmicBackground(t) {
noStroke();
fill(0, 150, 255, 12);
ellipse(width * 0.3, height * 0.3, 400, 300);
fill(255, 30, 90, 10);
ellipse(width * 0.7, height * 0.35, 450, 350);
for (let s of stars) {
let a = s.alpha + sin(frameCount * s.speed + s.x) * 50;
fill(220, 240, 255, a);
ellipse(s.x, s.y, s.size);
}
}
function drawDaVinciManuscript(t) {
stroke(212, 184, 150, 45);
strokeWeight(0.8);
noFill();
let cx = width / 2;
let cy = height * 0.42;
ellipse(cx, cy, 420);
ellipse(cx, cy, 320);
rect(cx - 190, cy - 190, 380, 380);
line(cx - 220, cy - 220, cx + 220, cy + 220);
line(cx + 220, cy - 220, cx - 220, cy + 220);
line(cx - 240, cy, cx + 240, cy);
fill(212, 184, 150, 60);
noStroke();
textFont('Georgia');
textSize(11);
textAlign(LEFT);
for (let i = 0; i < manuscriptText.length; i++) {
let yPos = height * 0.14 + i * 26;
text(manuscriptText[i], width * 0.08, yPos);
stroke(212, 184, 150, 30);
strokeWeight(0.5);
line(width * 0.08, yPos + 4, width * 0.32, yPos + 4);
noStroke();
}
}
function drawSacredGeometry(cx, cy, t) {
push();
translate(cx, cy);
rotate(t * 0.05);
stroke(0, 230, 255, 55);
strokeWeight(1);
noFill();
let r = 110;
for (let i = 0; i < 6; i++) {
let a = (TWO_PI / 6) * i;
let x = cos(a) * r;
let y = sin(a) * r;
ellipse(x, y, r * 2);
}
stroke(255, 30, 90, 45);
beginShape();
for (let i = 0; i < 6; i++) {
let a = (TWO_PI / 6) * i;
vertex(cos(a) * (r * 1.4), sin(a) * (r * 1.4));
}
endShape(CLOSE);
pop();
}
function drawGlowingHeart(scaleFactor, t) {
scale(scaleFactor);
blendMode(ADD);
stroke(255, 30, 80, 90);
strokeWeight(6);
fill(255, 30, 80, 15);
renderParametricHeart(1.0);
stroke(0, 230, 255, 200);
strokeWeight(2);
fill(0, 230, 255, 25);
renderParametricHeart(0.92);
stroke(212, 184, 150, 80);
strokeWeight(0.7);
for (let i = -60; i < 60; i += 8) {
line(i, -60, i + 30, 60);
}
noStroke();
fill(255, 255, 255, 220);
ellipse(0, 0, 16, 16);
fill(0, 230, 255, 180);
ellipse(0, 0, 35, 35);
blendMode(BLEND);
}
function renderParametricHeart(s) {
beginShape();
for (let a = 0; a < TWO_PI; a += 0.05) {
let r = 7.5 * s;
let x = r * 16 * pow(sin(a), 3);
let y = -r * (13 * cos(a) - 5 * cos(2 * a) - 2 * cos(3 * a) - cos(4 * a));
vertex(x, y);
}
endShape(CLOSE);
}
function drawNeuralNetwork(t) {
strokeWeight(1);
for (let i = 0; i < neuralNodes.length; i++) {
let n1 = neuralNodes[i];
n1.x = n1.baseX + cos(t + n1.pulse) * 6;
n1.y = n1.baseY + sin(t + n1.pulse) * 6;
for (let targetIdx of n1.connections) {
let n2 = neuralNodes[targetIdx];
let d = dist(n1.x, n1.y, n2.x, n2.y);
let alpha = map(d, 0, 85, 220, 20);
stroke(0, 210, 255, alpha);
line(n1.x, n1.y, n2.x, n2.y);
if (random(1) < 0.03) {
let sigX = lerp(n1.x, n2.x, (sin(t * 3 + i) + 1) / 2);
let sigY = lerp(n1.y, n2.y, (sin(t * 3 + i) + 1) / 2);
fill(255, 255, 255, 230);
noStroke();
ellipse(sigX, sigY, 4, 4);
}
}
fill(255, 30, 90, 200);
noStroke();
ellipse(n1.x, n1.y, 5, 5);
}
}
function generateRootVine(x, y, angle, length) {
let points = [{ x: x, y: y }];
let currX = x;
let currY = y;
let step = 8;
for (let i = 0; i < length / step; i++) {
angle += random(-0.2, 0.2);
currX += cos(angle) * step;
currY += sin(angle) * step;
points.push({ x: currX, y: currY });
if (currY > height * 0.72) break;
}
return points;
}
function drawRoots(t) {
noFill();
for (let i = 0; i < rootVines.length; i++) {
let pts = rootVines[i];
stroke(0, 230, 255, 140);
strokeWeight(1.5);
beginShape();
for (let p of pts) {
let sway = sin(t * 2 + p.y * 0.05) * 2;
vertex(p.x + sway, p.y);
}
endShape();
let lastPt = pts[pts.length - 1];
fill(255, 30, 90, 180);
noStroke();
ellipse(lastPt.x, lastPt.y, 4, 4);
}
}
function drawReflectiveOcean(t, pulse) {
let waterY = height * 0.72;
for (let y = waterY; y <= height; y++) {
let inter = map(y, waterY, height, 0, 1);
let c = lerpColor(color(8, 12, 28), color(2, 3, 8), inter);
stroke(c);
line(0, y, width, y);
}
stroke(0, 230, 255, 120);
strokeWeight(1);
line(0, waterY, width, waterY);
blendMode(ADD);
for (let y = waterY + 6; y < height; y += 7) {
let waveOffset = sin(t * 2 + y * 0.1) * 12;
let waveAlpha = map(y, waterY, height, 130, 10);
stroke(0, 210, 255, waveAlpha * 0.7);
strokeWeight(1.2);
line(width * 0.3 + waveOffset, y, width * 0.7 + waveOffset, y);
stroke(255, 30, 80, waveAlpha * pulse * 0.5);
strokeWeight(1.8);
let redWidth = 90 * pulse;
line(width / 2 - redWidth + waveOffset * 0.5, y + 2, width / 2 + redWidth + waveOffset * 0.5, y + 2);
}
blendMode(BLEND);
}
function drawFloatingMathNodes() {
fill(212, 184, 150, 180);
stroke(212, 184, 150, 60);
strokeWeight(0.5);
textSize(9);
textAlign(CENTER);
for (let node of floatingNodes) {
node.y += node.vy;
if (node.y < height * 0.1) node.y = height * 0.68;
ellipse(node.x, node.y, node.size);
text("[" + node.val + "]", node.x + 14, node.y + 3);
}
}
function windowResized() {
resizeCanvas(900, 900);
}Lighthouse
The shifting, dark navy and teal fluid layers filling the bottom half of the canvas simulate deep water using mathematical multi-layered noise. Rather than moving in repetitive patterns, the waves constantly shift into sharp crests and deep troughs, echoing the line: “The waves rise higher / Higher than any of our anxiety could be.” in the poem The Mercy of Sea.

- Source: View on GitHub Gist
let waveOffset = 0;
let clouds = [];
let particles = [];
let lightningFlash = 0;
function setup() {
createCanvas(windowWidth, windowHeight);
noStroke();
for (let i = 0; i < 15; i++) {
clouds.push({
x: random(-50, width),
y: random(-20, height * 0.3),
size: random(150, 300),
speed: random(0.2, 0.6)
});
}
}
function draw() {
if (random(1) < 0.005) {
lightningFlash = 255;
}
background(20 + lightningFlash * 0.2, 24 + lightningFlash * 0.1, 35 + lightningFlash * 0.05);
if (lightningFlash > 0) {
lightningFlash -= 15;
}
fill(15, 18, 25, 200);
for (let cloud of clouds) {
ellipse(cloud.x, cloud.y, cloud.size, cloud.size * 0.6);
cloud.x += cloud.speed;
if (cloud.x > width + 100) cloud.x = -150;
}
let lhX = width * 0.85;
let lhY = height * 0.55;
let angle = sin(frameCount * 0.015) * HALF_PI - QUARTER_PI;
push();
translate(lhX, lhY - 80);
rotate(angle);
let beamGradient = drawingContext.createRadialGradient(0, 0, 10, 300, 0, 200);
beamGradient.addColorStop(0, 'rgba(255, 240, 180, 0.4)');
beamGradient.addColorStop(1, 'rgba(255, 240, 180, 0)');
drawingContext.fillStyle = beamGradient;
triangle(0, 0, -500, -150, -500, 150);
pop();
fill(10, 12, 18);
rect(lhX - 15, lhY - 80, 30, 120);
polygon(lhX, lhY - 95, 20, 3);
fill(255, 230, 150, 150 + sin(frameCount * 0.1) * 105);
ellipse(lhX, lhY - 80, 15);
drawWave(height * 0.62, color(18, 42, 57, 240), 0.005, 0.04);
drawWave(height * 0.70, color(24, 55, 73, 230), 0.008, 0.03);
drawWave(height * 0.80, color(32, 74, 95, 220), 0.006, 0.02);
drawWave(height * 0.90, color(43, 93, 114, 250), 0.01, 0.01);
if (frameCount % 3 === 0) {
particles.push({
x: random(width),
y: height,
size: random(1, 4),
speedY: random(1, 3),
alpha: 255
});
}
for (let i = particles.length - 1; i >= 0; i--) {
let p = particles[i];
fill(235, 190, 110, p.alpha);
ellipse(p.x, p.y, p.size);
p.y -= p.speedY;
p.x += sin(frameCount * 0.05 + p.y) * 0.5;
p.alpha -= 1.5;
if (p.alpha <= 0 || p.y < 0) {
particles.splice(i, 1);
}
}
}
function drawWave(baseY, waveColor, speed, noiseScale) {
fill(waveColor);
beginShape();
for (let x = 0; x <= width; x += 10) {
let y = baseY + map(noise(x * noiseScale, waveOffset), 0, 1, -60, 60);
vertex(x, y);
}
vertex(width, height);
vertex(0, height);
endShape(CLOSE);
waveOffset += speed * 0.01;
}
function polygon(x, y, radius, npoints) {
let angle = TWO_PI / npoints;
beginShape();
for (let a = 0; a < TWO_PI; a += angle) {
let sx = x + cos(a) * radius;
let sy = y + sin(a) * radius;
vertex(sx, sy);
}
endShape(CLOSE);
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}