diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/ComfyUI_Fill-Nodes.iml b/.idea/ComfyUI_Fill-Nodes.iml
new file mode 100644
index 0000000..8b8c395
--- /dev/null
+++ b/.idea/ComfyUI_Fill-Nodes.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..86d07c1
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..5801c97
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/__init__.py b/__init__.py
index 230ab8c..643a201 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,41 +1,41 @@
-from .nodes.fl_image_randomizer import FL_ImageRandomizer
+from .nodes.FL_Image_Randomizer import FL_ImageRandomizer
from .nodes.FL_Image_Caption_Saver import FL_ImageCaptionSaver
-from .nodes.fl_image_dimension_display import FL_ImageDimensionDisplay
+from .nodes.FL_Image_Dimension_Display import FL_ImageDimensionDisplay
from .nodes.FL_Code_Node import FL_CodeNode
-from .nodes.fl_image_pixelator import FL_ImagePixelator
+from .nodes.FL_Image_Pixelator import FL_ImagePixelator
from .nodes.FL_DirectoryCrawl import FL_DirectoryCrawl
from .nodes.FL_Ascii import FL_Ascii
from .nodes.FL_Glitch import FL_Glitch
-from .nodes.fl_ripple import FL_Ripple
-from .nodes.fl_pixelsort import FL_PixelSort
+from .nodes.FL_Ripple import FL_Ripple
+from .nodes.FL_PixelSort import FL_PixelSort
from .nodes.FL_HexagonalPattern import FL_HexagonalPattern
-from .nodes.fl_nftgenerator import FL_NFTGenerator
+from .nodes.FL_NFTGenerator import FL_NFTGenerator
from .nodes.FL_HalfTone import FL_HalftonePattern
-from .nodes.fl_randomrange import FL_RandomNumber
-from .nodes.fl_promptselector import FL_PromptSelector
-from .nodes.fl_shader import FL_Shadertoy
-from .nodes.fl_pixelshader import FL_PixelArtShader
-from .nodes.fl_infinitezoom import FL_InfiniteZoom
-from .nodes.fl_paperdrawn import FL_PaperDrawn
-from .nodes.fl_imagenotes import FL_ImageNotes
-from .nodes.fl_imagecollage import FL_ImageCollage
-from .nodes.fl_ksamplersettings import FL_KsamplerSettings
-from .nodes.fl_retroeffect import FL_RetroEffect
-from .nodes.fl_td_ksampler import FL_TD_KSampler
-from .nodes.fl_inpaintcrop import FL_InpaintCrop
-from .nodes.fl_inpaintcrop import FL_Inpaint_Stitch
-from .nodes.fl_sdultimate_slices import FL_SDUltimate_Slices
+from .nodes.FL_RandomRange import FL_RandomNumber
+from .nodes.FL_PromptSelector import FL_PromptSelector
+from .nodes.FL_Shader import FL_Shadertoy
+from .nodes.FL_PixelShader import FL_PixelArtShader
+from .nodes.FL_InfiniteZoom import FL_InfiniteZoom
+from .nodes.FL_PaperDrawn import FL_PaperDrawn
+from .nodes.FL_ImageNotes import FL_ImageNotes
+from .nodes.FL_ImageCollage import FL_ImageCollage
+from .nodes.FL_KsamplerSettings import FL_KsamplerSettings
+from .nodes.FL_RetroEffect import FL_RetroEffect
+from .nodes.FL_TD_Ksampler import FL_TD_KSampler
+from .nodes.FL_InpaintCrop import FL_InpaintCrop
+from .nodes.FL_InpaintCrop import FL_Inpaint_Stitch
+from .nodes.FL_SD_Slices import FL_SDUltimate_Slices
from .nodes.FL_BatchAligned import FL_BatchAlign
-from .nodes.fl_videocropnstitch import FL_VideoCropMask
-from .nodes.fl_videocropnstitch import FL_VideoRecompose
-from .nodes.fl_separatemasks import FL_SeparateMaskComponents
-from .nodes.fl_pasteoncanvas import FL_PasteOnCanvas
+from .nodes.FL_VideoCropNStitch import FL_VideoCropMask
+from .nodes.FL_VideoCropNStitch import FL_VideoRecompose
+from .nodes.FL_SeparateMasks import FL_SeparateMaskComponents
+from .nodes.FL_PasteOnCanvas import FL_PasteOnCanvas
from .nodes.FL_BulletHellGame import FL_BulletHellGame
-from .nodes.fl_tetrisgame import FL_TetrisGame
+from .nodes.FL_TetrisGame import FL_TetrisGame
from .nodes.FL_Dither import FL_Dither
from .nodes.FL_SystemCheck import FL_SystemCheck
from .nodes.FL_ColorPicker import FL_ColorPicker
-from .nodes.GradientImageGenerator import GradientImageGenerator
+from .nodes.FL_GradGen import GradientImageGenerator
from .nodes.FL_MirrorAndAppendCaptions import FL_MirrorAndAppendCaptions
from .nodes.FL_ImageCaptionLayout import FL_ImageCaptionLayout
from .nodes.FL_HFHubModelUploader import FL_HFHubModelUploader
diff --git a/nodes/GradientImageGenerator.py b/nodes/FL_GradGen.py
similarity index 100%
rename from nodes/GradientImageGenerator.py
rename to nodes/FL_GradGen.py
diff --git a/nodes/FL_KsamplerPlus.py b/nodes/FL_KsamplerPlus.py
index 7415c66..488ead2 100644
--- a/nodes/FL_KsamplerPlus.py
+++ b/nodes/FL_KsamplerPlus.py
@@ -7,6 +7,7 @@ import logging
import numpy as np
from PIL import Image
import torch.nn.functional as F
+import latent_preview
class FL_KsamplerPlus:
@@ -33,7 +34,6 @@ class FL_KsamplerPlus:
},
"optional": {
"latent_image": ("LATENT",),
- "vae": ("VAE",),
"image": ("IMAGE",),
}
}
@@ -145,24 +145,28 @@ class FL_KsamplerPlus:
return section, y_start, y_end, x_start, x_end, cropped_positive, cropped_negative
total_slices = x_slices * y_slices
- all_slices = [(y, x) for y in range(y_slices) for x in range(x_slices)]
-
for i in range(0, total_slices, batch_size):
- batch_slices = all_slices[i:min(i + batch_size, total_slices)]
+ batch_slices = [(y, x) for y in range(y_slices) for x in range(x_slices)][
+ i:min(i + batch_size, total_slices)]
batch_sections = [process_slice(y, x) for y, x in batch_slices]
batch_latents = torch.cat([section for section, _, _, _, _, _, _ in batch_sections], dim=0)
- batch_positives = sum([pos for _, _, _, _, _, pos, _ in batch_sections], [])
- batch_negatives = sum([neg for _, _, _, _, _, _, neg in batch_sections], [])
- processed_batch = common_ksampler(model, seed + i, steps, cfg, sampler_name, scheduler,
- batch_positives, batch_negatives,
- {"samples": batch_latents}, denoise=denoise)[0]
+ if use_sliced_conditioning:
+ batch_positive = [cond for _, _, _, _, _, cond, _ in batch_sections]
+ batch_negative = [cond for _, _, _, _, _, _, cond in batch_sections]
+ else:
+ batch_positive = [positive] * len(batch_sections)
+ batch_negative = [negative] * len(batch_sections)
- processed_sections = torch.split(processed_batch["samples"], b, dim=0)
+ # Process each slice in the batch individually
+ for j, (slice_latent, slice_positive, slice_negative) in enumerate(
+ zip(torch.split(batch_latents, 1), batch_positive, batch_negative)):
+ processed_slice = common_ksampler(model, seed + i + j, steps, cfg, sampler_name, scheduler,
+ slice_positive, slice_negative,
+ {"samples": slice_latent}, denoise=denoise)[0]
- for (_, y_start, y_end, x_start, x_end, _, _), processed_section in zip(batch_sections,
- processed_sections):
+ _, y_start, y_end, x_start, x_end, _, _ = batch_sections[j]
is_top = y_start == 0
is_left = x_start == 0
is_bottom = y_end == h
@@ -170,14 +174,14 @@ class FL_KsamplerPlus:
blend_mask = create_blend_mask(y_end - y_start, x_end - x_start,
overlap_height, overlap_width,
is_top, is_left, is_bottom, is_right)
- blend_mask = blend_mask.unsqueeze(0).unsqueeze(0).expand_as(processed_section)
+ blend_mask = blend_mask.unsqueeze(0).unsqueeze(0).expand_as(processed_slice["samples"])
- processed_section = processed_section.to(device=device)
+ processed_slice = processed_slice["samples"].to(device=device)
blend_mask = blend_mask.to(device=device)
samples[:, :, y_start:y_end, x_start:x_end] = (
samples[:, :, y_start:y_end, x_start:x_end] * (1 - blend_mask) +
- processed_section * blend_mask
+ processed_slice * blend_mask
)
if device.type == 'cuda':
@@ -193,3 +197,9 @@ class FL_KsamplerPlus:
except Exception as e:
logging.error(f"Error in FL_UltimateUpscale: {str(e)}")
raise
+
+ @classmethod
+ def IS_CHANGED(s, model, positive, negative, x_slices, y_slices, overlap, batch_size, seed, steps, cfg,
+ sampler_name, scheduler, denoise, input_type, use_sliced_conditioning, latent_image=None, image=None,
+ vae=None):
+ return float("NaN")
\ No newline at end of file
diff --git a/nodes/fl_sdultimate_slices.py b/nodes/FL_SD_Slices.py
similarity index 100%
rename from nodes/fl_sdultimate_slices.py
rename to nodes/FL_SD_Slices.py
diff --git a/web/nodes/FL_KsamplerPlus.js b/web/nodes/FL_KsamplerPlus.js
index 4f9b2d2..45346f0 100644
--- a/web/nodes/FL_KsamplerPlus.js
+++ b/web/nodes/FL_KsamplerPlus.js
@@ -8,65 +8,359 @@ app.registerExtension({
];
if (animatedNodeClasses.includes(node.comfyClass)) {
- addDenoisingTextAnimatedDisplay(node);
+ addModernSlotMachineAnimation(node);
}
}
});
-function addDenoisingTextAnimatedDisplay(node) {
- const ANIMATION_WIDTH = 120;
- const ANIMATION_HEIGHT = 80;
- const PIXEL_SIZE = 1;
- const GRID_WIDTH = Math.floor(ANIMATION_WIDTH / PIXEL_SIZE);
- const GRID_HEIGHT = Math.floor(ANIMATION_HEIGHT / PIXEL_SIZE);
+function addModernSlotMachineAnimation(node) {
+ // Easily adjustable parameters
+ const PARAMS = {
+ // Animation dimensions
+ ANIMATION_WIDTH: 120,
+ ANIMATION_HEIGHT: 80,
+ X_OFFSET: 0,
+ Y_OFFSET: 100,
- let grid = createEmptyGrid(GRID_WIDTH, GRID_HEIGHT);
- let animationProgress = 0;
- let xOffset = 0;
- let yOffset = 90;
+ // Reel settings
+ REEL_WIDTH: 30,
+ REEL_HEIGHT: 30,
+ REEL_COUNT: 3,
+ SYMBOL_SIZE: 24,
+ SPIN_SPEED: 0.2,
- function createEmptyGrid(width, height) {
- return Array.from({ length: height }, () =>
- Array.from({ length: width }, () => ({ value: 0, hue: 0 }))
- );
- }
+ // Game logic
+ WIN_PROBABILITY: 0.25,
+ SYMBOLS: ['💎', '🔮', '🎭', '🎨', '🎬', '🏆'],
- function drawText(ctx) {
- ctx.font = 'bold 20px Arial';
+ // Button settings
+ BUTTON_WIDTH: 50,
+ BUTTON_HEIGHT: 20,
+ BUTTON_Y_OFFSET: 25,
+
+ // Animation timings
+ SPIN_DURATION: 1000,
+ WIN_ANIMATION_SPEED: 0.02,
+
+ // Visual styles
+ BACKGROUND_COLOR: '#4B0082',
+ FRAME_COLOR: '#8A2BE2',
+ REEL_COLOR: '#2E0854',
+ BUTTON_COLORS: {
+ NORMAL: ['#8A2BE2', '#6A0DAD'],
+ SPINNING: ['#6A0DAD', '#4B0082']
+ },
+ TEXT_COLOR: '#FFFFFF',
+
+ // Particle system
+ MAX_PARTICLES: 100,
+ PARTICLE_DECAY_RATE: 0.02
+ };
+
+ let reels = Array(PARAMS.REEL_COUNT).fill().map(() => ({
+ symbols: [...PARAMS.SYMBOLS],
+ position: 0,
+ spinning: false,
+ finalPosition: 0,
+ offset: 0
+ }));
+
+ let spinning = false;
+ let winningSymbol = null;
+ let winAnimation = null;
+ let winAnimationProgress = 0;
+ let particles = [];
+
+ function drawModernSlotMachine(ctx) {
+ // Background
+ ctx.fillStyle = PARAMS.BACKGROUND_COLOR;
+ ctx.fillRect(0, 0, PARAMS.ANIMATION_WIDTH, PARAMS.ANIMATION_HEIGHT);
+
+ // Frame
+ ctx.strokeStyle = PARAMS.FRAME_COLOR;
+ ctx.lineWidth = 2;
+ ctx.strokeRect(2, 2, PARAMS.ANIMATION_WIDTH - 4, PARAMS.ANIMATION_HEIGHT - 4);
+
+ // Reels
+ const reelStartX = (PARAMS.ANIMATION_WIDTH - (PARAMS.REEL_COUNT * PARAMS.REEL_WIDTH + (PARAMS.REEL_COUNT - 1) * 5)) / 2;
+ reels.forEach((reel, i) => {
+ ctx.fillStyle = PARAMS.REEL_COLOR;
+ ctx.fillRect(reelStartX + i * (PARAMS.REEL_WIDTH + 5), 15, PARAMS.REEL_WIDTH, PARAMS.REEL_HEIGHT);
+
+ ctx.save();
+ ctx.beginPath();
+ ctx.rect(reelStartX + i * (PARAMS.REEL_WIDTH + 5), 15, PARAMS.REEL_WIDTH, PARAMS.REEL_HEIGHT);
+ ctx.clip();
+
+ const symbolIndex = Math.floor(reel.position) % PARAMS.SYMBOLS.length;
+ const y = 30 - (reel.position % 1) * PARAMS.SYMBOL_SIZE + reel.offset;
+ ctx.font = `${PARAMS.SYMBOL_SIZE}px Arial`;
+ ctx.fillStyle = PARAMS.TEXT_COLOR;
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.fillText(reel.symbols[symbolIndex], reelStartX + PARAMS.REEL_WIDTH / 2 + i * (PARAMS.REEL_WIDTH + 5), y);
+
+ ctx.restore();
+ });
+
+ // Spin button
+ const buttonGradient = ctx.createLinearGradient(0, PARAMS.ANIMATION_HEIGHT - PARAMS.BUTTON_Y_OFFSET, 0, PARAMS.ANIMATION_HEIGHT - PARAMS.BUTTON_Y_OFFSET + PARAMS.BUTTON_HEIGHT);
+ const buttonColors = spinning ? PARAMS.BUTTON_COLORS.SPINNING : PARAMS.BUTTON_COLORS.NORMAL;
+ buttonGradient.addColorStop(0, buttonColors[0]);
+ buttonGradient.addColorStop(1, buttonColors[1]);
+ ctx.fillStyle = buttonGradient;
+ ctx.beginPath();
+ ctx.roundRect(PARAMS.ANIMATION_WIDTH / 2 - PARAMS.BUTTON_WIDTH / 2, PARAMS.ANIMATION_HEIGHT - PARAMS.BUTTON_Y_OFFSET, PARAMS.BUTTON_WIDTH, PARAMS.BUTTON_HEIGHT, 5);
+ ctx.fill();
+ ctx.fillStyle = PARAMS.TEXT_COLOR;
+ ctx.font = 'bold 12px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
- ctx.fillStyle = 'white';
- ctx.fillText('CUT', ANIMATION_WIDTH / 2, ANIMATION_HEIGHT / 2 - 15);
- ctx.fillText('THE NOISE', ANIMATION_WIDTH / 2, ANIMATION_HEIGHT / 2 + 15);
- }
+ ctx.fillText('SPIN', PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT - PARAMS.BUTTON_Y_OFFSET + PARAMS.BUTTON_HEIGHT / 2);
- function updateGrid() {
- animationProgress += 0.005; // Slightly faster animation
- if (animationProgress > 1) {
- animationProgress = 0;
- grid = createEmptyGrid(GRID_WIDTH, GRID_HEIGHT);
+ // Win animation
+ if (winAnimation) {
+ winAnimation(ctx, winAnimationProgress);
}
- const tempCanvas = document.createElement('canvas');
- tempCanvas.width = ANIMATION_WIDTH;
- tempCanvas.height = ANIMATION_HEIGHT;
- const tempCtx = tempCanvas.getContext('2d');
- drawText(tempCtx);
+ // Draw particles
+ particles.forEach(p => {
+ ctx.fillStyle = p.color;
+ ctx.beginPath();
+ ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
+ ctx.fill();
+ });
+ }
- const imageData = tempCtx.getImageData(0, 0, ANIMATION_WIDTH, ANIMATION_HEIGHT);
+ function spin() {
+ if (spinning) return;
+ spinning = true;
+ winningSymbol = null;
+ winAnimation = null;
+ winAnimationProgress = 0;
+ particles = [];
- for (let y = 0; y < GRID_HEIGHT; y++) {
- for (let x = 0; x < GRID_WIDTH; x++) {
- const i = (y * ANIMATION_WIDTH + x) * 4;
- if (imageData.data[i] > 0) {
- if (grid[y][x].value === 0) {
- grid[y][x].hue = Math.random() * 360; // Assign a random hue
- }
- if (Math.random() < animationProgress * 0.2) {
- grid[y][x].value = Math.min(grid[y][x].value + 0.2, 1);
- }
+ reels.forEach(reel => {
+ reel.spinning = true;
+ reel.spinDuration = 50 + Math.random() * 50;
+ reel.offset = Math.random() * PARAMS.SYMBOL_SIZE;
+ });
+
+ const isWin = Math.random() < PARAMS.WIN_PROBABILITY;
+ if (isWin) {
+ const winSymbolIndex = Math.floor(Math.random() * PARAMS.SYMBOLS.length);
+ reels.forEach(reel => reel.finalPosition = winSymbolIndex);
+ winningSymbol = PARAMS.SYMBOLS[winSymbolIndex];
+ } else {
+ reels.forEach(reel => reel.finalPosition = Math.floor(Math.random() * PARAMS.SYMBOLS.length));
+ }
+
+ setTimeout(() => {
+ reels.forEach(reel => {
+ reel.spinning = false;
+ reel.position = reel.finalPosition;
+ reel.offset = 0;
+ });
+ spinning = false;
+ if (isWin) triggerWin();
+ }, PARAMS.SPIN_DURATION);
+ }
+
+ function triggerWin() {
+ switch (winningSymbol) {
+ case '💎': winAnimation = diamondWinAnimation; break;
+ case '🔮': winAnimation = crystalBallWinAnimation; break;
+ case '🎭': winAnimation = masksWinAnimation; break;
+ case '🎨': winAnimation = paletteWinAnimation; break;
+ case '🎬': winAnimation = clapperboardWinAnimation; break;
+ case '🏆': winAnimation = trophyWinAnimation; break;
+ }
+ }
+
+ function diamondWinAnimation(ctx, progress) {
+ const size = 40 * Math.sin(progress * Math.PI);
+ ctx.font = `${size}px Arial`;
+ ctx.fillStyle = `rgba(255, 255, 255, ${1 - progress})`;
+ ctx.fillText('💎', PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT / 2);
+
+ // Sparkling effect
+ if (Math.random() < 0.3) {
+ particles.push({
+ x: Math.random() * PARAMS.ANIMATION_WIDTH,
+ y: Math.random() * PARAMS.ANIMATION_HEIGHT,
+ size: Math.random() * 3,
+ color: `hsl(${Math.random() * 360}, 100%, 50%)`,
+ life: 1
+ });
+ }
+ }
+
+ function crystalBallWinAnimation(ctx, progress) {
+ ctx.fillStyle = `rgba(128, 0, 128, ${0.5 - progress * 0.5})`;
+ ctx.beginPath();
+ ctx.arc(PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT / 2, 30 * progress, 0, Math.PI * 2);
+ ctx.fill();
+
+ // Swirling mist effect
+ for (let i = 0; i < 5; i++) {
+ const angle = progress * Math.PI * 2 + i * Math.PI / 2.5;
+ const x = PARAMS.ANIMATION_WIDTH / 2 + Math.cos(angle) * 20 * progress;
+ const y = PARAMS.ANIMATION_HEIGHT / 2 + Math.sin(angle) * 20 * progress;
+ ctx.font = '12px Arial';
+ ctx.fillStyle = `rgba(255, 255, 255, ${1 - progress})`;
+ ctx.fillText('✨', x, y);
+ }
+
+ ctx.font = '24px Arial';
+ ctx.fillStyle = 'white';
+ ctx.fillText('🔮', PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT / 2);
+ }
+
+ function masksWinAnimation(ctx, progress) {
+ const angle = progress * Math.PI * 4;
+ ctx.save();
+ ctx.translate(PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT / 2);
+
+ // Comedy mask
+ ctx.rotate(angle);
+ ctx.font = '24px Arial';
+ ctx.fillStyle = `rgba(255, 255, 0, ${1 - progress})`;
+ ctx.fillText('😂', 0, -20);
+
+ // Tragedy mask
+ ctx.rotate(Math.PI);
+ ctx.fillStyle = `rgba(0, 0, 255, ${1 - progress})`;
+ ctx.fillText('😭', 0, -20);
+
+ ctx.restore();
+
+ // Confetti effect
+ if (Math.random() < 0.2) {
+ particles.push({
+ x: Math.random() * PARAMS.ANIMATION_WIDTH,
+ y: -5,
+ size: Math.random() * 5 + 2,
+ color: Math.random() < 0.5 ? 'yellow' : 'blue',
+ life: 1,
+ vy: Math.random() * 2 + 1
+ });
+ }
+ }
+
+ function paletteWinAnimation(ctx, progress) {
+ const colors = ['red', 'blue', 'green', 'yellow', 'purple'];
+ colors.forEach((color, i) => {
+ const angle = (i / colors.length + progress) * Math.PI * 4;
+ const x = PARAMS.ANIMATION_WIDTH / 2 + Math.cos(angle) * 30 * Math.sin(progress * Math.PI);
+ const y = PARAMS.ANIMATION_HEIGHT / 2 + Math.sin(angle) * 30 * Math.sin(progress * Math.PI);
+ ctx.fillStyle = color;
+ ctx.beginPath();
+ ctx.arc(x, y, 5, 0, Math.PI * 2);
+ ctx.fill();
+
+ // Paint splatter effect
+ if (Math.random() < 0.1) {
+ particles.push({
+ x: x,
+ y: y,
+ size: Math.random() * 3 + 1,
+ color: color,
+ life: 1,
+ vx: (Math.random() - 0.5) * 2,
+ vy: (Math.random() - 0.5) * 2
+ });
+ }
+ });
+
+ ctx.font = '24px Arial';
+ ctx.fillStyle = 'white';
+ ctx.fillText('🎨', PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT / 2);
+ }
+
+ function clapperboardWinAnimation(ctx, progress) {
+ ctx.save();
+ ctx.translate(PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT / 2);
+
+ // Clapping effect
+ const clapAngle = Math.sin(progress * Math.PI * 8) * 0.3;
+ ctx.rotate(clapAngle);
+ ctx.font = '24px Arial';
+ ctx.fillStyle = 'white';
+ ctx.fillText('🎬', 0, 0);
+
+ // Starburst effect
+ for (let i = 0; i < 8; i++) {
+ const angle = i * Math.PI / 4 + progress * Math.PI * 2;
+ const length = 20 + Math.sin(progress * Math.PI) * 10;
+ ctx.strokeStyle = `rgba(255, 255, 0, ${1 - progress})`;
+ ctx.beginPath();
+ ctx.moveTo(0, 0);
+ ctx.lineTo(Math.cos(angle) * length, Math.sin(angle) * length);
+ ctx.stroke();
+ }
+
+ ctx.restore();
+ }
+
+ function trophyWinAnimation(ctx, progress) {
+ const y = PARAMS.ANIMATION_HEIGHT / 2 + Math.sin(progress * Math.PI * 2) * 10;
+ ctx.font = '24px Arial';
+ ctx.fillStyle = `rgba(255, 215, 0, ${1 - progress})`;
+ ctx.fillText('🏆', PARAMS.ANIMATION_WIDTH / 2, y);
+
+ // Fireworks effect
+ if (Math.random() < 0.2) {
+ const x = Math.random() * PARAMS.ANIMATION_WIDTH;
+ const y = PARAMS.ANIMATION_HEIGHT;
+ for (let i = 0; i < 20; i++) {
+ const angle = Math.random() * Math.PI * 2;
+ const speed = Math.random() * 2 + 1;
+ particles.push({
+ x: x,
+ y: y,
+ size: Math.random() * 2 + 1,
+ color: `hsl(${Math.random() * 360}, 100%, 50%)`,
+ life: 1,
+ vx: Math.cos(angle) * speed,
+ vy: Math.sin(angle) * speed - 2
+ });
+ }
+ }
+
+ ctx.font = '16px Arial';
+ ctx.fillStyle = `rgba(255, 255, 255, ${progress})`;
+ ctx.fillText('JACKPOT!', PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT / 2 + 30);
+ }
+
+ function updateAnimation() {
+ reels.forEach(reel => {
+ if (reel.spinning) {
+ reel.position += PARAMS.SPIN_SPEED;
+ if (reel.offset > 0) {
+ reel.offset -= 1;
}
}
+ });
+
+ if (winAnimation) {
+ winAnimationProgress += PARAMS.WIN_ANIMATION_SPEED;
+ if (winAnimationProgress >= 1) {
+ winAnimation = null;
+ winAnimationProgress = 0;
+ }
+ }
+
+ // Update particles
+ particles = particles.filter(p => {
+ p.x += p.vx || 0;
+ p.y += p.vy || 0;
+ p.life -= PARAMS.PARTICLE_DECAY_RATE;
+ return p.life > 0;
+ });
+
+ // Limit the number of particles
+ if (particles.length > PARAMS.MAX_PARTICLES) {
+ particles.splice(0, particles.length - PARAMS.MAX_PARTICLES);
}
}
@@ -75,32 +369,34 @@ function addDenoisingTextAnimatedDisplay(node) {
ctx.save();
const nodeWidth = this.size[0];
- const baseXOffset = (nodeWidth - ANIMATION_WIDTH) / 2;
- ctx.translate(baseXOffset + xOffset, -ANIMATION_HEIGHT + yOffset);
+ const baseXOffset = (nodeWidth - PARAMS.ANIMATION_WIDTH) / 2;
+ ctx.translate(baseXOffset + PARAMS.X_OFFSET, -PARAMS.ANIMATION_HEIGHT + PARAMS.Y_OFFSET);
- ctx.clearRect(0, 0, ANIMATION_WIDTH, ANIMATION_HEIGHT);
-
- // Draw appearing pixels with color and pulse effect
- for (let y = 0; y < GRID_HEIGHT; y++) {
- for (let x = 0; x < GRID_WIDTH; x++) {
- const { value, hue } = grid[y][x];
- if (value > 0) {
- const pulseEffect = 0.7 + 0.3 * Math.sin(animationProgress * 10 + x * 0.1 + y * 0.1);
- const alpha = value * pulseEffect;
- ctx.fillStyle = `hsla(${hue}, 100%, 50%, ${alpha})`;
- ctx.fillRect(x * PIXEL_SIZE, y * PIXEL_SIZE, PIXEL_SIZE, PIXEL_SIZE);
- }
- }
- }
+ drawModernSlotMachine(ctx);
ctx.restore();
- updateGrid();
+ updateAnimation();
this.setDirtyCanvas(true);
requestAnimationFrame(() => this.setDirtyCanvas(true));
}
};
+ node.onMouseDown = function(event) {
+ const nodeWidth = this.size[0];
+ const baseXOffset = (nodeWidth - PARAMS.ANIMATION_WIDTH) / 2;
+ const localX = event.canvasX - this.pos[0] - baseXOffset - PARAMS.X_OFFSET;
+ const localY = event.canvasY - this.pos[1] + PARAMS.ANIMATION_HEIGHT - PARAMS.Y_OFFSET;
+
+ if (localX >= PARAMS.ANIMATION_WIDTH / 2 - PARAMS.BUTTON_WIDTH / 2 &&
+ localX <= PARAMS.ANIMATION_WIDTH / 2 + PARAMS.BUTTON_WIDTH / 2 &&
+ localY >= PARAMS.ANIMATION_HEIGHT - PARAMS.BUTTON_Y_OFFSET &&
+ localY <= PARAMS.ANIMATION_HEIGHT - PARAMS.BUTTON_Y_OFFSET + PARAMS.BUTTON_HEIGHT) {
+ spin();
+ return true;
+ }
+ };
+
node.setDirtyCanvas(true);
}
\ No newline at end of file