- Fix absolute imports in shaders/ to use relative paths - Correct indentation in shader files - Refactor video_comparer.py and nodes/video_comparer.py - Add new example workflows - Update gitignore and package.json
951 lines
47 KiB
JavaScript
951 lines
47 KiB
JavaScript
/**
|
|
* noise_visualizer.ts - Noise pattern visualization for ComfyUI modal
|
|
*/
|
|
// ============================
|
|
// Implementation
|
|
// ============================
|
|
if (!window.NoiseVisualizer) {
|
|
const NoiseVisualizer = {
|
|
kofiCupImageBitmap: null,
|
|
kofiImageLoaded: false,
|
|
kofiImageLoadAttempted: false,
|
|
_preloadKofiImage: async function () {
|
|
if (this.kofiImageLoadAttempted)
|
|
return;
|
|
this.kofiImageLoadAttempted = true;
|
|
const absolutePath = '/extensions/comfyui-shadernoiseksampler/images/kofi_symbol.svg';
|
|
// Attempt 1: Direct Image load
|
|
try {
|
|
console.log('Attempting Ko-fi SVG load using new Image() with direct path (Primary Attempt)...');
|
|
const img = new Image();
|
|
await new Promise((resolve, reject) => {
|
|
img.onload = () => {
|
|
this.kofiCupImageBitmap = img;
|
|
this.kofiImageLoaded = true;
|
|
console.log('Ko-fi symbol SVG loaded via Image() successfully (Primary).');
|
|
resolve();
|
|
};
|
|
img.onerror = (e) => {
|
|
console.warn('Primary Ko-fi symbol SVG load via Image() failed. Proceeding to secondary attempt.', e);
|
|
reject(e);
|
|
};
|
|
img.src = absolutePath;
|
|
});
|
|
return;
|
|
}
|
|
catch {
|
|
this.kofiImageLoaded = false;
|
|
this.kofiCupImageBitmap = null;
|
|
}
|
|
// Attempt 2: Fetch -> Blob -> Intermediate Image -> ImageBitmap
|
|
let objectURL = null;
|
|
try {
|
|
console.log('Attempting Ko-fi SVG load via Blob -> Image -> ImageBitmap (Secondary Attempt)...');
|
|
const response = await fetch(absolutePath);
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status} for ${absolutePath}`);
|
|
}
|
|
const svgText = await response.text();
|
|
const blob = new Blob([svgText], { type: 'image/svg+xml' });
|
|
objectURL = URL.createObjectURL(blob);
|
|
const intermediateImg = new Image();
|
|
await new Promise((resolve, reject) => {
|
|
intermediateImg.onload = () => resolve();
|
|
intermediateImg.onerror = () => reject(new Error('Intermediate Image() load failed for SVG blob (Secondary).'));
|
|
intermediateImg.src = objectURL;
|
|
});
|
|
this.kofiCupImageBitmap = await createImageBitmap(intermediateImg);
|
|
this.kofiImageLoaded = true;
|
|
console.log('Ko-fi symbol SVG processed to ImageBitmap successfully (Secondary).');
|
|
}
|
|
catch (error) {
|
|
this.kofiImageLoaded = false;
|
|
this.kofiCupImageBitmap = null;
|
|
console.error('All Ko-fi SVG load attempts failed: ', error, '. Will use fallback drawing.');
|
|
}
|
|
finally {
|
|
if (objectURL) {
|
|
URL.revokeObjectURL(objectURL);
|
|
}
|
|
}
|
|
},
|
|
renderAllInModal: async function (modalContentElement) {
|
|
if (!this.kofiImageLoadAttempted) {
|
|
await this._preloadKofiImage();
|
|
}
|
|
const noiseCanvases = modalContentElement.querySelectorAll('.noise-canvas');
|
|
noiseCanvases.forEach(canvasDiv => {
|
|
const canvasId = canvasDiv.id;
|
|
if (!canvasId || !canvasId.startsWith('noise-canvas-'))
|
|
return;
|
|
const noiseType = canvasId.substring('noise-canvas-'.length);
|
|
const noiseName = noiseType.split('_').map(word => {
|
|
if (word.toLowerCase() === 'fbm')
|
|
return 'FBM';
|
|
if (word.toLowerCase() === '3d')
|
|
return '3D';
|
|
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
}).join(' ');
|
|
let canvas = canvasDiv.querySelector('canvas');
|
|
if (!canvas) {
|
|
canvas = document.createElement('canvas');
|
|
canvas.width = 130;
|
|
canvas.height = 130;
|
|
canvas.setAttribute('role', 'img');
|
|
canvas.setAttribute('aria-label', `Visualization of ${noiseName} noise pattern`);
|
|
canvasDiv.innerHTML = '';
|
|
canvasDiv.appendChild(canvas);
|
|
}
|
|
const functionNameSuffix = noiseType.split('_').map(word => {
|
|
if (word.toLowerCase() === 'fbm')
|
|
return 'FBM';
|
|
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
}).join('');
|
|
const renderFunctionName = `render${functionNameSuffix.replace('3d', '3D')}`;
|
|
const renderMethod = this[renderFunctionName];
|
|
if (typeof renderMethod === 'function') {
|
|
renderMethod.call(this, canvas);
|
|
}
|
|
else {
|
|
console.warn('No renderer function found:', renderFunctionName, 'for noise type:', noiseType);
|
|
this.renderPlaceholder(canvas, noiseName);
|
|
}
|
|
});
|
|
// Mask canvases
|
|
const maskCanvases = modalContentElement.querySelectorAll('.mask-canvas');
|
|
maskCanvases.forEach(canvasDiv => {
|
|
const canvasId = canvasDiv.id;
|
|
if (!canvasId || !canvasId.startsWith('mask-canvas-'))
|
|
return;
|
|
const maskType = canvasId.substring('mask-canvas-'.length);
|
|
const maskName = maskType.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
|
|
let canvas = canvasDiv.querySelector('canvas');
|
|
if (!canvas) {
|
|
canvas = document.createElement('canvas');
|
|
canvas.width = 100;
|
|
canvas.height = 70;
|
|
canvas.setAttribute('role', 'img');
|
|
canvas.setAttribute('aria-label', `Visualization of ${maskName} shape mask`);
|
|
canvasDiv.innerHTML = '';
|
|
canvasDiv.appendChild(canvas);
|
|
}
|
|
const functionNameSuffix = maskType.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join('');
|
|
const renderFunctionName = `renderMask${functionNameSuffix}`;
|
|
const renderMethod = this[renderFunctionName];
|
|
if (typeof renderMethod === 'function') {
|
|
renderMethod.call(this, canvas);
|
|
}
|
|
else {
|
|
console.warn('No renderer function found:', renderFunctionName, 'for mask type:', maskType);
|
|
this.renderMaskPlaceholder(canvas, maskName);
|
|
}
|
|
});
|
|
// Color scheme swatches
|
|
const colorSwatches = modalContentElement.querySelectorAll('.color-swatch');
|
|
colorSwatches.forEach(swatchDiv => {
|
|
const element = swatchDiv;
|
|
if (element.style.background)
|
|
return;
|
|
const schemeName = element.textContent?.trim().toLowerCase() ?? '';
|
|
const renderFunctionName = `renderColor${schemeName.charAt(0).toUpperCase() + schemeName.slice(1)}`;
|
|
const renderMethod = this[renderFunctionName];
|
|
if (typeof renderMethod === 'function') {
|
|
renderMethod.call(this, element);
|
|
}
|
|
else {
|
|
console.log('Using existing style for color scheme:', schemeName);
|
|
}
|
|
});
|
|
// Temporal animation demo
|
|
const animationDemoContainer = modalContentElement.querySelector('#animation-demo-placeholder');
|
|
if (animationDemoContainer) {
|
|
let canvas = animationDemoContainer.querySelector('canvas');
|
|
if (!canvas) {
|
|
canvas = document.createElement('canvas');
|
|
const containerStyle = getComputedStyle(animationDemoContainer);
|
|
canvas.width = parseInt(containerStyle.width) || 300;
|
|
canvas.height = parseInt(containerStyle.height) || 200;
|
|
canvas.setAttribute('role', 'img');
|
|
canvas.setAttribute('aria-label', 'Interactive animation demonstrating temporal coherence');
|
|
animationDemoContainer.innerHTML = '';
|
|
animationDemoContainer.appendChild(canvas);
|
|
animationDemoContainer.style.display = 'block';
|
|
}
|
|
if (typeof this.renderTemporalAnimation === 'function') {
|
|
this.renderTemporalAnimation(canvas);
|
|
}
|
|
else {
|
|
console.warn('renderTemporalAnimation function not found in NoiseVisualizer.');
|
|
this.renderMaskPlaceholder(canvas, 'Animation Demo');
|
|
}
|
|
}
|
|
// Intro noise demo
|
|
const introNoiseDemoContainer = modalContentElement.querySelector('#intro-noise-demo');
|
|
if (introNoiseDemoContainer) {
|
|
let canvas = introNoiseDemoContainer.querySelector('canvas');
|
|
if (!canvas) {
|
|
canvas = document.createElement('canvas');
|
|
const containerStyle = getComputedStyle(introNoiseDemoContainer);
|
|
canvas.width = parseInt(containerStyle.width) || 300;
|
|
canvas.height = parseInt(containerStyle.height) || 350;
|
|
canvas.setAttribute('role', 'img');
|
|
canvas.setAttribute('aria-label', 'Interactive visualization of noise patterns');
|
|
introNoiseDemoContainer.innerHTML = '';
|
|
introNoiseDemoContainer.appendChild(canvas);
|
|
introNoiseDemoContainer.style.display = 'block';
|
|
}
|
|
if (typeof this.renderIntroNoiseDemo === 'function') {
|
|
this.renderIntroNoiseDemo(canvas);
|
|
}
|
|
else {
|
|
console.warn('renderIntroNoiseDemo function not found in NoiseVisualizer.');
|
|
this.renderMaskPlaceholder(canvas, 'Intro Noise Demo');
|
|
}
|
|
}
|
|
},
|
|
_clearCanvas: function (canvas, backgroundColor = '#1a1a2e') {
|
|
const ctx = canvas.getContext('2d');
|
|
ctx.fillStyle = backgroundColor;
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
return ctx;
|
|
},
|
|
_drawKofiIcon: function (ctx) {
|
|
const iconSize = 18;
|
|
const padding = 3;
|
|
const x = ctx.canvas.width - iconSize - padding;
|
|
const y = ctx.canvas.height - iconSize - padding;
|
|
if (this.kofiImageLoaded && this.kofiCupImageBitmap) {
|
|
try {
|
|
ctx.drawImage(this.kofiCupImageBitmap, x, y, iconSize, iconSize);
|
|
}
|
|
catch (e) {
|
|
console.error('Error drawing local Ko-fi SVG ImageBitmap, falling back to manual draw:', e);
|
|
this._drawManualKofiCup(ctx, x, y, iconSize);
|
|
}
|
|
}
|
|
else {
|
|
this._drawManualKofiCup(ctx, x, y, iconSize);
|
|
}
|
|
},
|
|
_drawManualKofiCup: function (ctx, x, y, iconSize) {
|
|
ctx.save();
|
|
ctx.fillStyle = '#FFDD99';
|
|
ctx.strokeStyle = '#D2B48C';
|
|
ctx.lineWidth = 1;
|
|
ctx.beginPath();
|
|
ctx.moveTo(x, y + iconSize * 0.2);
|
|
ctx.lineTo(x, y + iconSize * 0.9);
|
|
ctx.quadraticCurveTo(x + iconSize * 0.5, y + iconSize * 1.1, x + iconSize, y + iconSize * 0.9);
|
|
ctx.lineTo(x + iconSize, y + iconSize * 0.2);
|
|
ctx.quadraticCurveTo(x + iconSize * 0.5, y, x, y + iconSize * 0.2);
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
ctx.beginPath();
|
|
ctx.arc(x + iconSize * 0.9, y + iconSize * 0.5, iconSize * 0.25, -Math.PI / 2, Math.PI / 2);
|
|
ctx.stroke();
|
|
ctx.strokeStyle = '#FFFFFF';
|
|
ctx.lineWidth = 0.5;
|
|
ctx.beginPath();
|
|
ctx.moveTo(x + iconSize * 0.3, y + iconSize * 0.1);
|
|
ctx.quadraticCurveTo(x + iconSize * 0.2, y - iconSize * 0.2, x + iconSize * 0.4, y - iconSize * 0.3);
|
|
ctx.moveTo(x + iconSize * 0.6, y + iconSize * 0.05);
|
|
ctx.quadraticCurveTo(x + iconSize * 0.5, y - iconSize * 0.3, x + iconSize * 0.7, y - iconSize * 0.4);
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
},
|
|
renderPlaceholder: function (canvas, noiseName) {
|
|
const ctx = this._clearCanvas(canvas, '#2c2c34');
|
|
const nameToShow = noiseName.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
|
|
ctx.fillStyle = '#e0e0e8';
|
|
ctx.textAlign = 'center';
|
|
ctx.textBaseline = 'middle';
|
|
ctx.font = 'bold 12px "Segoe UI", Tahoma, Geneva, Verdana, sans-serif';
|
|
const words = nameToShow.split(' ');
|
|
if (words.length > 2) {
|
|
ctx.fillText(words.slice(0, 2).join(' '), canvas.width / 2, canvas.height / 2 - 7);
|
|
ctx.fillText(words.slice(2).join(' '), canvas.width / 2, canvas.height / 2 + 7);
|
|
}
|
|
else {
|
|
ctx.fillText(nameToShow, canvas.width / 2, canvas.height / 2);
|
|
}
|
|
ctx.strokeStyle = '#555';
|
|
ctx.strokeRect(0, 0, canvas.width, canvas.height);
|
|
},
|
|
// --- Visualization Functions ---
|
|
renderTensorField: function (canvas) {
|
|
const ctx = this._clearCanvas(canvas);
|
|
ctx.strokeStyle = 'rgba(138, 43, 226, 0.7)';
|
|
ctx.lineWidth = 1;
|
|
for (let i = 0; i < 10; i++) {
|
|
ctx.beginPath();
|
|
const y = (i + 0.5) * (canvas.height / 10);
|
|
ctx.moveTo(0, y);
|
|
for (let x = 0; x <= canvas.width; x += 5) {
|
|
const angle = Math.sin(x * 0.1 + y * 0.05) * Math.PI * 0.25;
|
|
ctx.lineTo(x + Math.cos(angle) * 5, y + Math.sin(angle) * 5);
|
|
ctx.moveTo(x, y);
|
|
}
|
|
ctx.stroke();
|
|
}
|
|
},
|
|
renderCellular: function (canvas) {
|
|
const ctx = this._clearCanvas(canvas);
|
|
const numCells = 8;
|
|
const points = [];
|
|
for (let i = 0; i < numCells; i++) {
|
|
points.push({ x: Math.random() * canvas.width, y: Math.random() * canvas.height });
|
|
}
|
|
for (let x = 0; x < canvas.width; x += 4) {
|
|
for (let y = 0; y < canvas.height; y += 4) {
|
|
let minDist = Infinity;
|
|
points.forEach(p => {
|
|
minDist = Math.min(minDist, Math.hypot(p.x - x, p.y - y));
|
|
});
|
|
const intensity = Math.min(255, minDist * 2);
|
|
ctx.fillStyle = `rgb(${intensity},${intensity},${intensity + 50})`;
|
|
ctx.fillRect(x, y, 4, 4);
|
|
}
|
|
}
|
|
this._drawKofiIcon(ctx.canvas.getContext('2d'));
|
|
},
|
|
renderDomainWarp: function (canvas) {
|
|
const ctx = this._clearCanvas(canvas);
|
|
ctx.lineWidth = 1.5;
|
|
for (let i = 0; i < 20; i++) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(Math.random() * canvas.width, Math.random() * canvas.height);
|
|
ctx.strokeStyle = `rgba(${100 + Math.random() * 155}, ${100 + Math.random() * 155}, ${200 + Math.random() * 55}, 0.6)`;
|
|
for (let j = 0; j < 5; j++) {
|
|
const x = Math.random() * canvas.width;
|
|
const y = Math.random() * canvas.height;
|
|
const cp1x = Math.random() * canvas.width;
|
|
const cp1y = Math.random() * canvas.height;
|
|
const cp2x = Math.random() * canvas.width;
|
|
const cp2y = Math.random() * canvas.height;
|
|
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
|
|
}
|
|
ctx.stroke();
|
|
}
|
|
},
|
|
renderFractal: function (canvas) {
|
|
const ctx = this._clearCanvas(canvas);
|
|
const scale = 0.05;
|
|
const octaves = 4;
|
|
const persistence = 0.5;
|
|
const lacunarity = 2.0;
|
|
for (let x = 0; x < canvas.width; x += 2) {
|
|
for (let y = 0; y < canvas.height; y += 2) {
|
|
let total = 0;
|
|
let frequency = 1;
|
|
let amplitude = 1;
|
|
let maxValue = 0;
|
|
for (let i = 0; i < octaves; i++) {
|
|
const noiseVal = (Math.sin(x * scale * frequency + y * scale * frequency * 0.7) +
|
|
Math.cos(y * scale * frequency - x * scale * frequency * 0.3)) / 2;
|
|
total += noiseVal * amplitude;
|
|
maxValue += amplitude;
|
|
amplitude *= persistence;
|
|
frequency *= lacunarity;
|
|
}
|
|
const normalizedTotal = (total / maxValue + 1) / 2;
|
|
const colorVal = Math.floor(normalizedTotal * 200) + 55;
|
|
ctx.fillStyle = `rgb(${colorVal}, ${colorVal * 0.95}, ${colorVal * 0.9})`;
|
|
ctx.fillRect(x, y, 2, 2);
|
|
}
|
|
}
|
|
this._drawKofiIcon(ctx.canvas.getContext('2d'));
|
|
},
|
|
renderPerlin: function (canvas) {
|
|
const ctx = this._clearCanvas(canvas);
|
|
for (let x = 0; x < canvas.width; x += 3) {
|
|
for (let y = 0; y < canvas.height; y += 3) {
|
|
const noiseVal = (Math.sin(x * 0.05 + Math.cos(y * 0.08)) + Math.cos(y * 0.06)) / 2;
|
|
const intensity = (noiseVal + 1) / 2 * 200 + 55;
|
|
ctx.fillStyle = `rgba(${intensity * 0.8}, ${intensity * 0.9}, ${intensity}, 1)`;
|
|
ctx.fillRect(x, y, 3, 3);
|
|
}
|
|
}
|
|
this._drawKofiIcon(ctx.canvas.getContext('2d'));
|
|
},
|
|
renderWaves: function (canvas) {
|
|
const ctx = this._clearCanvas(canvas);
|
|
ctx.strokeStyle = 'rgba(52, 152, 219, 0.7)';
|
|
ctx.lineWidth = 1.5;
|
|
for (let i = 0; i < 15; i++) {
|
|
ctx.beginPath();
|
|
const startY = i * (canvas.height / 15);
|
|
ctx.moveTo(0, startY);
|
|
for (let x = 0; x <= canvas.width; x += 5) {
|
|
const yOffset = Math.sin(x * 0.1 + i * 0.5) * 10;
|
|
ctx.lineTo(x, startY + yOffset);
|
|
}
|
|
ctx.stroke();
|
|
}
|
|
this._drawKofiIcon(ctx.canvas.getContext('2d'));
|
|
},
|
|
renderGaussian: function (canvas) {
|
|
const ctx = this._clearCanvas(canvas);
|
|
for (let i = 0; i < 10000; i++) {
|
|
const x = Math.random() * canvas.width;
|
|
const y = Math.random() * canvas.height;
|
|
const intensity = Math.floor(Math.random() * 100) + 100;
|
|
ctx.fillStyle = `rgba(${intensity * 0.8}, ${intensity * 0.9}, ${intensity}, ${Math.random() * 0.5 + 0.1})`;
|
|
ctx.fillRect(x - 1, y - 1, 2, 2);
|
|
}
|
|
this._drawKofiIcon(ctx.canvas.getContext('2d'));
|
|
},
|
|
renderHeterogeneousFBM: function (canvas) {
|
|
const ctx = this._clearCanvas(canvas);
|
|
for (let region = 0; region < 3; region++) {
|
|
const regionX = (canvas.width / 3) * region;
|
|
const regionW = canvas.width / 3;
|
|
const step = 2 + region * 2;
|
|
for (let x = 0; x < regionW; x += step) {
|
|
for (let y = 0; y < canvas.height; y += step) {
|
|
const noiseVal = Math.random();
|
|
const intensity = noiseVal * 255;
|
|
ctx.fillStyle = `rgb(${intensity}, ${intensity}, ${intensity})`;
|
|
ctx.fillRect(regionX + x, y, step, step);
|
|
}
|
|
}
|
|
}
|
|
this._drawKofiIcon(ctx.canvas.getContext('2d'));
|
|
},
|
|
renderInterference: function (canvas) {
|
|
const ctx = this._clearCanvas(canvas);
|
|
ctx.lineWidth = 0.5;
|
|
const sources = [
|
|
{ x: canvas.width * 0.2, y: canvas.height * 0.3, phase: 0 },
|
|
{ x: canvas.width * 0.8, y: canvas.height * 0.7, phase: Math.PI / 2 }
|
|
];
|
|
for (let x = 0; x < canvas.width; x += 3) {
|
|
for (let y = 0; y < canvas.height; y += 3) {
|
|
let sum = 0;
|
|
sources.forEach(s => {
|
|
const dist = Math.hypot(s.x - x, s.y - y);
|
|
sum += Math.sin(dist * 0.1 + (s.phase ?? 0));
|
|
});
|
|
const intensity = (sum / sources.length + 1) / 2 * 255;
|
|
ctx.fillStyle = `rgb(${intensity * 0.7}, ${intensity}, ${intensity * 0.8})`;
|
|
ctx.fillRect(x, y, 3, 3);
|
|
}
|
|
}
|
|
this._drawKofiIcon(ctx.canvas.getContext('2d'));
|
|
},
|
|
renderSpectral: function (canvas) {
|
|
const ctx = this._clearCanvas(canvas);
|
|
for (let i = 0; i < canvas.height; i += 4) {
|
|
const freqComponent = Math.sin(i * 0.1) * 0.3 + Math.cos(i * 0.05) * 0.3 + Math.random() * 0.4;
|
|
const intensity = (freqComponent + 1) / 2 * 255;
|
|
ctx.fillStyle = `rgb(${intensity}, ${intensity * 0.8}, ${intensity * 0.6})`;
|
|
ctx.fillRect(0, i, canvas.width, 4);
|
|
ctx.strokeStyle = 'rgba(0,0,0,0.2)';
|
|
ctx.strokeRect(0, i, canvas.width, 4);
|
|
}
|
|
this._drawKofiIcon(ctx.canvas.getContext('2d'));
|
|
},
|
|
renderProjection3D: function (canvas) {
|
|
const ctx = this._clearCanvas(canvas);
|
|
for (let i = 0; i < 50; i++) {
|
|
const z = Math.random();
|
|
const x = (Math.random() - 0.5) * canvas.width * (1 + z) + canvas.width / 2;
|
|
const y = (Math.random() - 0.5) * canvas.height * (1 + z) + canvas.height / 2;
|
|
const size = (1 - z) * 10 + 2;
|
|
const opacity = (1 - z) * 0.7 + 0.1;
|
|
ctx.fillStyle = `rgba(${150 + z * 105}, ${150 + z * 105}, ${200 + z * 55}, ${opacity})`;
|
|
ctx.beginPath();
|
|
ctx.arc(x, y, size, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
this._drawKofiIcon(ctx.canvas.getContext('2d'));
|
|
},
|
|
renderCurlNoise: function (canvas) {
|
|
const ctx = this._clearCanvas(canvas);
|
|
ctx.lineWidth = 1;
|
|
for (let i = 0; i < 50; i++) {
|
|
let x = Math.random() * canvas.width;
|
|
let y = Math.random() * canvas.height;
|
|
ctx.beginPath();
|
|
ctx.moveTo(x, y);
|
|
ctx.strokeStyle = `rgba(${100 + Math.random() * 100}, ${150 + Math.random() * 105}, ${200 + Math.random() * 55}, 0.5)`;
|
|
for (let step = 0; step < 20; step++) {
|
|
const angle = Math.sin(x * 0.02 + y * 0.03) * Math.PI + Math.cos(y * 0.02 - x * 0.01) * Math.PI;
|
|
x += Math.cos(angle) * 5;
|
|
y += Math.sin(angle) * 5;
|
|
if (x < 0 || x > canvas.width || y < 0 || y > canvas.height)
|
|
break;
|
|
ctx.lineTo(x, y);
|
|
}
|
|
ctx.stroke();
|
|
}
|
|
},
|
|
// Mask Placeholder
|
|
renderMaskPlaceholder: function (canvas, maskName) {
|
|
const ctx = this._clearCanvas(canvas, '#2c2c34');
|
|
const nameToShow = maskName.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
|
|
ctx.fillStyle = '#e0e0e8';
|
|
ctx.textAlign = 'center';
|
|
ctx.textBaseline = 'middle';
|
|
ctx.font = 'bold 10px "Segoe UI", Tahoma, Geneva, Verdana, sans-serif';
|
|
ctx.fillText(nameToShow, canvas.width / 2, canvas.height / 2);
|
|
ctx.strokeStyle = '#555';
|
|
ctx.strokeRect(0, 0, canvas.width, canvas.height);
|
|
},
|
|
// Mask Visualization Functions
|
|
renderMaskRadial: function (canvas) {
|
|
const ctx = this._clearCanvas(canvas);
|
|
const centerX = canvas.width / 2;
|
|
const centerY = canvas.height / 2;
|
|
const maxRadius = Math.min(canvas.width, canvas.height) / 2;
|
|
const gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, maxRadius);
|
|
gradient.addColorStop(0, 'rgba(255, 255, 255, 1.0)');
|
|
gradient.addColorStop(1, 'rgba(255, 255, 255, 0.0)');
|
|
ctx.fillStyle = gradient;
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
|
|
ctx.strokeRect(0, 0, canvas.width, canvas.height);
|
|
},
|
|
renderMaskLinear: function (canvas) {
|
|
const ctx = this._clearCanvas(canvas);
|
|
const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
|
|
gradient.addColorStop(0, 'rgba(255, 255, 255, 1.0)');
|
|
gradient.addColorStop(1, 'rgba(255, 255, 255, 0.0)');
|
|
ctx.fillStyle = gradient;
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
|
|
ctx.strokeRect(0, 0, canvas.width, canvas.height);
|
|
},
|
|
renderMaskGrid: function (canvas) {
|
|
const ctx = this._clearCanvas(canvas);
|
|
const cellSize = 20;
|
|
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
|
|
for (let x = 0; x < canvas.width; x += cellSize) {
|
|
for (let y = 0; y < canvas.height; y += cellSize) {
|
|
if ((Math.floor(x / cellSize) + Math.floor(y / cellSize)) % 2 === 0) {
|
|
ctx.fillRect(x, y, cellSize, cellSize);
|
|
}
|
|
}
|
|
}
|
|
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
|
|
ctx.strokeRect(0, 0, canvas.width, canvas.height);
|
|
},
|
|
renderMaskVignette: function (canvas) {
|
|
const ctx = this._clearCanvas(canvas);
|
|
const centerX = canvas.width / 2;
|
|
const centerY = canvas.height / 2;
|
|
const maxRadius = Math.sqrt(centerX * centerX + centerY * centerY);
|
|
const gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, maxRadius);
|
|
gradient.addColorStop(0, 'rgba(255, 255, 255, 0.0)');
|
|
gradient.addColorStop(0.6, 'rgba(255, 255, 255, 0.3)');
|
|
gradient.addColorStop(1, 'rgba(255, 255, 255, 1.0)');
|
|
ctx.fillStyle = gradient;
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
|
|
ctx.strokeRect(0, 0, canvas.width, canvas.height);
|
|
},
|
|
renderMaskSpiral: function (canvas) {
|
|
const ctx = this._clearCanvas(canvas);
|
|
const centerX = canvas.width / 2;
|
|
const centerY = canvas.height / 2;
|
|
const maxRadius = Math.min(canvas.width, canvas.height) / 2;
|
|
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
|
|
ctx.strokeStyle = 'rgba(255, 255, 255, 1.0)';
|
|
ctx.lineWidth = 5;
|
|
ctx.beginPath();
|
|
for (let theta = 0; theta < 8 * Math.PI; theta += 0.1) {
|
|
const radius = (maxRadius / (8 * Math.PI)) * theta;
|
|
const x = centerX + radius * Math.cos(theta);
|
|
const y = centerY + radius * Math.sin(theta);
|
|
if (theta === 0) {
|
|
ctx.moveTo(x, y);
|
|
}
|
|
else {
|
|
ctx.lineTo(x, y);
|
|
}
|
|
}
|
|
ctx.stroke();
|
|
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
|
|
ctx.lineWidth = 1;
|
|
ctx.strokeRect(0, 0, canvas.width, canvas.height);
|
|
},
|
|
renderMaskHexgrid: function (canvas) {
|
|
const ctx = this._clearCanvas(canvas);
|
|
const hexSize = 15;
|
|
const hexHeight = hexSize * Math.sqrt(3);
|
|
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
|
|
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
|
|
ctx.lineWidth = 1;
|
|
for (let row = 0; row < canvas.height / hexHeight + 1; row++) {
|
|
for (let col = 0; col < canvas.width / (hexSize * 3) + 1; col++) {
|
|
const offsetX = (row % 2) * hexSize * 1.5;
|
|
const x = col * hexSize * 3 + offsetX;
|
|
const y = row * hexHeight;
|
|
ctx.beginPath();
|
|
for (let i = 0; i < 6; i++) {
|
|
const angle = i * Math.PI / 3;
|
|
const pX = x + hexSize * Math.cos(angle);
|
|
const pY = y + hexSize * Math.sin(angle);
|
|
if (i === 0) {
|
|
ctx.moveTo(pX, pY);
|
|
}
|
|
else {
|
|
ctx.lineTo(pX, pY);
|
|
}
|
|
}
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
|
|
ctx.strokeRect(0, 0, canvas.width, canvas.height);
|
|
},
|
|
renderMaskWavy: function (canvas) {
|
|
const ctx = this._clearCanvas(canvas);
|
|
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
|
|
ctx.strokeStyle = 'rgba(255, 255, 255, 1.0)';
|
|
ctx.lineWidth = 3;
|
|
const amplitude = 10;
|
|
const frequency = 0.05;
|
|
for (let y = 20; y < canvas.height; y += 20) {
|
|
ctx.beginPath();
|
|
for (let x = 0; x <= canvas.width; x += 2) {
|
|
const yOffset = Math.sin(x * frequency) * amplitude;
|
|
if (x === 0) {
|
|
ctx.moveTo(x, y + yOffset);
|
|
}
|
|
else {
|
|
ctx.lineTo(x, y + yOffset);
|
|
}
|
|
}
|
|
ctx.stroke();
|
|
}
|
|
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
|
|
ctx.lineWidth = 1;
|
|
ctx.strokeRect(0, 0, canvas.width, canvas.height);
|
|
},
|
|
renderMaskConcentricRings: function (canvas) {
|
|
const ctx = this._clearCanvas(canvas);
|
|
const centerX = canvas.width / 2;
|
|
const centerY = canvas.height / 2;
|
|
ctx.strokeStyle = 'rgba(255, 255, 255, 1.0)';
|
|
ctx.lineWidth = 2;
|
|
const ringCount = 6;
|
|
const maxRadius = Math.min(canvas.width, canvas.height) / 2;
|
|
const step = maxRadius / ringCount;
|
|
for (let i = 1; i <= ringCount; i++) {
|
|
const radius = i * step;
|
|
ctx.beginPath();
|
|
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
|
|
ctx.stroke();
|
|
if (i % 2 === 0) {
|
|
ctx.fillStyle = 'rgba(255, 255, 255, 0.15)';
|
|
ctx.beginPath();
|
|
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
}
|
|
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
|
|
ctx.lineWidth = 1;
|
|
ctx.strokeRect(0, 0, canvas.width, canvas.height);
|
|
},
|
|
// Color scheme rendering functions
|
|
renderColorInferno: function (swatchDiv) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #000004, #3b0f70, #8c2981, #de4968, #fe9f6d, #fcfdbf)';
|
|
},
|
|
renderColorMagma: function (swatchDiv) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #000004, #3b0f70, #8c2981, #de4968, #fe9f6d, #fcfdbf)';
|
|
},
|
|
renderColorPlasma: function (swatchDiv) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #0d0887, #7e03a8, #cc4678, #f89441, #f0f921)';
|
|
},
|
|
renderColorViridis: function (swatchDiv) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #440154, #30678d, #35b778, #fde724)';
|
|
},
|
|
renderColorTurbo: function (swatchDiv) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #30123b, #4669db, #26bf8c, #d4ff50, #fab74c, #ba0100)';
|
|
},
|
|
renderColorJet: function (swatchDiv) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #00007f, #0000ff, #00ffff, #ffff00, #ff0000, #7f0000)';
|
|
},
|
|
renderColorParula: function (swatchDiv) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #352a87, #0f5cdd, #00b5a6, #ffc337, #fcfea4)';
|
|
},
|
|
renderColorRainbow: function (swatchDiv) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #0000ff, #00ffff, #00ff00, #ffff00, #ff0000)';
|
|
},
|
|
renderColorHot: function (swatchDiv) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #000000, #ff0000, #ffff00, #ffffff)';
|
|
},
|
|
renderColorBlueRed: function (swatchDiv) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #0000ff, #7777ff, #ffffff, #ff7777, #ff0000)';
|
|
},
|
|
renderColorCool: function (swatchDiv) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #00ffff, #77aaff, #aa77ff, #ff00ff)';
|
|
},
|
|
renderColorHsv: function (swatchDiv) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #ff0000, #ffff00, #00ff00, #00ffff, #0000ff, #ff00ff, #ff0000)';
|
|
},
|
|
renderColorAutumn: function (swatchDiv) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #ff0000, #ff7700, #ffaa00, #ffdd00, #ffff00)';
|
|
},
|
|
renderColorWinter: function (swatchDiv) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #0000ff, #0077cc, #00aabb, #00ddaa)';
|
|
},
|
|
renderColorSpring: function (swatchDiv) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #ff00ff, #ff33cc, #ff77aa, #ffaa77, #ffdd44, #ffff00)';
|
|
},
|
|
renderColorSummer: function (swatchDiv) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #004433, #008855, #44aa66, #88cc77, #ccee88, #ffff66)';
|
|
},
|
|
renderColorCopper: function (swatchDiv) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #000000, #331100, #662200, #993300, #cc6644, #ff9966)';
|
|
},
|
|
renderColorPink: function (swatchDiv) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #0a0a0a, #550055, #aa0066, #ff44aa, #ffaadd, #ffffff)';
|
|
},
|
|
renderColorBone: function (swatchDiv) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #000000, #2a2a3a, #5a748a, #9ebacb, #dfdfef, #ffffff)';
|
|
},
|
|
renderColorOcean: function (swatchDiv) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #000000, #000066, #0000bb, #0066cc, #00ccff, #99ffff)';
|
|
},
|
|
renderColorTerrain: function (swatchDiv) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #333399, #009966, #66cc33, #cccc33, #ff9933, #ffffff)';
|
|
},
|
|
renderColorNeon: function (swatchDiv) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #ff00ff, #aa00ff, #5500ff, #0000ff, #00aaff, #00ffff, #00ff00, #aaff00, #ffff00)';
|
|
},
|
|
renderColorFire: function (swatchDiv) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #000000, #330000, #660000, #bb0000, #ff0000, #ff7700, #ffdd00, #ffffff)';
|
|
},
|
|
// Temporal animation
|
|
renderTemporalAnimation: function (canvas) {
|
|
const ctx = this._clearCanvas(canvas, '#0a0a0a');
|
|
let time = 0;
|
|
function drawPattern(currentTime) {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
ctx.fillStyle = '#0a0a0a';
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
const numLines = 20;
|
|
const maxOffset = 20;
|
|
ctx.strokeStyle = 'rgba(52, 152, 219, 0.6)';
|
|
ctx.lineWidth = 1.5;
|
|
for (let i = 0; i < numLines; i++) {
|
|
ctx.beginPath();
|
|
const startY = (i / numLines) * canvas.height;
|
|
ctx.moveTo(0, startY);
|
|
for (let x = 0; x <= canvas.width; x += 5) {
|
|
const yOffset = Math.sin(x * 0.02 + i * 0.3 + currentTime * 0.05) * maxOffset * Math.sin(currentTime * 0.02 + i * 0.1);
|
|
const actualY = startY + yOffset;
|
|
ctx.lineTo(x, actualY);
|
|
}
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
function animate() {
|
|
drawPattern(time);
|
|
time += 0.1;
|
|
if (canvas.isConnected) {
|
|
requestAnimationFrame(animate);
|
|
}
|
|
}
|
|
if (!canvas.dataset.animationRunning) {
|
|
canvas.dataset.animationRunning = 'true';
|
|
animate();
|
|
}
|
|
},
|
|
// Intro noise demo - complex particle animation
|
|
renderIntroNoiseDemo: function (canvas) {
|
|
const ctx = this._clearCanvas(canvas, '#0f0f12');
|
|
let time = 0;
|
|
const particles = [];
|
|
const numParticles = 120;
|
|
const particleColorBase = [138, 43, 226];
|
|
const particleColorHighlight = [224, 224, 232];
|
|
const loopCycleDuration = 420;
|
|
let lastLoopInstance = -1;
|
|
const currentBackgroundPattern = { type: null, variantSeed: 0, alpha: 0, startTime: 0 };
|
|
const primordialDuration = 60;
|
|
// Initialize particles
|
|
for (let i = 0; i < numParticles; i++) {
|
|
particles.push({
|
|
x: Math.random() * canvas.width,
|
|
y: Math.random() * canvas.height,
|
|
vx: 0,
|
|
vy: 0,
|
|
life: 0,
|
|
initialLife: Math.random() * 100 + 90,
|
|
radius: Math.random() * 1.7 + 0.6,
|
|
trail: [],
|
|
isPrimordial: true,
|
|
primordialTime: 0
|
|
});
|
|
}
|
|
function noiseField(x, y, t) {
|
|
const baseScale = 0.009;
|
|
const timeScale = 0.0023;
|
|
const loopTime = t % loopCycleDuration;
|
|
const currentLoopInst = Math.floor(t / loopCycleDuration);
|
|
const globalRotationAngle = currentLoopInst * 0.15;
|
|
const cosA = Math.cos(globalRotationAngle);
|
|
const sinA = Math.sin(globalRotationAngle);
|
|
const rotatedX = x * cosA - y * sinA;
|
|
const rotatedY = x * sinA + y * cosA;
|
|
let baseChaosVal = 0;
|
|
const scale1 = baseScale * 1.8;
|
|
baseChaosVal += Math.sin(rotatedX * scale1 + loopTime * timeScale * 1.1) *
|
|
Math.cos(rotatedY * scale1 * 0.8 - loopTime * timeScale * 0.9);
|
|
const scale2 = baseScale * 0.9;
|
|
baseChaosVal += Math.sin(rotatedY * scale2 * 1.1 - loopTime * timeScale * 1.3) *
|
|
Math.cos(rotatedX * scale2 * 0.9 + loopTime * timeScale * 1.0) * 0.8;
|
|
const scale3 = baseScale * 1.2;
|
|
const innerAngleOffset = Math.sin(loopTime * 0.0008 + currentLoopInst * 0.05) * 0.5;
|
|
const N_x_chaos = rotatedX * Math.cos(innerAngleOffset) - rotatedY * Math.sin(innerAngleOffset);
|
|
const N_y_chaos = rotatedX * Math.sin(innerAngleOffset) + rotatedY * Math.cos(innerAngleOffset);
|
|
baseChaosVal += Math.sin(N_x_chaos * scale3 * 0.6 + loopTime * timeScale * 0.7) *
|
|
Math.cos(N_y_chaos * scale3 * 0.7 - loopTime * timeScale * 0.5) * 0.9;
|
|
return (baseChaosVal / 2.7) * Math.PI * 4.0;
|
|
}
|
|
function drawIntroPattern() {
|
|
const currentLoopInst = Math.floor(time / loopCycleDuration);
|
|
const isNewLoopStart = (time % loopCycleDuration) < (60 / 2.5);
|
|
if (isNewLoopStart && currentLoopInst !== lastLoopInstance) {
|
|
ctx.fillStyle = 'rgba(15, 15, 18, 0.45)';
|
|
lastLoopInstance = currentLoopInst;
|
|
const shaderPatternTypes = ['tensor_field', 'cellular', 'domain_warp', 'perlin', 'curl_noise', 'waves_interference'];
|
|
currentBackgroundPattern.type = shaderPatternTypes[currentLoopInst % shaderPatternTypes.length];
|
|
currentBackgroundPattern.variantSeed = currentLoopInst;
|
|
currentBackgroundPattern.alpha = 1.0;
|
|
currentBackgroundPattern.startTime = time;
|
|
particles.forEach(p => {
|
|
p.x = Math.random() * canvas.width;
|
|
p.y = Math.random() * canvas.height;
|
|
p.vx = (Math.random() - 0.5) * 0.1;
|
|
p.vy = (Math.random() - 0.5) * 0.1;
|
|
p.life = p.initialLife;
|
|
p.trail = [];
|
|
p.isPrimordial = true;
|
|
p.primordialTime = 0;
|
|
});
|
|
}
|
|
else {
|
|
ctx.fillStyle = 'rgba(15, 15, 18, 0.015)';
|
|
}
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
// Update and draw particles
|
|
particles.forEach(p => {
|
|
if (p.isPrimordial) {
|
|
p.primordialTime++;
|
|
if (p.primordialTime >= primordialDuration) {
|
|
p.isPrimordial = false;
|
|
}
|
|
}
|
|
const finalAngle = noiseField(p.x, p.y, time);
|
|
p.vx += Math.cos(finalAngle) * 0.10;
|
|
p.vy += Math.sin(finalAngle) * 0.10;
|
|
p.vx *= 0.94;
|
|
p.vy *= 0.94;
|
|
p.x += p.vx;
|
|
p.y += p.vy;
|
|
p.trail.push({ x: p.x, y: p.y });
|
|
if (p.trail.length > 25) {
|
|
p.trail.shift();
|
|
}
|
|
let trailBaseAlpha = 0.8;
|
|
let particleHeadBaseAlpha = 0.65;
|
|
let currentParticleRadius = p.radius;
|
|
let r = particleColorBase[0], g = particleColorBase[1], b = particleColorBase[2];
|
|
if (p.isPrimordial) {
|
|
const primordialRatio = p.primordialTime / primordialDuration;
|
|
trailBaseAlpha *= primordialRatio * 0.5;
|
|
particleHeadBaseAlpha = primordialRatio * 0.5;
|
|
currentParticleRadius = p.radius * (0.3 + primordialRatio * 0.7);
|
|
r = (particleColorBase[0] * primordialRatio) + (70 * (1 - primordialRatio));
|
|
g = (particleColorBase[1] * primordialRatio) + (70 * (1 - primordialRatio));
|
|
b = (particleColorBase[2] * primordialRatio) + (90 * (1 - primordialRatio));
|
|
}
|
|
if (p.trail.length > 1) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(p.trail[0].x, p.trail[0].y);
|
|
for (let i = 1; i < p.trail.length; i++) {
|
|
const trailSegmentAlpha = (i / p.trail.length) * trailBaseAlpha;
|
|
ctx.strokeStyle = `rgba(200, 200, 200, ${trailSegmentAlpha})`;
|
|
ctx.lineWidth = currentParticleRadius * (i / p.trail.length) * 1.1;
|
|
ctx.lineTo(p.trail[i].x, p.trail[i].y);
|
|
}
|
|
ctx.stroke();
|
|
}
|
|
const lifeRatio = p.life / p.initialLife;
|
|
const emergenceFactor = p.isPrimordial ? (p.primordialTime / primordialDuration) : (1.0 - Math.pow(1.0 - lifeRatio, 2));
|
|
const finalRadius = p.isPrimordial ? currentParticleRadius : p.radius * emergenceFactor;
|
|
const mainParticleAlpha = emergenceFactor * particleHeadBaseAlpha;
|
|
const highlightAlpha = emergenceFactor * (particleHeadBaseAlpha + 0.1);
|
|
ctx.beginPath();
|
|
ctx.arc(p.x, p.y, Math.max(0.1, finalRadius), 0, Math.PI * 2);
|
|
if (!p.isPrimordial) {
|
|
const speed = Math.hypot(p.vx, p.vy);
|
|
r = Math.min(255, particleColorBase[0] + speed * 15);
|
|
g = Math.min(255, particleColorBase[1] - speed * 5);
|
|
b = Math.min(255, particleColorBase[2] + speed * 5);
|
|
}
|
|
ctx.fillStyle = `rgba(${Math.floor(r)}, ${Math.floor(g)}, ${Math.floor(b)}, ${mainParticleAlpha})`;
|
|
ctx.fill();
|
|
if (finalRadius > 0.5) {
|
|
ctx.beginPath();
|
|
ctx.arc(p.x, p.y, Math.max(0.1, finalRadius * 0.5), 0, Math.PI * 2);
|
|
ctx.fillStyle = `rgba(${particleColorHighlight[0]}, ${particleColorHighlight[1]}, ${particleColorHighlight[2]}, ${highlightAlpha})`;
|
|
ctx.fill();
|
|
}
|
|
p.life--;
|
|
if (p.life <= 0 || p.x < -finalRadius * 2 || p.x > canvas.width + finalRadius * 2 || p.y < -finalRadius * 2 || p.y > canvas.height + finalRadius * 2) {
|
|
p.x = Math.random() * canvas.width;
|
|
p.y = Math.random() * canvas.height;
|
|
p.vx = (Math.random() - 0.5) * 0.1;
|
|
p.vy = (Math.random() - 0.5) * 0.1;
|
|
p.life = p.initialLife;
|
|
p.trail = [];
|
|
p.isPrimordial = true;
|
|
p.primordialTime = 0;
|
|
}
|
|
});
|
|
}
|
|
function animateIntro() {
|
|
drawIntroPattern();
|
|
time++;
|
|
if (canvas.isConnected) {
|
|
requestAnimationFrame(animateIntro);
|
|
}
|
|
}
|
|
if (!canvas.dataset.animationRunningIntro) {
|
|
canvas.dataset.animationRunningIntro = 'true';
|
|
animateIntro();
|
|
}
|
|
},
|
|
renderColorPlaceholder: function (swatchDiv, schemeName) {
|
|
swatchDiv.style.background = 'linear-gradient(to bottom, #333333, #666666, #999999, #cccccc)';
|
|
const nameSpan = document.createElement('span');
|
|
nameSpan.textContent = schemeName;
|
|
nameSpan.style.position = 'absolute';
|
|
nameSpan.style.top = '50%';
|
|
nameSpan.style.left = '50%';
|
|
nameSpan.style.transform = 'translate(-50%, -50%)';
|
|
nameSpan.style.color = 'white';
|
|
nameSpan.style.textShadow = '1px 1px 1px black';
|
|
nameSpan.style.fontSize = '10px';
|
|
swatchDiv.appendChild(nameSpan);
|
|
}
|
|
};
|
|
window.NoiseVisualizer = NoiseVisualizer;
|
|
// Use local reference for type safety
|
|
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
|
(async () => { await NoiseVisualizer._preloadKofiImage(); })();
|
|
}
|
|
else {
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
await NoiseVisualizer._preloadKofiImage();
|
|
});
|
|
}
|
|
}
|
|
export {};
|
|
//# sourceMappingURL=noise_visualizer.js.map
|