0x73a6f785…9cabsent to0x265acf7f…22cd·#23,201,999·view on Etherscan
"};
let _seed;
let vehicles = [];
const vehicleTypes = ["train", "bus", "car"];
const morphDuration = 120;
let numTrains;
const trainLength = 100;
const trainHeight = 20;
let bgs; // Background settings object
let rAlpha; // Random alpha factor for backgrounds
let entropyType;
let bgTrait; // Sky trait
let st; // Spacetime settings object
let pBgo = 0, pMgo = 0, pFgo = 0; // Parallax offsets
const tsBg = 100, tsMg = 100, tsFg = 100; // Tile sizes
const pBgsp = 0.2, pMgsp = 0.5, pFgsp = 1.0; // Parallax speeds
let basesp; // Base speed for vehicles
const COLOR_CHANGE_COOLDOWN = 180;
const COLOR_LERP_DURATION = 60;
let lastColorChangeFrame = -COLOR_CHANGE_COOLDOWN;
let colorTransitionTarget = null;
let colorTransitionStartColors = {};
let colorTransitionProgress = 1;
const { PI, sin, cos, floor, round, abs, sqrt, max, min } = Math;
const TWO_PI = 2 * PI;
function map(value, start1, stop1, start2, stop2) {
return start2 + (stop2 - start2) * ((value - start1) / (stop1 - start1));
}
function lerp(start, stop, amt) {
return start + (stop - start) * amt;
}
function easeInOut(t) {
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
}
function rand() {
_seed ^= _seed << 13;
_seed ^= _seed >>> 17;
_seed ^= _seed << 5;
return (_seed >>> 0) / 4294967296;
}
function random(a, b) {
if (a === undefined) return rand();
if (b === undefined) { b = a; a = 0; }
return rand() * (b - a) + a;
}
const hl = {
randomElement: arr => arr[floor(rand() * arr.length)]
};
const PERLIN_YWRAPB = 4;
const PERLIN_YWRAP = 1 << PERLIN_YWRAPB;
const PERLIN_SIZE = 4095;
const PERLIN_OCTAVES = 4;
const PERLIN_AMP_FALLOFF = 0.5;
let perlin_noise_array;
function seedNoise() {
perlin_noise_array = new Array(PERLIN_SIZE + 1);
for (let i_pn = 0; i_pn < PERLIN_SIZE + 1; i_pn++) {
perlin_noise_array[i_pn] = floor(random(256));
}
}
const scaled_cosine = (i_sc) => 0.5 * (1.0 - cos(i_sc * PI));
function noise(x_noise, y_noise = 0) {
if (perlin_noise_array === undefined) {
seedNoise();
}
if (x_noise < 0) x_noise = -x_noise;
if (y_noise < 0) y_noise = -y_noise;
let xi_noise = floor(x_noise);
let yi_noise = floor(y_noise);
let xf_noise = x_noise - xi_noise;
let yf_noise = y_noise - yi_noise;
let rxf_noise, ryf_noise;
let r_noise = 0;
let ampl_noise = 0.5;
let n1_noise, n2_noise;
for (let o_noise = 0; o_noise < PERLIN_OCTAVES; o_noise++) {
let of_noise = xi_noise + (yi_noise << PERLIN_YWRAPB);
rxf_noise = scaled_cosine(xf_noise);
ryf_noise = scaled_cosine(yf_noise);
n1_noise = perlin_noise_array[of_noise & PERLIN_SIZE] / 255.0;
n1_noise += rxf_noise * ((perlin_noise_array[(of_noise + 1) & PERLIN_SIZE] / 255.0) - n1_noise);
n2_noise = perlin_noise_array[(of_noise + PERLIN_YWRAP) & PERLIN_SIZE] / 255.0;
n2_noise += rxf_noise * ((perlin_noise_array[(of_noise + PERLIN_YWRAP + 1) & PERLIN_SIZE] / 255.0) - n2_noise);
n1_noise += ryf_noise * (n2_noise - n1_noise);
r_noise += n1_noise * ampl_noise;
ampl_noise *= PERLIN_AMP_FALLOFF;
xi_noise <<= 1; xf_noise *= 2;
yi_noise <<= 1; yf_noise *= 2;
if (xf_noise >= 1.0) { xi_noise++; xf_noise--; }
if (yf_noise >= 1.0) { yi_noise++; yf_noise--; }
}
return max(0, min(1, r_noise));
}
function parseColor(c) {
if (typeof c === 'string' && c.startsWith('#')) {
let bigint = parseInt(c.slice(1), 16);
return { r: (bigint >> 16) & 255, g: (bigint >> 8) & 255, b: bigint & 255, a: 1 };
} else if (Array.isArray(c)) {
let [h, s, b_val, a_val] = c;
h %= 360; s /= 100; b_val /= 100; a_val = (a_val === undefined) ? 1 : a_val;
let i = floor(h / 60), f = h / 60 - i, p = b_val * (1 - s), q = b_val * (1 - f * s), t = b_val * (1 - (1 - f) * s);
let r_rgb, g_rgb, b_rgb_val;
switch (i % 6) {
case 0: r_rgb = b_val; g_rgb = t; b_rgb_val = p; break;
case 1: r_rgb = q; g_rgb = b_val; b_rgb_val = p; break;
case 2: r_rgb = p; g_rgb = b_val; b_rgb_val = t; break;
case 3: r_rgb = p; g_rgb = q; b_rgb_val = b_val; break;
case 4: r_rgb = t; g_rgb = p; b_rgb_val = b_val; break;
case 5: r_rgb = b_val; g_rgb = p; b_rgb_val = q; break;
default: r_rgb = 0; g_rgb = 0; b_rgb_val = 0;
}
return { r: r_rgb * 255, g: g_rgb * 255, b: b_rgb_val * 255, a: a_val };
} else if (typeof c === 'object' && c !== null && 'r' in c && 'g' in c && 'b' in c) {
return { ...c, a: c.a === undefined ? 1 : c.a };
}
return { r: 0, g: 0, b: 0, a: 1 };
}
function toCanvasRGBA(rgba) {
return `rgba(${round(rgba.r)},${round(rgba.g)},${round(rgba.b)},${rgba.a})`;
}
function lerpColor(color1, color2, amt) {
let c1 = parseColor(color1); let c2 = parseColor(color2);
return {
r: lerp(c1.r, c2.r, amt), g: lerp(c1.g, c2.g, amt),
b: lerp(c1.b, c2.b, amt), a: lerp(c1.a, c2.a, amt)
};
}
function color(...args) {
if (args.length === 1) return parseColor(args[0]);
if (args.length >= 3) return parseColor([args[0], args[1], args[2], args[3]]);
return parseColor(null);
}
class MorphingVehicle {
constructor(x, y, currentBasesp) {
this.x = x; this.y = y;
this.baseLength = trainLength; this.baseHeight = trainHeight;
this.sp = random(currentBasesp, currentBasesp * 2);
this.color = color(hl.randomElement(bgs.trc));
this.baseY = y;
this.fadeInStart = frameCount;
this.fadeInDuration = random(30, 90);
this.fadeOutStart = frameCount + random(300, 900);
this.fadeOutDuration = random(60, 120);
this.isMorphing = false;
this.holdDuration = floor(random(60, 240));
this.holdCounter = 0;
this.currentType = hl.randomElement(vehicleTypes);
this.nextType = this.getNextType();
this.morphProgress = 0;
this.vertices = this.getShapeVertices(this.currentType);
this.targetVertices = this.getShapeVertices(this.nextType);
this.fixVertexCount();
}
update() {
this.x += this.sp;
if (entropyType === "Wabisabi") {
this.y = this.baseY + map(noise(this.x * 0.005, frameCount * 0.003), 0, 1, -10, 10);
this.x += map(noise(this.x * 0.007, frameCount * 0.003), 0, 1, -2, 2);
} else if (entropyType === "Freestyle") {
this.y = this.baseY + map(noise(this.x * 0.005, frameCount * 0.005), 0, 1, -50, 50);
this.x += map(noise(this.x * 0.003, frameCount * 0.005), 0, 1, -3, 3);
}
if (entropyType === "Balance") {
this.y = this.baseY + sin(frameCount * 0.05 + this.x * 0.01) * 20;
}
if (this.x > width + this.baseLength || frameCount >= this.fadeOutStart + this.fadeOutDuration) {
this.reset(); return;
}
if (!this.isMorphing) {
this.holdCounter++;
if (this.holdCounter >= this.holdDuration) { this.isMorphing = true; this.holdCounter = 0; }
} else {
this.morphProgress += 1 / morphDuration;
if (this.morphProgress >= 1) {
this.currentType = this.nextType; this.vertices = this.targetVertices;
this.nextType = this.getNextType();
this.targetVertices = this.getShapeVertices(this.nextType);
this.fixVertexCount();
this.morphProgress = 0; this.isMorphing = false;
this.holdDuration = floor(random(60, 240));
}
}
}
display() {
let alpha;
if (frameCount < this.fadeInStart + this.fadeInDuration) alpha = map(frameCount, this.fadeInStart, this.fadeInStart + this.fadeInDuration, 0, 1);
else if (frameCount < this.fadeOutStart) alpha = 1;
else if (frameCount < this.fadeOutStart + this.fadeOutDuration) alpha = map(frameCount, this.fadeOutStart, this.fadeOutStart + this.fadeOutDuration, 1, 0);
else alpha = 0;
if (alpha <= 0) return;
let morphedVertices = this.vertices.map((v, i) => ({
x: lerp(v.x, this.targetVertices[i].x, easeInOut(this.morphProgress)),
y: lerp(v.y, this.targetVertices[i].y, easeInOut(this.morphProgress))
}));
let finalColor = { ...this.color, a: this.color.a * alpha };
ctx.strokeStyle = toCanvasRGBA(finalColor);
ctx.lineWidth = 3; ctx.fillStyle = 'transparent';
ctx.beginPath();
if (morphedVertices.length > 0) {
ctx.moveTo(this.x + morphedVertices[0].x, this.y + morphedVertices[0].y);
for (let i = 1; i < morphedVertices.length; i++) {
ctx.lineTo(this.x + morphedVertices[i].x, this.y + morphedVertices[i].y);
}
}
ctx.closePath(); ctx.stroke();
}
reset() {
this.x = random(-this.baseLength, width);
this.y = random(height * 0.05, height * 0.95);
this.baseY = this.y;
this.sp = random(basesp, basesp * 2);
this.color = color(hl.randomElement(bgs.trc));
this.fadeInStart = frameCount; this.fadeInDuration = random(30, 90);
this.fadeOutStart = frameCount + random(300, 900); this.fadeOutDuration = random(60, 120);
this.currentType = hl.randomElement(vehicleTypes);
this.nextType = this.getNextType(); this.morphProgress = 0; this.isMorphing = false;
this.holdCounter = 0; this.holdDuration = floor(random(60, 240));
this.vertices = this.getShapeVertices(this.currentType);
this.targetVertices = this.getShapeVertices(this.nextType);
this.fixVertexCount();
}
getNextType() {
return vehicleTypes[(vehicleTypes.indexOf(this.currentType) + 1) % vehicleTypes.length];
}
getShapeVertices(type) {
let L = this.baseLength, H = this.baseHeight;
if (type === "train") return [{x:-L,y:-H*.5},{x:-L*.3,y:-H*.5},{x:-L*.15,y:-H*.5},{x:L*.15,y:-H*.5},{x:L*.2,y:-H*.5},{x:L*.25,y:-H*.2},{x:L*.35,y:0},{x:L*.38,y:H*.2},{x:L*.3,y:H*.5},{x:-L*.15,y:H*.5},{x:-L*.3,y:H*.5},{x:-L,y:H*.5}];
if (type === "bus") return [{x:-L*.5,y:-H*.5},{x:-L*.3,y:-H*.5},{x:-L*.15,y:-H*.5},{x:0,y:-H*.5},{x:L*.15,y:-H*.5},{x:L*.25,y:-H*.5},{x:L*.3,y:-H*.3},{x:L*.32,y:H*.3},{x:L*.3,y:H*.5},{x:-L*.15,y:H*.5},{x:-L*.3,y:H*.5},{x:-L*.5,y:H*.5}];
if (type === "car") return [{x:-L*.3,y:-H*.35},{x:-L*.1,y:-H*.5},{x:0,y:-H*.5},{x:L*.1,y:-H*.2},{x:L*.2,y:-H*.1},{x:L*.27,y:H*.1},{x:L*.25,y:H*.3},{x:L*.1,y:H*.3},{x:-L*.1,y:H*.3},{x:-L*.3,y:H*.3},{x:-L*.35,y:H*.1},{x:-L*.3,y:-H*.1}];
return [];
}
fixVertexCount() {
let maxLen = max(this.vertices.length, this.targetVertices.length);
while (this.vertices.length < maxLen) this.vertices.push({ ...this.vertices[this.vertices.length - 1] });
while (this.targetVertices.length < maxLen) this.targetVertices.push({ ...this.targetVertices[this.targetVertices.length - 1] });
}
}
let effectStates = {};
function initializeArtwork() {
// Read data injected by the smart contract
const traits = window.GOM_TRAITS;
if (!traits || !traits.hash || !traits.rand_indices) {
document.body.innerHTML = `<pre style="color:red;">Error: Trait data not found.</pre>`;
return;
}
const rand_indices = traits.rand_indices.split(',').map(n => parseInt(n));
_seed = parseInt(traits.hash.slice(2, 18), 16);
perlin_noise_array = undefined;
// --- START: Trait Generation using On-Chain Indices ---
const numTrainsOptions = [1,1,3,3,3,3,3,11,11,11,11,11,11,11,11,11,21,21,21,33];
numTrains = numTrainsOptions[rand_indices[0]];
const rAlphaOptions = [37, 38, 39];
rAlpha = rAlphaOptions[rand_indices[1]] / 1000.0;
const bgsOptions = [
{ sn: "Kabuki", trc: ["#fa4e28","#A10D2E","#E63946","#F4A261","#f0d507"], bgt_raw: [240,77,28], bgb_raw: [240,70,39], trailColor: "#B51A15"},
{ sn: "Diamond Sky", trc: ["#a7fae7","#fca9e3","#b97af5","#f2ffed","#ff6b84"], bgt_raw: [199,100.0,98.04], bgb_raw: [197,87,98], trailColor: "#fcfcfc"},
{ sn: "Red Silk", trc: ["#ff6b84","#ff1c1c","#ffc505","#e4b7e8","#f2e8b6"], bgt_raw: [358,94.44,35], bgb_raw: [355,70.0,25], trailColor: "#fcd4d4"},
{ sn: "Green Tea", trc: ["#a34ea3","#e4b7e8","#aae6e6","#9e9ef7","#f5c4c4"], bgt_raw: [111,50,60], bgb_raw: [111,58,73], trailColor: "#EA1DC3"},
{ sn: "Sand Wave", trc: ["#dbf1ff","#F2EBEF","#00acd0","#6270a3","#026bc7"], bgt_raw: [50,50,65], bgb_raw: [50,50,77], trailColor: "#afe3ed"},
{ sn: "Whispers of History", trc: ["#f51b05","#a60228","#343deb","#f5f0f0","#202691"], bgt_raw: [220,15,5], bgb_raw: [355,96,18], trailColor: "#850303"},
{ sn: "Plum Blossom", trc: ["#b50719","#d1ba02","#E5D0A8","#607D3B","#B18969"], bgt_raw: [290,65,39], bgb_raw: [284,63,55], trailColor: "#f7d7f7"},
{ sn: "Typhoon", trc: ["#66a3d4","#333f4d","#5ebda4","#A3A3A3","#414B56"], bgt_raw: [119,15,92], bgb_raw: [192,35,95], trailColor: "#65c1f7"},
{ sn: "Maple Leaves", trc: ["#D81159","#8B0000","#F4A261","#264653","#A53860"], bgt_raw: [38,35,92.16], bgb_raw: [32,27,92], trailColor: "#fa5050"}
];
bgs = bgsOptions[rand_indices[2]];
bgs.bgt = [...bgs.bgt_raw, rAlpha];
bgs.bgb = [...bgs.bgb_raw, rAlpha];
const stOptions = [
{ spt: "Markets", _a:bgs.bgb, _b:bgs.trailColor, _c:bgs.bgb, wa:0, uta:!1, wb:3, utb:!0, wc:0, utc:!1 },
{ spt: "Streets Imaginaire",_a:bgs.bgb, _b:bgs.trailColor, _c:bgs.trailColor, wa:35, uta:!1, wb:3, utb:!0, wc:15, utc:!0 },
{ spt: "XXX", _a:bgs.bgb, _b:bgs.bgb, _c:bgs.trailColor, wa:10, uta:!1, wb:5, utb:!1, wc:1, utc:!0 },
{ spt: "Drift", _a:bgs.bgb, _b:bgs.bgb, _c:bgs.trailColor, wa:0, uta:!1, wb:25, utb:!1, wc:3, utc:!0 },
{ spt: "Portals", _a:bgs.trailColor, _b:bgs.trailColor, _c:bgs.bgb, wa:3, uta:!0, wb:2, utb:!0, wc:10, utc:!1 },
{ spt: "Gang", _a:bgs.trailColor, _b:bgs.trailColor, _c:bgs.trailColor, wa:3, uta:!0, wb:1, utb:!0, wc:1, utc:!0 },
{ spt: "Dust", _a:bgs.trailColor, _b:bgs.trailColor, _c:bgs.trailColor, wa:1, uta:!0, wb:1, utb:!0, wc:1.5,utc:!0 },
{ spt: "Rings", _a:bgs.trailColor, _b:bgs.trailColor, _c:bgs.trailColor, wa:1.5,uta:!0, wb:1, utb:!0, wc:0.5,utc:!0 },
{ spt: "Entangled", _a:bgs.trailColor, _b:bgs.bgb, _c:bgs.bgb, wa:3, uta:!0, wb:0, utb:!1, wc:0, utc:!1 }
];
st = stOptions[rand_indices[3]];
st.acla = Array.isArray(st._a) ? color(...st._a) : color(st._a);
st.aclb = Array.isArray(st._b) ? color(...st._b) : color(st._b);
st.aclc = Array.isArray(st._c) ? color(...st._c) : color(st._c);
st.weighta = st.wa; st.usesTrailColorA = st.uta;
st.weightb = st.wb; st.usesTrailColorB = st.utb;
st.weightc = st.wc; st.usesTrailColorC = st.utc;
entropyType = ({0.039:"Wabisabi", 0.038:"Balance", 0.037:"Freestyle"})[rAlpha.toFixed(3)];
const bgTraitOptions = ["Comet", "Sun", "Stars", "Moonbow", "Nebula"];
bgTrait = bgTraitOptions[rand_indices[4]];
basesp = sqrt(width * height) * 0.002;
vehicles = [];
for (let i = 0; i < numTrains; i++) {
vehicles.push(new MorphingVehicle(random(-trainLength, width), random(height * 0.05, height * 0.95), basesp));
}
effectStates = {};
if (bgTrait === "Sun") effectStates.sun = initSun();
if (bgTrait === "Comet") effectStates.comet = initComet();
if (bgTrait === "Stars") {
effectStates.stars = [];
for (let i = 0; i < 500; i++) {
effectStates.stars.push({ x: random(width), y: random(height), size: random(1, 3) });
}
effectStates.starRotationCenter = { x: random(width/3-width*.25, width/3+width*.25), y: random(height/3-height*.25, height/3+height*.25) };
effectStates.starRotation = random(TWO_PI);
effectStates.starRotationsp = random(0.0005, 0.002) * (random() < 0.5 ? 1 : -1);
}
if (bgTrait === "Moonbow") effectStates.moonbow = initMoonbow();
if (bgTrait === "Nebula") effectStates.nebula = initNebula();
drawBgTile.ballX = undefined;
if (perlin_noise_array === undefined) {
seedNoise();
}
}
function renderFrame() {
let bgTopColor = color(...bgs.bgt);
ctx.fillStyle = toCanvasRGBA(bgTopColor);
ctx.fillRect(0, 0, width, height);
if (colorTransitionProgress < 1 && colorTransitionTarget) {
colorTransitionProgress += 1 / COLOR_LERP_DURATION;
colorTransitionProgress = min(1, colorTransitionProgress);
let lerpAmt = easeInOut(colorTransitionProgress);
if(st.usesTrailColorA) st.acla = lerpColor(colorTransitionStartColors.a, colorTransitionTarget, lerpAmt);
if(st.usesTrailColorB) st.aclb = lerpColor(colorTransitionStartColors.b, colorTransitionTarget, lerpAmt);
if(st.usesTrailColorC) st.aclc = lerpColor(colorTransitionStartColors.c, colorTransitionTarget, lerpAmt);
}
if (random() < 0.05 && frameCount >= lastColorChangeFrame + COLOR_CHANGE_COOLDOWN && colorTransitionProgress >=1) {
let visVehicles = vehicles.filter(v => v.x >= 0 && v.x <= width);
if (visVehicles.length > 0) {
let visColors = visVehicles.map(v => v.color);
if (visColors.length > 0) {
colorTransitionTarget = hl.randomElement(visColors);
colorTransitionStartColors = { a: {...st.acla}, b: {...st.aclb}, c: {...st.aclc} };
colorTransitionProgress = 0;
lastColorChangeFrame = frameCount;
}
}
}
drawpLayers();
drawAdditionalBackground();
for (let vehicle of vehicles) { vehicle.update(); vehicle.display(); }
}
function drawpLayers() { ctx.save(); drawBackgroundLayer(); drawMidgroundLayer(); drawForegroundLayer(); ctx.restore(); }
function drawBackgroundLayer() {
pBgo += pBgsp; let oX = pBgo % tsBg;
for (let x = -tsBg; x < width + tsBg; x += tsBg) for (let y = 0; y < height; y += tsBg) {
ctx.save(); ctx.translate(x - oX, y); drawBgTile(); ctx.restore();
}
}
function drawMidgroundLayer() {
pMgo += pMgsp; let oX = pMgo % tsMg;
for (let x = -tsMg; x < width + tsMg; x += tsMg) for (let y = 0; y < height; y += tsMg) {
ctx.save(); ctx.translate(x - oX, y); drawMgTile(); ctx.restore();
}
}
function drawForegroundLayer() {
pFgo += pFgsp; let oX = pFgo % tsFg;
for (let x = -tsFg; x < width + tsFg; x += tsFg) for (let y = 0; y < height; y += tsFg) {
ctx.save(); ctx.translate(x - oX, y); drawFgTile(); ctx.restore();
}
}
function drawBgTile() {
let cola = st.acla; if (st.weighta <= 0) return;
ctx.strokeStyle = toCanvasRGBA(cola); ctx.lineWidth = st.weighta; ctx.fillStyle = 'transparent'; ctx.beginPath();
const spt = st.spt;
if (spt === "Markets" && typeof noise === 'function') {
ctx.moveTo(0, map(noise(0, frameCount * 0.01), 0, 1, 0, tsBg));
for (let x = 5; x <= tsBg; x += 5) ctx.lineTo(x, map(noise(x * 0.05, frameCount * 0.01), 0, 1, 0, tsBg));
} else if (spt === "Streets Imaginaire") {
ctx.lineCap = 'square'; ctx.moveTo(0,0);ctx.lineTo(tsBg,0); ctx.moveTo(0,0);ctx.lineTo(0,tsBg); ctx.moveTo(0,tsBg);ctx.lineTo(tsBg,tsBg); ctx.moveTo(tsBg,0);ctx.lineTo(tsBg,tsBg);
} else if (spt === "XXX") {
ctx.moveTo(0,0);ctx.lineTo(tsBg,0); ctx.moveTo(0,0);ctx.lineTo(0,tsBg); ctx.moveTo(0,tsBg);ctx.lineTo(tsBg,tsBg); ctx.moveTo(tsBg,0);ctx.lineTo(tsBg,tsBg); ctx.moveTo(0,0);ctx.lineTo(tsBg,tsBg); ctx.moveTo(tsBg,0);ctx.lineTo(0,tsBg);
} else if (spt === "Drift") {
ctx.moveTo(0,0);ctx.lineTo(tsBg,0);
} else if (spt === "Portals") {
let y = map(sin(frameCount*0.02),-1,1,0,tsBg); ctx.moveTo(0,y);ctx.lineTo(tsBg,y);
let x = map(sin(frameCount*0.02),-1,1,0,tsBg); ctx.moveTo(x,0);ctx.lineTo(x,tsBg);
} else if (spt === "Gang" && typeof noise === 'function') {
let oX = map(noise(frameCount*0.0025),0,1,-90,90), oY = map(noise(frameCount*0.0025+100),0,1,-90,90);
ctx.save(); ctx.translate(tsBg/2+oX, tsBg/2+oY); ctx.strokeRect(-tsBg*.1,0,tsBg*.07,tsBg*.07); ctx.strokeRect(tsBg*.1,0,tsBg*.07,tsBg*.07); ctx.restore(); ctx.closePath(); return;
} else if (spt === "Dust") {
if (drawBgTile.ballX === undefined) { drawBgTile.ballX = tsBg/2; drawBgTile.ballY = tsBg/2; drawBgTile.ballVX = 2; drawBgTile.ballVY = 3; }
drawBgTile.ballX += drawBgTile.ballVX; drawBgTile.ballY += drawBgTile.ballVY;
if (drawBgTile.ballX<0||drawBgTile.ballX>tsBg) { drawBgTile.ballX=max(0,min(tsBg,drawBgTile.ballX)); drawBgTile.ballVX*=-1; }
if (drawBgTile.ballY<0||drawBgTile.ballY>tsBg) { drawBgTile.ballY=max(0,min(tsBg,drawBgTile.ballY)); drawBgTile.ballVY*=-1; }
ctx.lineWidth = st.weighta; ctx.beginPath(); ctx.ellipse(drawBgTile.ballX,drawBgTile.ballY,tsBg*.01,tsBg*.01,0,0,TWO_PI); ctx.stroke(); ctx.closePath(); return;
} else if (spt === "Rings") {
let ang=frameCount*.015, pX=map(cos(ang),-1,1,0,tsBg), pY=map(sin(ang*1.2),-1,1,0,tsBg);
ctx.beginPath(); ctx.ellipse(pX,pY,1,1,0,0,TWO_PI); ctx.stroke(); ctx.closePath(); return;
} else if (spt === "Entangled" && typeof noise === 'function') {
let sd=floor(noise(frameCount*.001)*1e4), raw=sin(sd*.00123), ang=map(raw,-1,1,-PI/4,PI/4); if(sd%2===0)ang=PI-ang;
let dX=cos(ang),dY=sin(ang),rX=tsBg/2+dX*frameCount,rY=tsBg/2+dY*frameCount;
const mB=(p,s)=>abs(((p%(2*s))+(2*s))%(2*s)-s);
ctx.beginPath();ctx.ellipse(mB(rX,tsBg),mB(rY,tsBg),1.5,1.5,0,0,TWO_PI);ctx.stroke();ctx.closePath();return;
}
ctx.stroke();
}
function drawMgTile() {
let colb = st.aclb; if (st.weightb <= 0) return;
ctx.strokeStyle = toCanvasRGBA(colb); ctx.lineWidth = st.weightb; ctx.fillStyle = 'transparent'; ctx.beginPath();
const spt = st.spt;
if (spt === "Markets" && typeof noise === 'function') {
ctx.moveTo(0, map(noise(0, frameCount * 0.01), 0, 1, 0, tsMg));
for (let x = 5; x <= tsMg; x += 5) ctx.lineTo(x, map(noise(x * 0.05, frameCount * 0.01), 0, 1, 0, tsMg));
} else if (spt === "Streets Imaginaire" && typeof noise === 'function') {
ctx.save(); ctx.translate(tsMg/2,tsMg/2); ctx.rotate(noise(frameCount*.01)*TWO_PI); ctx.lineWidth=3;
ctx.moveTo(-tsMg*.1,0); ctx.lineTo(tsMg*.1,0); ctx.stroke(); ctx.restore(); ctx.closePath(); return;
} else if (spt === "XXX") {
ctx.moveTo(0,0);ctx.lineTo(tsMg,0); ctx.moveTo(0,0);ctx.lineTo(0,tsMg); ctx.moveTo(0,tsMg);ctx.lineTo(tsMg,tsMg); ctx.moveTo(tsMg,0);ctx.lineTo(tsMg,tsMg); ctx.moveTo(0,0);ctx.lineTo(tsMg,tsMg); ctx.moveTo(tsMg,0);ctx.lineTo(0,tsMg);
} else if (spt === "Drift") {
ctx.moveTo(0,0);ctx.lineTo(tsMg,tsMg); ctx.moveTo(tsMg,0);ctx.lineTo(0,tsMg);
} else if (spt === "Portals") {
let y=map(sin(frameCount*.02),-1,1,0,tsMg); ctx.moveTo(0,y);ctx.lineTo(tsMg,y);
let x=map(sin(frameCount*.02),-1,1,0,tsMg); ctx.moveTo(x,0);ctx.lineTo(x,tsMg);
} else if (spt === "Gang") {
let s2=map(sin(frameCount*.05),-1,1,-99,-90),seta=tsMg/2; ctx.moveTo(0,seta);ctx.lineTo(tsMg+s2,seta); ctx.moveTo(0,seta);ctx.lineTo(0,tsMg+s2+seta); ctx.moveTo(0,tsMg+s2+seta);ctx.lineTo(tsMg+s2,tsMg+s2+seta); ctx.moveTo(tsMg+s2,seta);ctx.lineTo(tsMg+s2,tsMg+s2+seta);
let s2b=map(sin(frameCount*.05),-1,1,-99,-90),gap2=tsMg/5; ctx.moveTo(gap2,seta);ctx.lineTo(gap2+tsMg+s2b,seta); ctx.moveTo(gap2,seta);ctx.lineTo(gap2,tsMg+s2b+seta); ctx.moveTo(gap2,tsMg+s2b+seta);ctx.lineTo(gap2+tsMg+s2b,tsMg+s2b+seta); ctx.moveTo(gap2+tsMg+s2b,seta);ctx.lineTo(gap2+tsMg+s2b,tsMg+s2b+seta);
} else if (spt === "Dust") {
ctx.beginPath(); ctx.ellipse(0,tsMg,1,1,0,0,TWO_PI); ctx.fillStyle=toCanvasRGBA(colb); ctx.fill(); ctx.closePath(); return;
} else if (spt === "Rings") {
let ang=frameCount*.015, pX=map(cos(ang),-1,1,0,tsMg), pY=map(sin(ang*1.2),-1,1,0,tsMg);
ctx.beginPath(); ctx.ellipse(pX,pY,1,1,0,0,TWO_PI); ctx.stroke(); ctx.closePath(); return;
} else if (spt === "Entangled") {
ctx.moveTo(0,0);ctx.lineTo(tsMg,tsMg);
}
ctx.stroke();
}
function drawFgTile() {
let colc = st.aclc; if (st.weightc <= 0) return;
ctx.strokeStyle = toCanvasRGBA(colc); ctx.lineWidth = st.weightc; ctx.fillStyle = 'transparent'; ctx.lineCap = 'square'; ctx.beginPath();
const spt = st.spt;
if (spt === "Streets Imaginaire") {
ctx.moveTo(0,0);ctx.lineTo(tsFg,0); ctx.moveTo(0,0);ctx.lineTo(0,tsFg); ctx.moveTo(0,tsFg);ctx.lineTo(tsFg,tsFg); ctx.moveTo(tsFg,0);ctx.lineTo(tsFg,tsFg);
} else if (spt === "XXX") {
ctx.moveTo(0,0);ctx.lineTo(tsFg,tsFg); ctx.moveTo(tsFg,0);ctx.lineTo(0,tsFg);
} else if (spt === "Drift") {
ctx.beginPath(); ctx.moveTo(0,0);ctx.lineTo(0,tsFg); ctx.stroke();
ctx.beginPath(); ctx.moveTo(tsFg,0);ctx.lineTo(tsFg,tsFg); ctx.stroke();
ctx.fillStyle=toCanvasRGBA(colc); let cX=tsFg/10, pS=max(1,st.weightc);
for(let i=5;i<tsFg;i+=10) ctx.fillRect(cX-pS/2,i-pS/2,pS,pS); return;
} else if (spt === "Portals" || spt === "Gang") {
ctx.moveTo(0,0);ctx.lineTo(0,tsFg); ctx.moveTo(tsFg,0);ctx.lineTo(tsFg,tsFg);
} else if (spt === "Dust") {
ctx.beginPath(); ctx.ellipse(0,tsFg/2,1.5,1.5,0,0,TWO_PI); ctx.fillStyle=toCanvasRGBA(colc); ctx.fill(); ctx.closePath(); return;
} else if (spt === "Rings") {
let ang=frameCount*.015,pX=map(cos(ang),-1,1,0,tsFg),pY=map(sin(ang*1.2),-1,1,0,tsFg);
ctx.beginPath();ctx.ellipse(pX,pY,1,1,0,0,TWO_PI);ctx.stroke();ctx.closePath();return;
} else if (spt === "Entangled") {
ctx.moveTo(0,0);ctx.lineTo(tsFg,tsFg);
}
if (spt !== "Markets" && spt !== "Drift" && spt !== "Dust" && spt !== "Rings") {
ctx.stroke();
}
}
function initSun() { return { active:!0, startTime:frameCount, cycleTime:1200, pauseTime:300, startY:random(height/1.5,height/1.05), endY:random(height/1.5,height/1.05) }; }
function initComet() { return { active:!0, startTime:frameCount, cycleTime:1000, pauseTime:120, fromLeft:random()<.5, startY:random(height*.2,height*.8), endY:random(height*.3,height*.05), horizontalDir:random()<.5?1:-1 }; }
function initMoonbow() { return { active:!0, startTime:frameCount, cycleTime:random(1200,2000), pauseTime:random(150,200), startX:-80, startY:random(height/8,height/2.2), endX:width+80, endY:random(height/8,height/2.2), size:random(25,50) }; }
function initNebula() { let iW=random(100,150); return { active:!0, startTime:frameCount, cycleTime:random(1800,2300), pauseTime:random(120,300), startX:random(width), startY:height+80, endX:random(width), endY:-80, nebulaWidth:iW, nebulaHeight:iW/3 }; }
function manageEffectLifecycle(effectName, initFn) {
if (!effectStates[effectName]) effectStates[effectName] = initFn();
let state = effectStates[effectName], isActive = !1, t = null;
if (state.active) {
t = (frameCount - state.startTime) / state.cycleTime;
if (t >= 1) { state.active = !1; state.pauseStart = frameCount; t = null; } else isActive = !0;
} else if (frameCount - state.pauseStart > state.pauseTime) {
effectStates[effectName] = initFn();
}
return { isActive: isActive, t: t, state: state };
}
function drawAdditionalBackground() {
ctx.save(); ctx.strokeStyle = 'transparent';
const currentBgTrait = bgTrait;
if (currentBgTrait === "Sun") {
let eS = manageEffectLifecycle("sun", initSun);
if (eS.isActive) {
let s=eS.state, t=eS.t, sX=lerp(width+150,-150,t), sY=lerp(s.startY,s.endY,t)-sin(PI*t)*250;
let iC=color(bgs.trailColor), oC=color(bgs.trailColor); iC.a=1; oC.a=1;
for(let r=40;r>0;r-=4){ let intr=map(r,0,40,1,0), curC=lerpColor(iC,oC,intr); ctx.fillStyle=toCanvasRGBA(curC); ctx.beginPath(); let rad=r*1.75; ctx.ellipse(sX,sY,rad,rad,0,0,TWO_PI); ctx.fill(); }
}
} else if (currentBgTrait === "Comet") {
let eS = manageEffectLifecycle("comet", initComet);
if (eS.isActive) {
let c=eS.state, t=eS.t, cX=c.fromLeft?lerp(-50,width+50,t):lerp(width+50,-50,t), cY=lerp(c.startY,c.endY,t)-sin(t*PI)*c.endY;
let bCometC=color(bgs.trailColor); bCometC.a=1; ctx.fillStyle=toCanvasRGBA(bCometC); ctx.beginPath(); ctx.ellipse(cX,cY,15,15,0,0,TWO_PI); ctx.fill();
let tDX=c.fromLeft?-1:1, tDY=0, tL=15, bTEC=color(...bgs.bgb);
for(let i=0;i<tL;i++){ let intr=map(i,0,tL,0,1), tailC=lerpColor(bCometC,bTEC,intr); tailC.a=map(i,0,tL,1,0); let tX=cX+tDX*i,tY=cY+tDY*i,tRad=(30-i*.15)/2; if(tRad<=0)continue; ctx.fillStyle=toCanvasRGBA(tailC);ctx.beginPath();ctx.ellipse(tX,tY,tRad,tRad,0,0,TWO_PI);ctx.fill(); }
}
} else if (currentBgTrait === "Stars") {
effectStates.starRotation += effectStates.starRotationsp;
let cx_s=effectStates.starRotationCenter.x, cy_s=effectStates.starRotationCenter.y, theta=effectStates.starRotation;
let starC=color(bgs.trailColor); ctx.fillStyle=toCanvasRGBA(starC);
for(let s_star of effectStates.stars){ let dX=s_star.x-cx_s,dY=s_star.y-cy_s,rX=dX*cos(theta)-dY*sin(theta)+cx_s,rY=dX*sin(theta)+dY*cos(theta)+cy_s; ctx.beginPath();let rad=s_star.size/2;ctx.ellipse(rX,rY,rad,rad,0,0,TWO_PI);ctx.fill(); }
} else if (currentBgTrait === "Moonbow") {
let eS = manageEffectLifecycle("moonbow", initMoonbow);
if (eS.isActive) {
let bh=eS.state, t=eS.t, bX=lerp(width+80,-80,t), bY=lerp(bh.startY,bh.endY,t), pY=bY+sin(PI*t)*100;
ctx.save();ctx.translate(bX,pY); let rad=bh.size,c1=color("#f5de10"),c2=color(bgs.trailColor);
for(let ym=-rad;ym<=rad;ym++){let intr=map(ym,-rad,rad,0,1),gC=lerpColor(c1,c2,intr);gC.a=map(abs(ym),0,rad,.05,.01);ctx.fillStyle=toCanvasRGBA(gC);let sH=2*sqrt(max(0,rad*rad-ym*ym));if(sH>0)ctx.fillRect(-sH/2,ym,sH,1);} ctx.restore();
}
} else if (currentBgTrait === "Nebula") {
let eS = manageEffectLifecycle("nebula", initNebula);
if (eS.isActive && typeof noise === 'function') {
let bh=eS.state, t=eS.t, bX=lerp(bh.startX,bh.endX,t), bY=lerp(bh.startY,bh.endY,t), pX=bX+sin(t*TWO_PI*3)*50;
ctx.save();ctx.translate(pX,bY); let nebC=color(...bgs.bgb); ctx.fillStyle=toCanvasRGBA(nebC);
let topEdgePts=[]; for(let xn=-bh.nebulaWidth/2;xn<=bh.nebulaWidth/2;xn++){let sH=bh.nebulaHeight*sqrt(max(0,1-(2*xn/bh.nebulaWidth)*(2*xn/bh.nebulaWidth)));if(sH>0){topEdgePts.push({x:xn,y:-sH/2});ctx.fillStyle=toCanvasRGBA(nebC);ctx.fillRect(xn,-sH/2,1,sH);}}
if(topEdgePts.length>1){let distArcPts=[];for(let p of topEdgePts){let nV=noise((p.x+1e3)*.01,frameCount*.002);distArcPts.push({x:p.x,y:p.y+map(nV,0,1,-35,35)});} let arcC=color(bgs.trailColor);arcC.a=.8;ctx.strokeStyle=toCanvasRGBA(arcC);ctx.lineCap='round';ctx.lineJoin='round';let minW=1,maxW=3;for(let i=1;i<distArcPts.length;i++){let p1=distArcPts[i-1],p2=distArcPts[i],tArc=(i-.5)/(distArcPts.length-1);ctx.lineWidth=lerp(maxW,minW,abs(tArc-.5)*2);ctx.beginPath();ctx.moveTo(p1.x,p1.y);ctx.lineTo(p2.x,p2.y);ctx.stroke();}}
ctx.restore();
}
}
ctx.restore();
}
if (typeof window !== 'undefined') {
window.GOM_ART = {
initialize: initializeArtwork,
render: renderFrame
};
} else if (typeof self !== 'undefined') {
self.GOM_ART = {
initialize: initializeArtwork,
render: renderFrame
};
}
// --- Renderer Setup and Execution ---
window.ctx = canvas.getContext('2d');
window.width = canvas.width;
window.height = canvas.height;
window.frameCount = 0;
try {
if (window.GOM_ART && typeof window.GOM_ART.initialize === 'function') {
window.GOM_ART.initialize();
function animate() {
window.frameCount++;
window.GOM_ART.render();
requestAnimationFrame(animate);
}
animate();
} else {
throw new Error("GOM_ART object not found after script execution.");
}
} catch (e) {
console.error("Error during animation setup:", e);
document.body.innerHTML = `<pre style="color:red;font-family:sans-serif;">Error: ${e.message}</pre>`;
}
</script>
</body>
</html>