From 9a75b23169c614b6fb6b697befd0e86b5d555c39 Mon Sep 17 00:00:00 2001 From: 0x1d Date: Sun, 12 Oct 2025 10:35:56 +0200 Subject: [PATCH] feat: colors --- presets/aurora-curtains-preset.js | 24 ++++++++--- presets/circuit-pulse-preset.js | 40 +++++++++++++----- presets/fade-green-blue-preset.js | 51 ----------------------- presets/fade-preset.js | 68 +++++++++++++++++++++++++++++++ presets/lava-lamp-preset.js | 30 ++++++++++---- presets/meteor-rain-preset.js | 31 +++++++++----- presets/nebula-drift-preset.js | 31 ++++++++++---- presets/ocean-glimmer-preset.js | 27 ++++++++---- presets/preset-registry.js | 4 +- presets/spiral-bloom-preset.js | 31 ++++++++++---- presets/voxel-fireflies-preset.js | 40 ++++++++++++------ presets/wormhole-tunnel-preset.js | 31 ++++++++++---- 12 files changed, 272 insertions(+), 136 deletions(-) delete mode 100644 presets/fade-green-blue-preset.js create mode 100644 presets/fade-preset.js diff --git a/presets/aurora-curtains-preset.js b/presets/aurora-curtains-preset.js index 999004f..65f7720 100644 --- a/presets/aurora-curtains-preset.js +++ b/presets/aurora-curtains-preset.js @@ -12,6 +12,12 @@ class AuroraCurtainsPreset extends BasePreset { waveSpeed: 0.35, horizontalSway: 0.45, brightness: 1.0, + color1: '01010a', + color2: '041332', + color3: '0c3857', + color4: '1aa07a', + color5: '68d284', + color6: 'f4f5c6', }; } @@ -41,12 +47,12 @@ class AuroraCurtainsPreset extends BasePreset { const timeSeconds = this.frameCount * 0.05; // Convert frame count to time const paletteStops = [ - { stop: 0.0, color: hexToRgb('01010a') }, - { stop: 0.2, color: hexToRgb('041332') }, - { stop: 0.4, color: hexToRgb('0c3857') }, - { stop: 0.65, color: hexToRgb('1aa07a') }, - { stop: 0.85, color: hexToRgb('68d284') }, - { stop: 1.0, color: hexToRgb('f4f5c6') }, + { stop: 0.0, color: hexToRgb(this.getParameter('color1') || '01010a') }, + { stop: 0.2, color: hexToRgb(this.getParameter('color2') || '041332') }, + { stop: 0.4, color: hexToRgb(this.getParameter('color3') || '0c3857') }, + { stop: 0.65, color: hexToRgb(this.getParameter('color4') || '1aa07a') }, + { stop: 0.85, color: hexToRgb(this.getParameter('color5') || '68d284') }, + { stop: 1.0, color: hexToRgb(this.getParameter('color6') || 'f4f5c6') }, ]; for (let row = 0; row < this.height; ++row) { @@ -101,6 +107,12 @@ class AuroraCurtainsPreset extends BasePreset { waveSpeed: { type: 'range', min: 0.1, max: 2.0, step: 0.05, default: 0.35 }, horizontalSway: { type: 'range', min: 0.1, max: 1.0, step: 0.05, default: 0.45 }, brightness: { type: 'range', min: 0.1, max: 1.0, step: 0.1, default: 1.0 }, + color1: { type: 'color', default: '01010a' }, + color2: { type: 'color', default: '041332' }, + color3: { type: 'color', default: '0c3857' }, + color4: { type: 'color', default: '1aa07a' }, + color5: { type: 'color', default: '68d284' }, + color6: { type: 'color', default: 'f4f5c6' }, }, width: this.width, height: this.height, diff --git a/presets/circuit-pulse-preset.js b/presets/circuit-pulse-preset.js index 41ba78b..d04bba0 100644 --- a/presets/circuit-pulse-preset.js +++ b/presets/circuit-pulse-preset.js @@ -8,19 +8,19 @@ class CircuitPulsePreset extends BasePreset { super(width, height); this.paths = []; this.pulses = []; - this.paletteStops = [ - { stop: 0.0, color: hexToRgb('020209') }, - { stop: 0.3, color: hexToRgb('023047') }, - { stop: 0.6, color: hexToRgb('115173') }, - { stop: 0.8, color: hexToRgb('1ca78f') }, - { stop: 1.0, color: hexToRgb('94fdf3') }, - ]; - this.accentColors = ['14f5ff', 'a7ff4d', 'ffcc3f']; this.defaultParameters = { pathFade: 0.85, pulseLength: 6, pulseSpeed: 5.0, pulseCount: 3, + color1: '020209', + color2: '023047', + color3: '115173', + color4: '1ca78f', + color5: '94fdf3', + accentColor1: '14f5ff', + accentColor2: 'a7ff4d', + accentColor3: 'ffcc3f', }; } @@ -65,7 +65,12 @@ class CircuitPulsePreset extends BasePreset { } spawnPulse(pathIndex) { - const color = this.accentColors[pathIndex % this.accentColors.length]; + const accentColors = [ + this.getParameter('accentColor1') || '14f5ff', + this.getParameter('accentColor2') || 'a7ff4d', + this.getParameter('accentColor3') || 'ffcc3f', + ]; + const color = accentColors[pathIndex % accentColors.length]; return { pathIndex, position: 0, @@ -95,6 +100,13 @@ class CircuitPulsePreset extends BasePreset { } const pulseLength = this.getParameter('pulseLength'); + const paletteStops = [ + { stop: 0.0, color: hexToRgb(this.getParameter('color1') || '020209') }, + { stop: 0.3, color: hexToRgb(this.getParameter('color2') || '023047') }, + { stop: 0.6, color: hexToRgb(this.getParameter('color3') || '115173') }, + { stop: 0.8, color: hexToRgb(this.getParameter('color4') || '1ca78f') }, + { stop: 1.0, color: hexToRgb(this.getParameter('color5') || '94fdf3') }, + ]; for (let offset = 0; offset < pulseLength; ++offset) { const index = Math.floor(pulse.position) - offset; @@ -104,7 +116,7 @@ class CircuitPulsePreset extends BasePreset { const { x, y } = path[index]; const intensity = Math.max(0, 1 - offset / pulseLength); - const baseColor = samplePalette(this.paletteStops, intensity); + const baseColor = samplePalette(paletteStops, intensity); this.frame[toIndex(x, y, this.width)] = baseColor; addHexColor(this.frame, toIndex(x, y, this.width), pulse.color, intensity * 1.4); } @@ -142,6 +154,14 @@ class CircuitPulsePreset extends BasePreset { pulseLength: { type: 'range', min: 3, max: 12, step: 1, default: 6 }, pulseSpeed: { type: 'range', min: 2.0, max: 8.0, step: 0.5, default: 5.0 }, pulseCount: { type: 'range', min: 1, max: 6, step: 1, default: 3 }, + color1: { type: 'color', default: '020209' }, + color2: { type: 'color', default: '023047' }, + color3: { type: 'color', default: '115173' }, + color4: { type: 'color', default: '1ca78f' }, + color5: { type: 'color', default: '94fdf3' }, + accentColor1: { type: 'color', default: '14f5ff' }, + accentColor2: { type: 'color', default: 'a7ff4d' }, + accentColor3: { type: 'color', default: 'ffcc3f' }, }, width: this.width, height: this.height, diff --git a/presets/fade-green-blue-preset.js b/presets/fade-green-blue-preset.js deleted file mode 100644 index c19f786..0000000 --- a/presets/fade-green-blue-preset.js +++ /dev/null @@ -1,51 +0,0 @@ -// Fade Green Blue preset for LEDLab - -const BasePreset = require('./base-preset'); -const { createFrame, frameToPayload } = require('./frame-utils'); - -class FadeGreenBluePreset extends BasePreset { - constructor(width = 16, height = 16) { - super(width, height); - this.tick = 0; - this.defaultParameters = { - speed: 0.5, // cycles per second - brightness: 1.0, - }; - } - - renderFrame() { - const frame = createFrame(this.width, this.height); - const timeSeconds = (this.tick * 0.016); // Assume 60 FPS - const phase = timeSeconds * this.getParameter('speed') * Math.PI * 2; - const blend = (Math.sin(phase) + 1) * 0.5; // 0..1 - - const brightness = this.getParameter('brightness') || 1.0; - const green = Math.round(255 * (1 - blend) * brightness); - const blue = Math.round(255 * blend * brightness); - - const gHex = green.toString(16).padStart(2, '0'); - const bHex = blue.toString(16).padStart(2, '0'); - - for (let i = 0; i < frame.length; i++) { - frame[i] = '00' + gHex + bHex; - } - - this.tick++; - return frame; - } - - getMetadata() { - return { - name: 'Fade Green Blue', - description: 'Smooth fade between green and blue colors', - parameters: { - speed: { type: 'range', min: 0.1, max: 2.0, step: 0.1, default: 0.5 }, - brightness: { type: 'range', min: 0.1, max: 1.0, step: 0.1, default: 1.0 }, - }, - width: this.width, - height: this.height, - }; - } -} - -module.exports = FadeGreenBluePreset; diff --git a/presets/fade-preset.js b/presets/fade-preset.js new file mode 100644 index 0000000..04fc2b9 --- /dev/null +++ b/presets/fade-preset.js @@ -0,0 +1,68 @@ +// Fade preset for LEDLab + +const BasePreset = require('./base-preset'); +const { createFrame, frameToPayload } = require('./frame-utils'); + +class FadePreset extends BasePreset { + constructor(width = 16, height = 16) { + super(width, height); + this.tick = 0; + this.defaultParameters = { + speed: 0.5, // cycles per second + brightness: 1.0, + color1: '00ff00', // Green + color2: '0000ff', // Blue + }; + } + + hexToRgb(hex) { + const r = parseInt(hex.slice(0, 2), 16); + const g = parseInt(hex.slice(2, 4), 16); + const b = parseInt(hex.slice(4, 6), 16); + return { r, g, b }; + } + + renderFrame() { + const frame = createFrame(this.width, this.height); + const timeSeconds = (this.tick * 0.016); // Assume 60 FPS + const phase = timeSeconds * this.getParameter('speed') * Math.PI * 2; + const blend = (Math.sin(phase) + 1) * 0.5; // 0..1 + + const brightness = this.getParameter('brightness') || 1.0; + const color1 = this.hexToRgb(this.getParameter('color1') || '00ff00'); + const color2 = this.hexToRgb(this.getParameter('color2') || '0000ff'); + + const r = Math.round((color1.r * (1 - blend) + color2.r * blend) * brightness); + const g = Math.round((color1.g * (1 - blend) + color2.g * blend) * brightness); + const b = Math.round((color1.b * (1 - blend) + color2.b * blend) * brightness); + + const rHex = r.toString(16).padStart(2, '0'); + const gHex = g.toString(16).padStart(2, '0'); + const bHex = b.toString(16).padStart(2, '0'); + + for (let i = 0; i < frame.length; i++) { + frame[i] = rHex + gHex + bHex; + } + + this.tick++; + return frame; + } + + getMetadata() { + return { + name: 'Fade', + description: 'Smooth fade between two colors', + parameters: { + speed: { type: 'range', min: 0.1, max: 2.0, step: 0.1, default: 0.5 }, + brightness: { type: 'range', min: 0.1, max: 1.0, step: 0.1, default: 1.0 }, + color1: { type: 'color', default: '00ff00' }, + color2: { type: 'color', default: '0000ff' }, + }, + width: this.width, + height: this.height, + }; + } +} + +module.exports = FadePreset; + diff --git a/presets/lava-lamp-preset.js b/presets/lava-lamp-preset.js index cc128d4..3690d6a 100644 --- a/presets/lava-lamp-preset.js +++ b/presets/lava-lamp-preset.js @@ -7,20 +7,18 @@ class LavaLampPreset extends BasePreset { constructor(width = 16, height = 16) { super(width, height); this.blobs = []; - this.paletteStops = [ - { stop: 0.0, color: hexToRgb('050319') }, - { stop: 0.28, color: hexToRgb('2a0c4f') }, - { stop: 0.55, color: hexToRgb('8f1f73') }, - { stop: 0.75, color: hexToRgb('ff4a22') }, - { stop: 0.9, color: hexToRgb('ff9333') }, - { stop: 1.0, color: hexToRgb('fff7b0') }, - ]; this.defaultParameters = { blobCount: 6, blobSpeed: 0.18, minBlobRadius: 0.18, maxBlobRadius: 0.38, intensity: 1.0, + color1: '050319', + color2: '2a0c4f', + color3: '8f1f73', + color4: 'ff4a22', + color5: 'ff9333', + color6: 'fff7b0', }; } @@ -85,11 +83,19 @@ class LavaLampPreset extends BasePreset { this.updateBlobs(0.016); // Assume 60 FPS const frame = createFrame(this.width, this.height); + const paletteStops = [ + { stop: 0.0, color: hexToRgb(this.getParameter('color1') || '050319') }, + { stop: 0.28, color: hexToRgb(this.getParameter('color2') || '2a0c4f') }, + { stop: 0.55, color: hexToRgb(this.getParameter('color3') || '8f1f73') }, + { stop: 0.75, color: hexToRgb(this.getParameter('color4') || 'ff4a22') }, + { stop: 0.9, color: hexToRgb(this.getParameter('color5') || 'ff9333') }, + { stop: 1.0, color: hexToRgb(this.getParameter('color6') || 'fff7b0') }, + ]; for (let row = 0; row < this.height; ++row) { for (let col = 0; col < this.width; ++col) { const energy = this.calculateEnergyAt(col, row); - const color = samplePalette(this.paletteStops, energy); + const color = samplePalette(paletteStops, energy); frame[toIndex(col, row, this.width)] = color; } } @@ -136,6 +142,12 @@ class LavaLampPreset extends BasePreset { minBlobRadius: { type: 'range', min: 0.1, max: 0.3, step: 0.02, default: 0.18 }, maxBlobRadius: { type: 'range', min: 0.2, max: 0.5, step: 0.02, default: 0.38 }, intensity: { type: 'range', min: 0.5, max: 2.0, step: 0.1, default: 1.0 }, + color1: { type: 'color', default: '050319' }, + color2: { type: 'color', default: '2a0c4f' }, + color3: { type: 'color', default: '8f1f73' }, + color4: { type: 'color', default: 'ff4a22' }, + color5: { type: 'color', default: 'ff9333' }, + color6: { type: 'color', default: 'fff7b0' }, }, width: this.width, height: this.height, diff --git a/presets/meteor-rain-preset.js b/presets/meteor-rain-preset.js index 485d0d4..66c08bf 100644 --- a/presets/meteor-rain-preset.js +++ b/presets/meteor-rain-preset.js @@ -7,17 +7,15 @@ class MeteorRainPreset extends BasePreset { constructor(width = 16, height = 16) { super(width, height); this.meteors = []; - this.paletteStops = [ - { stop: 0.0, color: hexToRgb('0a0126') }, - { stop: 0.3, color: hexToRgb('123d8b') }, - { stop: 0.7, color: hexToRgb('21c7d9') }, - { stop: 1.0, color: hexToRgb('f7ffff') }, - ]; this.defaultParameters = { meteorCount: 12, minSpeed: 4, maxSpeed: 10, trailDecay: 0.76, + color1: '0a0126', + color2: '123d8b', + color3: '21c7d9', + color4: 'f7ffff', }; } @@ -45,7 +43,7 @@ class MeteorRainPreset extends BasePreset { }; } - drawMeteor(meteor) { + drawMeteor(meteor, paletteStops) { const col = Math.round(meteor.x); const row = Math.round(meteor.y); if (col < 0 || col >= this.width || row < 0 || row >= this.height) { @@ -53,15 +51,15 @@ class MeteorRainPreset extends BasePreset { } const energy = clamp(1.2 - Math.random() * 0.2, 0, 1); - this.frame[toIndex(col, row, this.width)] = samplePalette(this.paletteStops, energy); + this.frame[toIndex(col, row, this.width)] = samplePalette(paletteStops, energy); } - updateMeteors(deltaSeconds) { + updateMeteors(deltaSeconds, paletteStops) { this.meteors.forEach((meteor, index) => { meteor.x += meteor.vx * deltaSeconds; meteor.y += meteor.vy * deltaSeconds; - this.drawMeteor(meteor); + this.drawMeteor(meteor, paletteStops); if (meteor.x > this.width + 1 || meteor.y > this.height + 1) { this.meteors[index] = this.spawnMeteor(this.width, this.height); @@ -74,6 +72,13 @@ class MeteorRainPreset extends BasePreset { const trailDecay = this.getParameter('trailDecay') || 0.76; const meteorCount = this.getParameter('meteorCount') || 12; + const paletteStops = [ + { stop: 0.0, color: hexToRgb(this.getParameter('color1') || '0a0126') }, + { stop: 0.3, color: hexToRgb(this.getParameter('color2') || '123d8b') }, + { stop: 0.7, color: hexToRgb(this.getParameter('color3') || '21c7d9') }, + { stop: 1.0, color: hexToRgb(this.getParameter('color4') || 'f7ffff') }, + ]; + fadeFrame(this.frame, trailDecay); // Update meteor count if it changed @@ -84,7 +89,7 @@ class MeteorRainPreset extends BasePreset { this.meteors.pop(); } - this.updateMeteors(0.016); // Assume 60 FPS + this.updateMeteors(0.016, paletteStops); // Assume 60 FPS return this.frame; } @@ -98,6 +103,10 @@ class MeteorRainPreset extends BasePreset { minSpeed: { type: 'range', min: 2, max: 8, step: 1, default: 4 }, maxSpeed: { type: 'range', min: 6, max: 15, step: 1, default: 10 }, trailDecay: { type: 'range', min: 0.6, max: 0.9, step: 0.02, default: 0.76 }, + color1: { type: 'color', default: '0a0126' }, + color2: { type: 'color', default: '123d8b' }, + color3: { type: 'color', default: '21c7d9' }, + color4: { type: 'color', default: 'f7ffff' }, }, width: this.width, height: this.height, diff --git a/presets/nebula-drift-preset.js b/presets/nebula-drift-preset.js index f9b694f..1f21f47 100644 --- a/presets/nebula-drift-preset.js +++ b/presets/nebula-drift-preset.js @@ -7,19 +7,17 @@ class NebulaDriftPreset extends BasePreset { constructor(width = 16, height = 16) { super(width, height); this.timeSeconds = 0; - this.paletteStops = [ - { stop: 0.0, color: hexToRgb('100406') }, - { stop: 0.25, color: hexToRgb('2e0f1f') }, - { stop: 0.5, color: hexToRgb('6a1731') }, - { stop: 0.7, color: hexToRgb('b63b32') }, - { stop: 0.85, color: hexToRgb('f48b2a') }, - { stop: 1.0, color: hexToRgb('ffe9b0') }, - ]; this.defaultParameters = { primarySpeed: 0.15, secondarySpeed: 0.32, waveScale: 0.75, brightness: 1.0, + color1: '100406', + color2: '2e0f1f', + color3: '6a1731', + color4: 'b63b32', + color5: 'f48b2a', + color6: 'ffe9b0', }; } @@ -33,6 +31,15 @@ class NebulaDriftPreset extends BasePreset { const frame = createFrame(this.width, this.height); const brightness = this.getParameter('brightness') || 1.0; + const paletteStops = [ + { stop: 0.0, color: hexToRgb(this.getParameter('color1') || '100406') }, + { stop: 0.25, color: hexToRgb(this.getParameter('color2') || '2e0f1f') }, + { stop: 0.5, color: hexToRgb(this.getParameter('color3') || '6a1731') }, + { stop: 0.7, color: hexToRgb(this.getParameter('color4') || 'b63b32') }, + { stop: 0.85, color: hexToRgb(this.getParameter('color5') || 'f48b2a') }, + { stop: 1.0, color: hexToRgb(this.getParameter('color6') || 'ffe9b0') }, + ]; + for (let row = 0; row < this.height; ++row) { const v = row / Math.max(1, this.height - 1); for (let col = 0; col < this.width; ++col) { @@ -45,7 +52,7 @@ class NebulaDriftPreset extends BasePreset { const envelope = Math.sin((u * v) * Math.PI * 2 + this.timeSeconds * 0.1) * 0.25 + 0.75; const value = clamp((combined * 0.5 + 0.5) * envelope, 0, 1); - let color = samplePalette(this.paletteStops, value); + let color = samplePalette(paletteStops, value); // Apply brightness if (brightness < 1.0) { @@ -71,6 +78,12 @@ class NebulaDriftPreset extends BasePreset { secondarySpeed: { type: 'range', min: 0.2, max: 0.5, step: 0.02, default: 0.32 }, waveScale: { type: 'range', min: 0.5, max: 1.0, step: 0.05, default: 0.75 }, brightness: { type: 'range', min: 0.3, max: 1.0, step: 0.1, default: 1.0 }, + color1: { type: 'color', default: '100406' }, + color2: { type: 'color', default: '2e0f1f' }, + color3: { type: 'color', default: '6a1731' }, + color4: { type: 'color', default: 'b63b32' }, + color5: { type: 'color', default: 'f48b2a' }, + color6: { type: 'color', default: 'ffe9b0' }, }, width: this.width, height: this.height, diff --git a/presets/ocean-glimmer-preset.js b/presets/ocean-glimmer-preset.js index 1e00e7f..e81c3a5 100644 --- a/presets/ocean-glimmer-preset.js +++ b/presets/ocean-glimmer-preset.js @@ -7,19 +7,17 @@ class OceanGlimmerPreset extends BasePreset { constructor(width = 16, height = 16) { super(width, height); this.timeSeconds = 0; - this.paletteStops = [ - { stop: 0.0, color: hexToRgb('031521') }, - { stop: 0.35, color: hexToRgb('024f6d') }, - { stop: 0.65, color: hexToRgb('13a4a1') }, - { stop: 0.85, color: hexToRgb('67dcd0') }, - { stop: 1.0, color: hexToRgb('fcdba4') }, - ]; this.defaultParameters = { shimmer: 0.08, waveSpeed1: 1.2, waveSpeed2: 0.9, waveSpeed3: 0.5, brightness: 1.0, + color1: '031521', + color2: '024f6d', + color3: '13a4a1', + color4: '67dcd0', + color5: 'fcdba4', }; } @@ -30,6 +28,14 @@ class OceanGlimmerPreset extends BasePreset { const shimmer = this.getParameter('shimmer') || 0.08; const brightness = this.getParameter('brightness') || 1.0; + const paletteStops = [ + { stop: 0.0, color: hexToRgb(this.getParameter('color1') || '031521') }, + { stop: 0.35, color: hexToRgb(this.getParameter('color2') || '024f6d') }, + { stop: 0.65, color: hexToRgb(this.getParameter('color3') || '13a4a1') }, + { stop: 0.85, color: hexToRgb(this.getParameter('color4') || '67dcd0') }, + { stop: 1.0, color: hexToRgb(this.getParameter('color5') || 'fcdba4') }, + ]; + for (let row = 0; row < this.height; ++row) { const v = row / Math.max(1, this.height - 1); for (let col = 0; col < this.width; ++col) { @@ -42,7 +48,7 @@ class OceanGlimmerPreset extends BasePreset { const noise = (Math.random() - 0.5) * shimmer; const value = clamp(base + noise, 0, 1); - let color = samplePalette(this.paletteStops, value); + let color = samplePalette(paletteStops, value); // Apply brightness if (brightness < 1.0) { @@ -69,6 +75,11 @@ class OceanGlimmerPreset extends BasePreset { waveSpeed2: { type: 'range', min: 0.5, max: 2.0, step: 0.1, default: 0.9 }, waveSpeed3: { type: 'range', min: 0.2, max: 1.0, step: 0.1, default: 0.5 }, brightness: { type: 'range', min: 0.3, max: 1.0, step: 0.1, default: 1.0 }, + color1: { type: 'color', default: '031521' }, + color2: { type: 'color', default: '024f6d' }, + color3: { type: 'color', default: '13a4a1' }, + color4: { type: 'color', default: '67dcd0' }, + color5: { type: 'color', default: 'fcdba4' }, }, width: this.width, height: this.height, diff --git a/presets/preset-registry.js b/presets/preset-registry.js index 43527ae..92ce45f 100644 --- a/presets/preset-registry.js +++ b/presets/preset-registry.js @@ -4,7 +4,7 @@ const RainbowPreset = require('./rainbow-preset'); const AuroraCurtainsPreset = require('./aurora-curtains-preset'); const BouncingBallPreset = require('./bouncing-ball-preset'); const CircuitPulsePreset = require('./circuit-pulse-preset'); -const FadeGreenBluePreset = require('./fade-green-blue-preset'); +const FadePreset = require('./fade-preset'); const LavaLampPreset = require('./lava-lamp-preset'); const MeteorRainPreset = require('./meteor-rain-preset'); const NebulaDriftPreset = require('./nebula-drift-preset'); @@ -24,7 +24,7 @@ class PresetRegistry { this.register('aurora-curtains', AuroraCurtainsPreset); this.register('bouncing-ball', BouncingBallPreset); this.register('circuit-pulse', CircuitPulsePreset); - this.register('fade-green-blue', FadeGreenBluePreset); + this.register('fade', FadePreset); this.register('lava-lamp', LavaLampPreset); this.register('meteor-rain', MeteorRainPreset); this.register('nebula-drift', NebulaDriftPreset); diff --git a/presets/spiral-bloom-preset.js b/presets/spiral-bloom-preset.js index 7f37689..d8b9a99 100644 --- a/presets/spiral-bloom-preset.js +++ b/presets/spiral-bloom-preset.js @@ -8,19 +8,17 @@ class SpiralBloomPreset extends BasePreset { super(width, height); this.rotation = 0; this.hueShift = 0; - this.paletteStops = [ - { stop: 0.0, color: hexToRgb('051923') }, - { stop: 0.2, color: hexToRgb('0c4057') }, - { stop: 0.45, color: hexToRgb('1d7a70') }, - { stop: 0.7, color: hexToRgb('39b15f') }, - { stop: 0.88, color: hexToRgb('9dd54c') }, - { stop: 1.0, color: hexToRgb('f7f5bc') }, - ]; this.defaultParameters = { rotationSpeed: 0.7, hueSpeed: 0.2, spiralArms: 5, brightness: 1.0, + color1: '051923', + color2: '0c4057', + color3: '1d7a70', + color4: '39b15f', + color5: '9dd54c', + color6: 'f7f5bc', }; } @@ -32,6 +30,15 @@ class SpiralBloomPreset extends BasePreset { const brightness = this.getParameter('brightness') || 1.0; const spiralArms = this.getParameter('spiralArms') || 5; + const paletteStops = [ + { stop: 0.0, color: hexToRgb(this.getParameter('color1') || '051923') }, + { stop: 0.2, color: hexToRgb(this.getParameter('color2') || '0c4057') }, + { stop: 0.45, color: hexToRgb(this.getParameter('color3') || '1d7a70') }, + { stop: 0.7, color: hexToRgb(this.getParameter('color4') || '39b15f') }, + { stop: 0.88, color: hexToRgb(this.getParameter('color5') || '9dd54c') }, + { stop: 1.0, color: hexToRgb(this.getParameter('color6') || 'f7f5bc') }, + ]; + const cx = (this.width - 1) / 2; const cy = (this.height - 1) / 2; const radiusNorm = Math.hypot(cx, cy) || 1; @@ -45,7 +52,7 @@ class SpiralBloomPreset extends BasePreset { const arm = 0.5 + 0.5 * Math.sin(spiralArms * (angle + this.rotation) + this.hueShift * Math.PI * 2); const value = Math.min(1, radius * 0.8 + arm * 0.4); - let color = samplePalette(this.paletteStops, value); + let color = samplePalette(paletteStops, value); // Apply brightness if (brightness < 1.0) { @@ -71,6 +78,12 @@ class SpiralBloomPreset extends BasePreset { hueSpeed: { type: 'range', min: 0.1, max: 0.5, step: 0.05, default: 0.2 }, spiralArms: { type: 'range', min: 3, max: 8, step: 1, default: 5 }, brightness: { type: 'range', min: 0.3, max: 1.0, step: 0.1, default: 1.0 }, + color1: { type: 'color', default: '051923' }, + color2: { type: 'color', default: '0c4057' }, + color3: { type: 'color', default: '1d7a70' }, + color4: { type: 'color', default: '39b15f' }, + color5: { type: 'color', default: '9dd54c' }, + color6: { type: 'color', default: 'f7f5bc' }, }, width: this.width, height: this.height, diff --git a/presets/voxel-fireflies-preset.js b/presets/voxel-fireflies-preset.js index 5eba08e..7b1d066 100644 --- a/presets/voxel-fireflies-preset.js +++ b/presets/voxel-fireflies-preset.js @@ -7,20 +7,19 @@ class VoxelFirefliesPreset extends BasePreset { constructor(width = 16, height = 16) { super(width, height); this.fireflies = []; - this.paletteStops = [ - { stop: 0.0, color: hexToRgb('02030a') }, - { stop: 0.2, color: hexToRgb('031c2d') }, - { stop: 0.4, color: hexToRgb('053d4a') }, - { stop: 0.6, color: hexToRgb('107b68') }, - { stop: 0.8, color: hexToRgb('14c491') }, - { stop: 1.0, color: hexToRgb('f2ffd2') }, - ]; this.defaultParameters = { fireflyCount: 18, hoverSpeed: 0.6, glowSpeed: 1.8, trailDecay: 0.8, brightness: 1.0, + color1: '02030a', + color2: '031c2d', + color3: '053d4a', + color4: '107b68', + color5: '14c491', + color6: 'f2ffd2', + accentColor: 'ffd966', }; } @@ -69,10 +68,11 @@ class VoxelFirefliesPreset extends BasePreset { firefly.glowPhase += deltaSeconds * (this.getParameter('glowSpeed') || 1.8) * (0.7 + Math.random() * 0.6); } - drawFirefly(firefly) { + drawFirefly(firefly, paletteStops) { const baseGlow = (Math.sin(firefly.glowPhase) + 1) * 0.5; const col = Math.round(firefly.x); const row = Math.round(firefly.y); + const accentColor = this.getParameter('accentColor') || 'ffd966'; for (let dy = -1; dy <= 1; ++dy) { for (let dx = -1; dx <= 1; ++dx) { @@ -89,9 +89,9 @@ class VoxelFirefliesPreset extends BasePreset { continue; } - this.frame[toIndex(sampleX, sampleY, this.width)] = samplePalette(this.paletteStops, intensity); + this.frame[toIndex(sampleX, sampleY, this.width)] = samplePalette(paletteStops, intensity); if (distance === 0) { - addHexColor(this.frame, toIndex(sampleX, sampleY, this.width), 'ffd966', intensity * 1.6); + addHexColor(this.frame, toIndex(sampleX, sampleY, this.width), accentColor, intensity * 1.6); } } } @@ -103,6 +103,15 @@ class VoxelFirefliesPreset extends BasePreset { const fireflyCount = this.getParameter('fireflyCount') || 18; const brightness = this.getParameter('brightness') || 1.0; + const paletteStops = [ + { stop: 0.0, color: hexToRgb(this.getParameter('color1') || '02030a') }, + { stop: 0.2, color: hexToRgb(this.getParameter('color2') || '031c2d') }, + { stop: 0.4, color: hexToRgb(this.getParameter('color3') || '053d4a') }, + { stop: 0.6, color: hexToRgb(this.getParameter('color4') || '107b68') }, + { stop: 0.8, color: hexToRgb(this.getParameter('color5') || '14c491') }, + { stop: 1.0, color: hexToRgb(this.getParameter('color6') || 'f2ffd2') }, + ]; + fadeFrame(this.frame, trailDecay); // Update firefly count if it changed @@ -115,7 +124,7 @@ class VoxelFirefliesPreset extends BasePreset { this.fireflies.forEach((firefly) => { this.updateFirefly(firefly, 0.016); // Assume 60 FPS - this.drawFirefly(firefly); + this.drawFirefly(firefly, paletteStops); }); return this.frame; @@ -131,6 +140,13 @@ class VoxelFirefliesPreset extends BasePreset { glowSpeed: { type: 'range', min: 1.0, max: 3.0, step: 0.2, default: 1.8 }, trailDecay: { type: 'range', min: 0.7, max: 0.95, step: 0.05, default: 0.8 }, brightness: { type: 'range', min: 0.5, max: 1.5, step: 0.1, default: 1.0 }, + color1: { type: 'color', default: '02030a' }, + color2: { type: 'color', default: '031c2d' }, + color3: { type: 'color', default: '053d4a' }, + color4: { type: 'color', default: '107b68' }, + color5: { type: 'color', default: '14c491' }, + color6: { type: 'color', default: 'f2ffd2' }, + accentColor: { type: 'color', default: 'ffd966' }, }, width: this.width, height: this.height, diff --git a/presets/wormhole-tunnel-preset.js b/presets/wormhole-tunnel-preset.js index 5463db2..2170196 100644 --- a/presets/wormhole-tunnel-preset.js +++ b/presets/wormhole-tunnel-preset.js @@ -7,14 +7,6 @@ class WormholeTunnelPreset extends BasePreset { constructor(width = 16, height = 16) { super(width, height); this.timeSeconds = 0; - this.paletteStops = [ - { stop: 0.0, color: hexToRgb('010005') }, - { stop: 0.2, color: hexToRgb('07204f') }, - { stop: 0.45, color: hexToRgb('124aa0') }, - { stop: 0.7, color: hexToRgb('36a5ff') }, - { stop: 0.87, color: hexToRgb('99e6ff') }, - { stop: 1.0, color: hexToRgb('f1fbff') }, - ]; this.defaultParameters = { ringDensity: 8, ringSpeed: 1.4, @@ -23,6 +15,12 @@ class WormholeTunnelPreset extends BasePreset { twistSpeed: 0.9, coreExponent: 1.6, brightness: 1.0, + color1: '010005', + color2: '07204f', + color3: '124aa0', + color4: '36a5ff', + color5: '99e6ff', + color6: 'f1fbff', }; } @@ -32,6 +30,15 @@ class WormholeTunnelPreset extends BasePreset { const frame = createFrame(this.width, this.height); const brightness = this.getParameter('brightness') || 1.0; + const paletteStops = [ + { stop: 0.0, color: hexToRgb(this.getParameter('color1') || '010005') }, + { stop: 0.2, color: hexToRgb(this.getParameter('color2') || '07204f') }, + { stop: 0.45, color: hexToRgb(this.getParameter('color3') || '124aa0') }, + { stop: 0.7, color: hexToRgb(this.getParameter('color4') || '36a5ff') }, + { stop: 0.87, color: hexToRgb(this.getParameter('color5') || '99e6ff') }, + { stop: 1.0, color: hexToRgb(this.getParameter('color6') || 'f1fbff') }, + ]; + const cx = (this.width - 1) / 2; const cy = (this.height - 1) / 2; const radiusNorm = Math.hypot(cx, cy) || 1; @@ -51,7 +58,7 @@ class WormholeTunnelPreset extends BasePreset { const value = clamp(ring * 0.6 + depth * 0.3 + twist * 0.1, 0, 1); - let color = samplePalette(this.paletteStops, value); + let color = samplePalette(paletteStops, value); // Apply brightness if (brightness < 1.0) { @@ -80,6 +87,12 @@ class WormholeTunnelPreset extends BasePreset { twistSpeed: { type: 'range', min: 0.3, max: 1.5, step: 0.1, default: 0.9 }, coreExponent: { type: 'range', min: 1.0, max: 2.5, step: 0.1, default: 1.6 }, brightness: { type: 'range', min: 0.3, max: 1.0, step: 0.1, default: 1.0 }, + color1: { type: 'color', default: '010005' }, + color2: { type: 'color', default: '07204f' }, + color3: { type: 'color', default: '124aa0' }, + color4: { type: 'color', default: '36a5ff' }, + color5: { type: 'color', default: '99e6ff' }, + color6: { type: 'color', default: 'f1fbff' }, }, width: this.width, height: this.height,