memoscan
← all memos

Memo 0x48f88b44…530354 on Ethereum

g.rect(0, 0, r.width, r.height) g.pop() } } isDone() { if (this.rectangles.length === 0) return true return this.fadedCount >= this.rectangles.length } averageOpacity() { if (this.startedCount === 0) return 100 let sum = 0 for (let r of this.rectangles) { if (r.started) sum += r.opacity } return sum / this.startedCount } } class Shockwave extends FillAnimation { constructor(x, y, palette, traits) { super(x, y, palette, traits) this.palette = palette this.rectangles = [] this._initRects(x, y, palette) } _initRects(x, y, palette) { const numRects = floor(random(3, 30)) for (let i = 0; i < numRects; i++) { const finalWidth = random(150, 1100) * _designScale const finalHeight = random(150, 1100) * _designScale const duration = random(80, 250) / FILL_ANIMATION_SPEED_MULT const startDelay = floor(random(0, 80)) const originalSW = random(8, 150) * _designScale this.rectangles.push({ x: x, y: y, width: 0, height: 0, targetWidth: finalWidth, targetHeight: finalHeight, growthRateWidth: finalWidth / duration, growthRateHeight: finalHeight / duration, color: random(palette), sw: originalSW, originalSW: originalSW, delay: startDelay, started: false, done: false, blendMode: this.getBlend(random() < 0.2 ? DIFFERENCE : BLEND), }) } } reset(x, y, palette, traits) { super.reset(x, y, palette, traits) this.palette = palette this.rectangles = [] this._initRects(x, y, palette) } update() { super.update() for (let r of this.rectangles) { if (this.age < r.delay) continue if (!r.started) r.started = true r.width += r.growthRateWidth r.height += r.growthRateHeight const sizeProgress = max(r.width / r.targetWidth, r.height / r.targetHeight) if (sizeProgress >= 0.6) { const ease = pow((sizeProgress - 0.6) / 0.4, 2) r.sw = max(0, r.originalSW * (1 - ease)) } if (!r.done && r.width >= r.targetWidth && r.height >= r.targetHeight && r.sw <= 0) { r.done = true } } } display(g) { for (let r of this.rectangles) { if (!r.started || r.sw <= 0) continue g.push() g.blendMode(r.blendMode) g.stroke(r.color) g.strokeWeight(r.sw) g.noFill() g.rectMode(CENTER) const adjW = max(0, r.width - r.sw) const adjH = max(0, r.height - r.sw) g.rect(r.x, r.y, adjW, adjH) g.pop() } } isDone() { if (this.rectangles.length === 0) return true return this.rectangles.every(r => r.done) } averageOpacity() { if (this.rectangles.length === 0) return 0 let visible = 0 for (let r of this.rectangles) { if (r.started && r.sw > 0) visible++ } return (visible / this.rectangles.length) * 100 } } class ExpandingCircles extends FillAnimation { constructor(x, y, palette, traits) { super(x, y, palette, traits) this.circles = [] const count = floor(random(1, 7)) for (let i = 0; i < count; i++) { this.circles.push({ radius: random(10, 30) * _designScale, maxRadius: random(500, 900) * _designScale, speed: random(0.375, 3.0) * FILL_ANIMATION_SPEED_MULT, strokeWeight: random(2, 6) * _designScale, color: random(palette), opacity: 100, blendMode: this.getBlend(i % 2 === 0 ? DIFFERENCE : ADD), maxLife: floor(random(60, 350)), age: 0 }) } } reset(x, y, palette, traits) { super.reset(x, y, palette, traits) this.circles = [] const count = floor(random(1, 3)) for (let i = 0; i < count; i++) { this.circles.push({ radius: random(10, 20) * _designScale, maxRadius: random(500, 900) * _designScale, speed: random(0.375, 3.0) * FILL_ANIMATION_SPEED_MULT, strokeWeight: random(2, 6) * _designScale, color: random(palette), opacity: 100, blendMode: this.getBlend(i % 2 === 0 ? DIFFERENCE : ADD), maxLife: floor(random(60, 350)), age: 0 }) } } update() { super.update() for (let c of this.circles) { c.age++ const selfFading = c.age > c.maxLife c.radius += c.speed * (selfFading ? 2 : 1) const fadeProgress = c.radius / c.maxRadius const t = constrain(map(fadeProgress, 0.2, 1.1, 0, 1), 0, 1) c.opacity = max(0, 100 * (1 - t)) } } display(g) { g.push() g.noFill() for (let c of this.circles) { if (c.opacity <= 0) continue g.push() g.blendMode(c.blendMode) const col = g.color(c.color) col.setAlpha(c.opacity) g.stroke(col) g.strokeWeight(c.strokeWeight) g.noFill() g.circle(this.x, this.y, c.radius * 2) g.pop() } g.pop() } isDone() { if (this.circles.length === 0) return true return this.circles.every(c => c.opacity <= 0) } averageOpacity() { if (this.circles.length === 0) return 0 return this.circles.reduce((sum, c) => sum + c.opacity, 0) / this.circles.length } } class ExpandingSquares extends FillAnimation { constructor(x, y, palette, traits) { super(x, y, palette, traits) this.fadeInDuration = 60 this.squares = [] this.persistMode = (traits.expandingPersist === 'persist') this.persistentLayer = null this.rotationMode = traits.expandingRotation || 'rotating' this.fixedRotation = this.rotationMode === 'static' ? random([0, QUARTER_PI, HALF_PI, QUARTER_PI * 3]) : 0 const count = floor(random(1, 7)) for (let i = 0; i < count; i++) { this.squares.push({ size: random(10, 30) * _designScale, maxSize: random(500, 900) * _designScale, speed: random(0.375, 3.0) * FILL_ANIMATION_SPEED_MULT, strokeWeight: random(2, 6) * _designScale, width: random(0.5, 1.5), height: random(0.5, 1.5), rotation: this.rotationMode === 'rotating' ? random(TWO_PI) : this.fixedRotation, rotSpeed: this.rotationMode === 'rotating' ? random(-0.02, 0.02) : 0, color: random(palette), opacity: 100, blendMode: this.getBlend(i % 2 === 0 ? DIFFERENCE : ADD), maxLife: floor(random(60, 350)), age: 0 }) } } reset(x, y, palette, traits) { super.reset(x, y, palette, traits) this.squares = [] this.persistMode = (traits.expandingPersist === 'persist') this.rotationMode = traits.expandingRotation || 'rotating' this._fadeoutStart = undefined if (!this.fixedRotation && this.rotationMode === 'static') { this.fixedRotation = random([0, QUARTER_PI, HALF_PI, QUARTER_PI * 3]) } const count = floor(random(1, 3)) if (this.persistMode && !this.persistentLayer) { this.persistentLayer = createGraphics(width, height) this.persistentLayer.clear() } else if (!this.persistMode && this.persistentLayer) { this.persistentLayer.remove() this.persistentLayer = null } else if (this.persistMode && this.persistentLayer) { this.persistentLayer.clear() } for (let i = 0; i < count; i++) { this.squares.push({ size: random(10, 20), maxSize: random(500, 900) * _designScale, speed: random(0.375, 3.0) * FILL_ANIMATION_SPEED_MULT, strokeWeight: random(2, 6) * _designScale, width: random(0.5, 1.5), height: random(0.5, 1.5), rotation: this.rotationMode === 'rotating' ? random(TWO_PI) : this.fixedRotation, rotSpeed: this.rotationMode === 'rotating' ? random(-0.02, 0.02) : 0, color: random(palette), opacity: 100, blendMode: this.getBlend(i % 2 === 0 ? DIFFERENCE : ADD), maxLife: floor(random(60, 350)), age: 0 }) } } update() { super.update() for (let s of this.squares) { s.age++ const selfFading = s.age > s.maxLife s.size += s.speed * (selfFading ? 2 : 1) const fadeProgress = s.size / s.maxSize const t = constrain(map(fadeProgress, 0.2, 1.1, 0, 1), 0, 1) s.opacity = max(0, 100 * (1 - t)) if (this.persistMode && s.size >= s.maxSize) { s.opacity = max(0, s.opacity - (selfFading ? 0.8 : 0.5)) } } } display(g) { g.push() const visualOpacity = this.getVisualOpacity() if (this.persistMode && this.persistentLayer) { if (visualOpacity < 255) { g.tint(255, visualOpacity) } g.image(this.persistentLayer, 0, 0) } g.noFill() for (let s of this.squares) { if (s.opacity <= 0) continue const targetBuffer = (this.persistMode && this.persistentLayer) ? this.persistentLayer : g targetBuffer.push() targetBuffer.blendMode(s.blendMode) targetBuffer.stroke(s.color) targetBuffer.strokeWeight(s.strokeWeight) targetBuffer.noFill() if (!this.persistMode) { const c = color(s.color) c.setAlpha(s.opacity * (visualOpacity / 255)) targetBuffer.stroke(c) } targetBuffer.translate(this.x, this.y) targetBuffer.rotate(s.rotation) targetBuffer.rectMode(CENTER) targetBuffer.rect(0, 0, s.size * s.width * 2, s.size * s.height * 2) targetBuffer.pop() if (this.persistMode && this.persistentLayer && targetBuffer === this.persistentLayer) { g.push() g.blendMode(s.blendMode) const c = color(s.color) c.setAlpha(visualOpacity) g.stroke(c) g.strokeWeight(s.strokeWeight) g.noFill() g.translate(this.x, this.y) g.rotate(s.rotation) g.rectMode(CENTER) g.rect(0, 0, s.size * s.width * 2, s.size * s.height * 2) g.pop() } } g.pop() } getVisualOpacity() { const anyAlive = this.squares.some(s => s.opacity > 0) if (anyAlive) { this._fadeoutStart = undefined return 255 } if (!this._fadeoutStart) this._fadeoutStart = this.age const elapsed = this.age - this._fadeoutStart const fadeDuration = this.persistMode ? 100 : 1 // Only persistent layer needs fadeout return max(0, map(elapsed, 0, fadeDuration, 255, 0)) } isDone() { return this.getVisualOpacity() <= 0 } averageOpacity() { const squareOpacity = this.squares.length === 0 ? 0 : this.squares.reduce((sum, s) => sum + s.opacity, 0) / this.squares.length if (squareOpacity > 0) return squareOpacity return this.getVisualOpacity() / 255 * 100 } } class VerticalStripes extends FillAnimation { constructor(x, y, palette, traits) { super(x, y, palette, traits) this.stripes = [] this.reset(x, y, palette, traits) } reset(x, y, palette, traits) { super.reset(x, y, palette, traits) this.stripes = [] const count = floor(random(4, 10)) const spacing = width / count for (let i = 0; i < count; i++) { this.stripes.push({ x: i * spacing + random(-spacing * 0.3, spacing * 0.3), y: random(-height * 0.2, height * 0.2), width: random(spacing * 0.05, spacing * 0.5), height: 0, maxHeight: height * random(1, 7), speed: random(3, 10) * FILL_ANIMATION_SPEED_MULT, color: random(palette), opacity: 100, blendMode: this.getBlend([DIFFERENCE, ADD, SCREEN][i % 3]), maxLife: floor(random(60, 300)), age: 0, selfFading: false }) } } update() { super.update() for (let s of this.stripes) { s.age++ if (!s.selfFading && s.age > s.maxLife) { s.selfFading = true } s.height = min(s.height + s.speed * (s.selfFading ? 1.5 : 1), s.maxHeight) if (s.selfFading || s.height >= s.maxHeight) { s.opacity = max(0, s.opacity - (s.selfFading ? 0.8 : 0.3)) } } } display(g) { g.push() g.noStroke() for (let s of this.stripes) { if (s.opacity <= 0) continue g.push() g.blendMode(s.blendMode) const c = g.color(s.color) c.setAlpha(s.opacity) g.fill(c) g.rectMode(CENTER) g.rect(s.x, s.y, s.width, s.height) g.pop() } g.pop() } isDone() { if (this.stripes.length === 0) return true return this.stripes.every(s => s.opacity <= 0) } averageOpacity() { if (this.stripes.length === 0) return 0 return this.stripes.reduce((sum, s) => sum + s.opacity, 0) / this.stripes.length } } class HorizontalStripes extends FillAnimation { constructor(x, y, palette, traits) { super(x, y, palette, traits) this.stripes = [] this.reset(x, y, palette, traits) } reset(x, y, palette, traits) { super.reset(x, y, palette, traits) this.stripes = [] const verticalBuffer = 0.25 const baseHeight = globalBounds ? (globalBounds.maxY - globalBounds.minY) : height const baseMinY = globalBounds ? globalBounds.minY : 0 const baseMaxY = globalBounds ? globalBounds.maxY : height const maskMinY = baseMinY - baseHeight * verticalBuffer const maskMaxY = baseMaxY + baseHeight * verticalBuffer const maskHeight = maskMaxY - maskMinY const maskMinX = globalBounds ? globalBounds.minX : 0 const maskMaxX = globalBounds ? globalBounds.maxX : width const maskWidth = maskMaxX - maskMinX const count = floor(random(5, 15)) const spacing = maskHeight / count const widthMode = random(['wide', 'narrow', 'variable']) for (let i = 0; i < count; i++) { let stripeHeight if (widthMode === 'wide') { stripeHeight = spacing * random(0.7, 1.2) } else if (widthMode === 'narrow') { stripeHeight = spacing * random(0.05, 0.25) } else { stripeHeight = random(spacing * 0.1, spacing * 1.3) } this.stripes.push({ x: random(maskMinX - maskWidth * 0.3, maskMinX + maskWidth * 1.5), y: maskMinY + i * spacing + random(-spacing * 0.3, spacing * 0.3), width: 0, maxWidth: maskWidth * random(1.2, 2.0), height: stripeHeight, speed: random(2, 4.5) * FILL_ANIMATION_SPEED_MULT, color: random(palette), opacity: 100, blendMode: this.getBlend([DIFFERENCE, ADD, MULTIPLY][i % 3]), maxLife: floor(random(60, 300)), age: 0, selfFading: false }) } } update() { super.update() for (let s of this.stripes) { s.age++ if (!s.selfFading && s.age > s.maxLife) { s.selfFading = true } s.width = min(s.width + s.speed * (s.selfFading ? 1.5 : 1), s.maxWidth) if (s.selfFading || s.width >= s.maxWidth) { s.opacity = max(0, s.opacity - (s.selfFading ? 0.8 : 0.3)) } } } display(g) { g.push() g.noStroke() for (let s of this.stripes) { if (s.opacity <= 0) continue g.push() g.blendMode(s.blendMode) const c = g.color(s.color) c.setAlpha(s.opacity) g.fill(c) g.rectMode(CENTER) g.rect(s.x, s.y, s.width, s.height) g.pop() } g.pop() } isDone() { if (this.stripes.length === 0) return true return this.stripes.every(s => s.opacity <= 0) } averageOpacity() { if (this.stripes.length === 0) return 0 return this.stripes.reduce((sum, s) => sum + s.opacity, 0) / this.stripes.length } } class GradientFill extends FillAnimation { constructor(x, y, palette, traits) { super(x, y, palette, traits) this.fadeInDuration = 60 this.radius = 0 this.prevRadius = 0 this.maxRadius = max(width, height) * 1.5 this.speed = random(1.5, 3) * FILL_ANIMATION_SPEED_MULT this.colors = [] this.persistentLayer = createGraphics(width, height) this.angle = random(TWO_PI) this.lifetime = 0 this.fadeStartTime = null const colorCount = floor(random(3, 6)) for (let i = 0; i < colorCount; i++) { this.colors.push(random(palette)) } } reset(x, y, palette, traits) { super.reset(x, y, palette, traits) this.radius = 0 this.prevRadius = 0 this.maxRadius = max(width, height) * 1.5 this.speed = random(1.5, 3) * FILL_ANIMATION_SPEED_MULT this.colors = [] this.angle = random(TWO_PI) this.lifetime = 0 this.fadeStartTime = null if (!this.persistentLayer) { this.persistentLayer = createGraphics(width, height) } this.persistentLayer.clear() const colorCount = floor(random(3, 20)) for (let i = 0; i < colorCount; i++) { this.colors.push(random(palette)) } } update() { super.update() this.lifetime++ this.prevRadius = this.radius this.radius = min(this.radius + this.speed, this.maxRadius) if (this.radius >= this.maxRadius && this.fadeStartTime === null) { this.fadeStartTime = this.lifetime } if (this.radius > this.prevRadius && this.radius < this.maxRadius) { const pl = this.persistentLayer pl.push() pl.noStroke() pl.blendMode(BLEND) const r = this.radius const t = r / this.maxRadius const colorIndex = floor(t * (this.colors.length - 1)) const nextColorIndex = min(colorIndex + 1, this.colors.length - 1) const localT = (t * (this.colors.length - 1)) % 1 const c = lerpColor(color(this.colors[colorIndex]), color(this.colors[nextColorIndex]), localT) pl.noFill() pl.stroke(c) pl.strokeWeight(this.speed + 1) pl.circle(this.x, this.y, r * 2) pl.pop() } } getVisualOpacity() { if (this.fadeStartTime === null) { return 255 } const fadeTime = 150 const timeSinceFade = this.lifetime - this.fadeStartTime return max(0, map(timeSinceFade, 0, fadeTime, 255, 0)) } display(g) { g.push() const opacity = this.getVisualOpacity() if (opacity < 255) { g.tint(255, opacity) } g.image(this.persistentLayer, 0, 0) g.pop() } isDone() { return this.getVisualOpacity() <= 0 } averageOpacity() { return this.getVisualOpacity() / 255 * 100 } } class WavyLines extends FillAnimation { constructor(x, y, palette, traits) { super(x, y, palette, traits) this.fadeInDuration = 60 this.lines = [] this.trailLayer = null // Decaying trail buffer — always active this.directionMode = traits.wavyDirection || 'chaos' this.noiseOffset = random(100) // Unique noise offset per instance this.diagonalAngle = null // Locked per instance for diagonal mode const lineCount = floor(random(10, 24)) for (let i = 0; i < lineCount; i++) { const orientation = this.pickOrientation() this.lines.push(this.createLine(orientation, palette, i)) } } pickOrientation() { const mode = this.directionMode if (mode === 'horizontal-clash') { return random() < 0.5 ? 'horizontal-right' : 'horizontal-left' } if (mode === 'chaos') { const dirs = ['horizontal-right', 'horizontal-left', 'vertical-down', 'vertical-up', 'diagonal-down-right', 'diagonal-down-left', 'diagonal-up-right', 'diagonal-up-left'] return random(dirs) } return mode } createLine(orientation, palette, index) { const fullSpan = max(width, height) * 1.5 // Extended for diagonal lines const crossSpan = orientation === 'horizontal' ? height : width const spanType = random() let startT, endT if (spanType < 0.3) { startT = 0 endT = 1 } else if (spanType < 0.6) { startT = 0 endT = random(0.3, 0.8) } else if (spanType < 0.8) { startT = random(0.2, 0.7) endT = 1 } else { startT = random(0.1, 0.4) endT = random(0.6, 0.9) } const baseFreq = random(1.5, 4) const harmonics = [ { freq: baseFreq, amp: random(20, 10) * _designScale, phase: random(TWO_PI) }, { freq: baseFreq * random(1.8, 2.5), amp: random(8, 25) * _designScale, phase: random(TWO_PI) }, { freq: baseFreq * random(0.3, 0.6), amp: random(15, 40) * _designScale, phase: random(TWO_PI) }, ] const noiseSeedX = random(100) const noiseSeedY = random(1000) const numSegments = 40 const baseOffset = -random(100, 300) const baseSpeed = random(0.2, 2.8) * FILL_ANIMATION_SPEED_MULT const segments = [] for (let i = 0; i <= numSegments; i++) { const t = i / numSegments const speedVariation = random(0.4, 1.6) // Some segments faster, some slower segments.push({ offset: baseOffset, speed: baseSpeed * speedVariation, speedNoiseSeed: random(1000), }) } let angle, moveAngle if (orientation === 'horizontal-right') { angle = 0 moveAngle = PI / 2 // Move downward (perpendicular) } else if (orientation === 'horizontal-left') { angle = 0 moveAngle = -PI / 2 // Move upward (perpendicular) } else if (orientation === 'vertical-down') { angle = PI / 2 moveAngle = 0 // Move rightward (perpendicular) } else if (orientation === 'vertical-up') { angle = PI / 2 moveAngle = PI // Move leftward (perpendicular) } else if (orientation === 'diagonal-down-right') { angle = random(PI / 6, PI / 3) // 30-60 degrees moveAngle = angle + PI / 2 // Perpendicular movement } else if (orientation === 'diagonal-down-left') { angle = random(2 * PI / 3, 5 * PI / 6) // 120-150 degrees moveAngle = angle + PI / 2 } else if (orientation === 'diagonal-up-right') { angle = random(-PI / 3, -PI / 6) // -30 to -60 degrees moveAngle = angle + PI / 2 } else if (orientation === 'diagonal-up-left') { angle = random(-5 * PI / 6, -2 * PI / 3) // -120 to -150 degrees moveAngle = angle + PI / 2 } else { angle = random(TWO_PI) moveAngle = angle + PI / 2 } const startX = random(-width * 0.3, width * 1.3) const startY = random(-height * 0.3, height * 1.3) return { startX: startX, startY: startY, angle: angle, // Direction the line extends moveAngle: moveAngle, // Direction the line travels baseOffset: baseOffset, maxOffset: random(800, 2000) * _designScale, // Travel distance before starting to check exit baseSpeed: baseSpeed, segments: segments, harmonics: harmonics, thickness: random(3, 25) * _designScale, thicknessVariation: random(0.3, 10.8) * _designScale, color: random(palette), opacity: 100, isFading: false, // Once true, line commits to fading out (no flicker) blendMode: this.getBlend([DIFFERENCE, ADD, MULTIPLY][index % 3]), orientation: orientation, // Keep for reference startT: startT, endT: endT, noiseSeedX: noiseSeedX, noiseSeedY: noiseSeedY, noiseScale: random(0.003, 0.08), noiseAmp: random(15, 40) * _designScale, dripFactor: random(0.5, 1.5), phaseSpeed: random(0.01, 0.04), birthTime: this.age || 0, maxLife: floor(random(300, 700)), // 12-29 seconds at 24fps fadeInDuration: random(30, 60), speedEvolution: random(0.005, 0.02), speedNoiseScale: random(0.01, 0.03), lineLength: random(width * 0.8, width * 1.4), // How long the line extends } } reset(x, y, palette, traits) { super.reset(x, y, palette, traits) this.lines = [] this.directionMode = traits.wavyDirection || 'chaos' this.noiseOffset = random(1000) if (!this.trailLayer) { this.trailLayer = createGraphics(width, height) this.trailLayer.colorMode(HSB, 360, 100, 100, 100) } this.trailLayer.clear() this._trailDecayStart = undefined this._fadeoutStart = undefined const lineCount = floor(random(10, 24)) for (let i = 0; i < lineCount; i++) { const orientation = this.pickOrientation() this.lines.push(this.createLine(orientation, palette, i)) } } getWaveDisplacement(l, t, time) { const pos = t * l.lineLength let wave = 0 for (const h of l.harmonics) { const animatedPhase = h.phase + time * l.phaseSpeed wave += sin(pos * 0.02 * h.freq + animatedPhase) * h.amp } const noiseVal = noise( l.noiseSeedX + pos * l.noiseScale, l.noiseSeedY + time * 0.01 + this.noiseOffset ) const noiseDisplacement = (noiseVal - 0.5) * 2 * l.noiseAmp const ampModulation = noise(l.noiseSeedX + 100, t * 3) * 0.5 + 0.5 return (wave * ampModulation + noiseDisplacement) * l.dripFactor } getThicknessAt(l, t) { const thickNoise = noise(l.noiseSeedX + 200, t * 4) const variation = 1 - l.thicknessVariation + thickNoise * l.thicknessVariation * 2 return l.thickness * variation } update() { super.update() const time = this.age const padding = 100 const minX = -padding const maxX = width + padding const minY = -padding const maxY = height + padding for (let l of this.lines) { let maxSegmentOffset = -Infinity let minSegmentOffset = Infinity for (let i = 0; i < l.segments.length; i++) { const seg = l.segments[i] const t = i / (l.segments.length - 1) const speedNoise = noise( seg.speedNoiseSeed + time * l.speedEvolution, t * 3 ) const dynamicSpeedMult = 0.3 + speedNoise * 1.4 seg.offset += seg.speed * dynamicSpeedMult maxSegmentOffset = max(maxSegmentOffset, seg.offset) minSegmentOffset = min(minSegmentOffset, seg.offset) } l.currentMaxOffset = maxSegmentOffset l.currentMinOffset = minSegmentOffset let isOffCanvas = true const numSegments = l.segments.length - 1 for (let i = 0; i <= numSegments && isOffCanvas; i++) { const t = i / numSegments if (t < l.startT || t > l.endT) continue const seg = l.segments[i] const wave = this.getWaveDisplacement(l, t, time) const alongLine = t * l.lineLength - l.lineLength / 2 const baseX = l.startX + cos(l.angle) * alongLine const baseY = l.startY + sin(l.angle) * alongLine const px = baseX + cos(l.moveAngle) * (seg.offset + wave) const py = baseY + sin(l.moveAngle) * (seg.offset + wave) if (px >= minX && px <= maxX && py >= minY && py <= maxY) { isOffCanvas = false } } const lineAge = this.age - l.birthTime const pastMaxLife = lineAge > l.maxLife if (l.isFading) { l.opacity = max(0, l.opacity - 0.15) } else if (isOffCanvas && maxSegmentOffset > 0) { l.isFading = true l.opacity = max(0, l.opacity - 0.15) } else if (pastMaxLife) { l.isFading = true l.opacity = max(0, l.opacity - 0.25) } } } isDone() { return this.getVisualOpacity() <= 0 } getVisualOpacity() { const anyAlive = this.lines.some(l => l.opacity > 0) if (anyAlive) { this._fadeoutStart = undefined return 255 } if (!this._fadeoutStart) this._fadeoutStart = this.age const elapsed = this.age - this._fadeoutStart const fadeDuration = 150 // ~6 seconds at 24fps return max(0, map(elapsed, 0, fadeDuration, 255, 0)) } averageOpacity() { return this.getVisualOpacity() / 255 * 100 } display(g) { g.push() const visualOpacity = this.getVisualOpacity() if (this.trailLayer) { this.trailLayer.push() this.trailLayer.blendMode(BLEND) this.trailLayer.noStroke() this.trailLayer.fill(0, 0, 0, 3) // ~3% decay per frame this.trailLayer.rect(0, 0, width, height) this.trailLayer.pop() } const time = this.age for (let l of this.lines) { const lineAge = time - l.birthTime const fadeInAlpha = min(1, lineAge / l.fadeInDuration) const effectiveOpacity = l.opacity * fadeInAlpha * (visualOpacity / 255) if (effectiveOpacity <= 0) continue const numSegments = l.segments.length - 1 const segmentData = [] for (let i = 0; i < numSegments; i++) { const t1 = i / numSegments const t2 = (i + 1) / numSegments if (t2 < l.startT || t1 > l.endT) continue const segT1 = max(t1, l.startT) const segT2 = min(t2, l.endT) let edgeFade = 1 const edgeWidth = 0.1 if (segT1 < l.startT + edgeWidth) { edgeFade *= map(segT1, l.startT, l.startT + edgeWidth, 0, 1) } if (segT2 > l.endT - edgeWidth) { edgeFade *= map(segT2, l.endT - edgeWidth, l.endT, 1, 0) } const midT = (segT1 + segT2) / 2 const thickness = this.getThicknessAt(l, midT) const segIdx1 = floor(t1 * numSegments) const segIdx2 = min(segIdx1 + 1, numSegments) const localT = (t1 * numSegments) - segIdx1 const offset1 = lerp(l.segments[segIdx1].offset, l.segments[segIdx2].offset, localT) const segIdx3 = floor(t2 * numSegments) const segIdx4 = min(segIdx3 + 1, numSegments) const localT2 = (t2 * numSegments) - segIdx3 const offset2 = lerp(l.segments[segIdx3].offset, l.segments[segIdx4].offset, localT2) const alongLine1 = segT1 * l.lineLength - l.lineLength / 2 const alongLine2 = segT2 * l.lineLength - l.lineLength / 2 const baseX1 = l.startX + cos(l.angle) * alongLine1 const baseY1 = l.startY + sin(l.angle) * alongLine1 const baseX2 = l.startX + cos(l.angle) * alongLine2 const baseY2 = l.startY + sin(l.angle) * alongLine2 const wave1 = this.getWaveDisplacement(l, segT1, time) const wave2 = this.getWaveDisplacement(l, segT2, time) const x1 = baseX1 + cos(l.moveAngle) * (offset1 + wave1) const y1 = baseY1 + sin(l.moveAngle) * (offset1 + wave1) const x2 = baseX2 + cos(l.moveAngle) * (offset2 + wave2) const y2 = baseY2 + sin(l.moveAngle) * (offset2 + wave2) segmentData.push({ x1, y1, x2, y2, thickness, edgeFade }) } if (this.trailLayer) { this.trailLayer.push() this.trailLayer.blendMode(l.blendMode) this.trailLayer.noFill() for (const seg of segmentData) { const trailColor = color(l.color) trailColor.setAlpha(effectiveOpacity * seg.edgeFade * 0.5) // Half opacity for trail this.trailLayer.stroke(trailColor) this.trailLayer.strokeWeight(seg.thickness) this.trailLayer.line(seg.x1, seg.y1, seg.x2, seg.y2) } this.trailLayer.pop() } g.push() g.blendMode(l.blendMode) g.noFill() for (const seg of segmentData) { const coreColor = color(l.color) coreColor.setAlpha(effectiveOpacity * seg.edgeFade) g.stroke(coreColor) g.strokeWeight(seg.thickness) g.line(seg.x1, seg.y1, seg.x2, seg.y2) } g.pop() } if (this.trailLayer) { g.push() if (visualOpacity < 255) { g.tint(255, visualOpacity) } g.image(this.trailLayer, 0, 0) g.pop() } g.pop() } } class Sparkles extends FillAnimation { constructor(x, y, palette, traits) { super(x, y, palette, traits) this.fadeInDuration = 60 this.sparklePaths = [] this.lifetime = 0 this.maxLifetime = random(120, 250) this.maxActivePaths = 40 this.palette = palette this.persistentLayer = null } reset(x, y, palette, traits) { super.reset(x, y, palette, traits) this.sparklePaths = [] this.lifetime = 0 this.maxLifetime = random(120, 250) this.palette = palette if (!this.persistentLayer) { this.persistentLayer = createGraphics(width, height) } this.persistentLayer.clear() for (let i = 0; i < this.maxActivePaths; i++) { this.sparklePaths.push(this.createSparkle()) } } createSparkle() { const angle = random(TWO_PI) const speed = random(1, 3) * FILL_ANIMATION_SPEED_MULT return { x: this.x, y: this.y, vx: cos(angle) * speed, vy: sin(angle) * speed, size: random(10, 50) * _designScale, color: random(this.palette), life: random(0, 80), maxLife: random(60, 180) } } resetSparkle(s) { const angle = random(TWO_PI) const speed = random(1, 3) * FILL_ANIMATION_SPEED_MULT s.x = this.x s.y = this.y s.vx = cos(angle) * speed s.vy = sin(angle) * speed s.size = random(10, 50) * _designScale s.color = random(this.palette) s.life = 0 s.maxLife = random(60, 180) } update() { super.update() this.lifetime++ const stillSpawning = this.lifetime < this.maxLifetime const pl = this.persistentLayer pl.push() pl.noStroke() pl.blendMode(BLEND) const bounds = globalBounds const hasB = !!bounds const bounce = 0.7 for (let i = 0; i < this.sparklePaths.length; i++) { const s = this.sparklePaths[i] pl.fill(s.color) pl.circle(s.x, s.y, s.size) let x = s.x + s.vx let y = s.y + s.vy if (hasB) { if (x < bounds.minX) { x = bounds.minX; s.vx = Math.abs(s.vx) * bounce } else if (x > bounds.maxX) { x = bounds.maxX; s.vx = -Math.abs(s.vx) * bounce } if (y < bounds.minY) { y = bounds.minY; s.vy = Math.abs(s.vy) * bounce } else if (y > bounds.maxY) { y = bounds.maxY; s.vy = -Math.abs(s.vy) * bounce } } s.x = x s.y = y s.life++ if (s.life > s.maxLife) { if (stillSpawning) { this.resetSparkle(s) } } } pl.pop() } display(g) { g.push() const visualOpacity = this.getVisualOpacity() if (visualOpacity < 255) { g.tint(255, visualOpacity) } g.image(this.persistentLayer, 0, 0) g.pop() } getVisualOpacity() { if (this.lifetime < this.maxLifetime) { return 255 } const fadeTime = 150 const timeSinceEnd = this.lifetime - this.maxLifetime return max(0, map(timeSinceEnd, 0, fadeTime, 255, 0)) } isDone() { return this.getVisualOpacity() <= 0 } averageOpacity() { return this.getVisualOpacity() / 255 * 100 } } class VoronoiCells extends FillAnimation { constructor(x, y, palette, traits) { super(x, y, palette, traits) this.cells = [] const cellCount = floor(random(4, 10)) for (let i = 0; i < cellCount; i++) { const angle = random(TWO_PI) const dist = random(20, 80) * _designScale this.cells.push({ x: this.x + cos(angle) * dist, y: this.y + sin(angle) * dist, vx: cos(angle) * random(0.1, 0.4) * FILL_ANIMATION_SPEED_MULT, vy: sin(angle) * random(0.1, 0.4) * FILL_ANIMATION_SPEED_MULT, radius: random(15, 35) * _designScale, targetRadius: random(60, 120) * _designScale, color: random(palette), opacity: 100, blendMode: this.getBlend([DIFFERENCE, ADD, MULTIPLY][i % 3]), growthSpeed: random(0.3, 0.8) * FILL_ANIMATION_SPEED_MULT, maxLife: floor(random(80, 400)), age: 0, selfFading: false }) } } reset(x, y, palette, traits) { super.reset(x, y, palette, traits) this.cells = [] const cellCount = floor(random(4, 10)) for (let i = 0; i < cellCount; i++) { const angle = random(TWO_PI) const dist = random(20, 80) * _designScale this.cells.push({ x: this.x + cos(angle) * dist, y: this.y + sin(angle) * dist, vx: cos(angle) * random(0.1, 0.4) * FILL_ANIMATION_SPEED_MULT, vy: sin(angle) * random(0.1, 0.4) * FILL_ANIMATION_SPEED_MULT, radius: random(15, 35) * _designScale, targetRadius: random(60, 120) * _designScale, color: random(palette), opacity: 100, blendMode: this.getBlend([DIFFERENCE, ADD, MULTIPLY][i % 3]), growthSpeed: random(0.3, 0.8) * FILL_ANIMATION_SPEED_MULT, maxLife: floor(random(80, 400)), age: 0, selfFading: false }) } } update() { super.update() for (let c of this.cells) { c.age++ if (!c.selfFading && c.age > c.maxLife) { c.selfFading = true } if (c.radius < c.targetRadius) { c.radius += c.growthSpeed } let newX = c.x + c.vx let newY = c.y + c.vy const constrained = constrainToMaskBounds(newX, newY, c.vx, c.vy) c.x = constrained.x c.y = constrained.y c.vx = constrained.vx c.vy = constrained.vy const pulse = sin(this.age * 0.05) * 3 c.displayRadius = c.radius + pulse if (c.selfFading) { c.opacity = max(0, c.opacity - 0.6) } } } display(g) { for (let c of this.cells) { if (c.opacity <= 0) continue g.push() g.blendMode(c.blendMode) const col = g.color(c.color) col.setAlpha(c.opacity) g.fill(col) g.noStroke() g.circle(c.x, c.y, c.displayRadius * 2) g.pop() } } isDone() { if (this.cells.length === 0) return true return this.cells.every(c => c.opacity <= 0) } averageOpacity() { if (this.cells.length === 0) return 0 return this.cells.reduce((sum, c) => sum + c.opacity, 0) / this.cells.length } } class AnimationPool { constructor() { this.pools = { Supernova: [], ExpandingCircles: [], ExpandingSquares: [], VerticalStripes: [], HorizontalStripes: [], GradientFill: [], WavyLines: [], Sparkles: [], VoronoiCells: [], Shockwave: [], } this.maxPoolSize = 100 } createNew(type) { switch (type) { case 'Supernova': return new Supernova(0, 0, [], 0, [0, 0], {}) case 'ExpandingCircles': return new ExpandingCircles(0, 0, [], {}) case 'ExpandingSquares': return new ExpandingSquares(0, 0, [], {}) case 'VerticalStripes': return new VerticalStripes(0, 0, [], {}) case 'HorizontalStripes': return new HorizontalStripes(0, 0, [], {}) case 'GradientFill': return new GradientFill(0, 0, [], {}) case 'WavyLines': return new WavyLines(0, 0, [], {}) case 'Sparkles': return new Sparkles(0, 0, [], {}) case 'VoronoiCells': return new VoronoiCells(0, 0, [], {}) case 'Shockwave': return new Shockwave(0, 0, [], {}) default: return null } } acquire(type, x, y, palette, ...extraArgs) { const pool = this.pools[type] let obj = pool.pop() if (!obj) { obj = this.createNew(type) } obj.reset(x, y, palette, ...extraArgs) return obj } release(obj) { const type = obj.constructor.name const pool = this.pools[type] if (pool && pool.length < this.maxPoolSize) { if (obj.persistentLayer) { obj.persistentLayer.clear() } if (obj.trailLayer) { obj.trailLayer.clear() } pool.push(obj) } else { if (obj.persistentLayer) { obj.persistentLayer.remove() obj.persistentLayer = null } if (obj.trailLayer) { obj.trailLayer.remove() obj.trailLayer = null } } } clear() { Object.keys(this.pools).forEach(key => { this.pools[key] = [] }) } } let animationPool let TRAITS, PARAMS let fillAnimations = [] let orbitingElements = [] let starburstRotation = 0 let nextSpawnFrame = 0 let originCenters = [] let tidalAngle = 0 let eyesFlickerStart = 30 let eyesFlickerDuration = 35 let eyesFullyOn = false let regenStartFrame = 0 let staticLayerCache let formPart1Cache let formPart2Cache let maskSilhouetteCache let fillBuffer let fillPersistentLayer // Composites all current fill animations per frame let eyesCache let eyesBlockingCache let omniaEyesBase let orbitBuffer let meltBuffer let corruptBuffer let finalComposite let finalShadedBuffer let asciiShader let clipBuffer // temp buffer for mask clipping compositing let masks = [] let maskBoundsCache = [] let globalBounds = null let isDirty = true function breathWave(t, hz) { const cycleTime = 1.0 / hz const phase = (t % cycleTime) / cycleTime if (phase < 0.65) { const p = phase / 0.65 const sine = Math.sin(p * Math.PI) return Math.pow(sine, 0.7) } else { return 0.0 } } function getBreathTime() { if (_breathStartMillis === null) _breathStartMillis = millis() return (millis() - _breathStartMillis) / 1000.0 } function formBreathOffsetPart2() { const t = getBreathTime() const wave = breathWave(t, FORM_BREATH_HZ) const amp = TRAITS.form === "Armatus" ? FORM_BREATH_AMP * 0.5 : FORM_BREATH_AMP return wave * amp } function formBreathOffsetPart1() { const t = getBreathTime() const delayedT = t - BREATH_DELAY_PART1 const wave = breathWave(delayedT, FORM_BREATH_HZ) const amp = TRAITS.form === "Armatus" ? FORM_BREATH_AMP * 0.5 : FORM_BREATH_AMP return wave * amp } function maskBreathOffset() { const t = getBreathTime() const delayedT = t - BREATH_DELAY_MASK const wave = breathWave(delayedT, FORM_BREATH_HZ) const amp = TRAITS.form === "Armatus" ? FORM_BREATH_AMP * 0.5 : FORM_BREATH_AMP return wave * amp } function getEyeOpacity() { if (eyesFullyOn) return 1.0 const relativeFrame = frameCount - regenStartFrame const flickerEnd = eyesFlickerStart + eyesFlickerDuration if (relativeFrame < eyesFlickerStart) { return 0.0 } else if (relativeFrame >= flickerEnd) { eyesFullyOn = true return 1.0 } else { const flickerProgress = (relativeFrame - eyesFlickerStart) / eyesFlickerDuration const flickerSpeed = map(flickerProgress, 0, 1, 8, 2) const baseOpacity = map(flickerProgress, 0, 1, 0.3, 1.0) const flicker = noise(frameCount * flickerSpeed) > 0.4 ? 1.0 : 0.0 return baseOpacity * flicker } } function calculateMaskBounds() { maskBoundsCache = [] for (const m of masks) { const padding = 20 const bounds = { minX: m.x - m.w / 2 + padding, maxX: m.x + m.w / 2 - padding, minY: m.y - m.h / 2 + padding, maxY: m.y + m.h / 2 - padding, centerX: m.x, centerY: m.y, w: m.w, h: m.h, mask: m } maskBoundsCache.push(bounds) } calculateGlobalBounds() } function calculateGlobalBounds() { if (maskBoundsCache.length === 0) { globalBounds = null return } let minX = Infinity, maxX = -Infinity let minY = Infinity, maxY = -Infinity for (const bounds of maskBoundsCache) { minX = min(minX, bounds.minX) maxX = max(maxX, bounds.maxX) minY = min(minY, bounds.minY) maxY = max(maxY, bounds.maxY) } globalBounds = { minX: minX, maxX: maxX, minY: minY, maxY: maxY, centerX: (minX + maxX) / 2, centerY: (minY + maxY) / 2 } } function getRandomPointInMasks() { if (maskBoundsCache.length === 0) return { x: width / 2, y: height / 2 } const bounds = random(maskBoundsCache) return { x: random(bounds.minX, bounds.maxX), y: random(bounds.minY, bounds.maxY) } } function co