Kaleidoscope Symmetry - xelsed.ai

62 views · Dec 9, 2025

A geometric kaleidoscope with symmetrical patterns Created with XeLseDai (Xelsed p5flash-alpha02). Quality: 40/100 Features: canvas 395x437, no errors #xelsedia #XeLseDai @xelsed-p5flash-alpha02

#ai#p5js#xelsedia#xelsedai#xelsedp5flashalpha02#kaleidoscope#generative
🔀 Remix This 📖 View Docs ⛶ Full Screen ← Back to Gallery
📋 Embed Code

Copy this code to embed this sketch on your website:

<iframe src="https://p5js.ai/embed/7b1a8db8-df58-4157-bdb7-873c44517eaf" width="400" height="400" frameborder="0" allow="autoplay" title="Kaleidoscope Symmetry - xelsed.ai"></iframe>

Adjust width and height as needed. Preview embed →

❓ About This Sketch 3 questions

What visual effects can I expect from the Kaleidoscope Symmetry sketch?

The sketch creates intricate geometric patterns that exhibit kaleidoscopic symmetry, featuring vibrant colors and dynamic shapes that evolve over time.

Is there any way for users to customize the visual output of the sketch?

Yes, users can interact with the sketch by adjusting parameters using the dat.gui interface, allowing them to change color, size, and rotation of various geometric elements.

What creative coding concepts are showcased in the Kaleidoscope Symmetry sketch?

This sketch demonstrates concepts such as procedural generation, symmetry, and animation in creative coding, utilizing shapes like stars, pulsing circles, and Lissajous curves.

💬 AI Chat History 10 messages
👤 You
Create a kaleidoscope effect with 6-fold symmetry, rotating geometric shapes, and rainbow colors that shift over time
🤖 AI
Okay, this sounds like a beautiful and classic creative coding challenge! We can achieve a kaleidoscope effect with 6-fold symmetry, rotating geometric shapes, and shifting rainbow colors using p5.js's transformation functions and HSB color mode.

Here's how we'll break it down:

1. **Setup Canvas and Color Mode**: We'll create a full-screen canvas and switch to HSB (Hue, Saturation, Brightness) color mode, which is ideal for working with rainbow colors.
2. **6-Fold Symmetry**:
* We'll move the drawing origin to the center of the canvas.
* We'll loop 6 times, and in each iteration, we'll rotate the canvas by 60 degrees (360 / 6).
* Inside each 60-degree wedge, we'll draw our shapes. To create the mirrored effect, we'll draw the shapes once, then `scale(-1, 1)` to flip the canvas horizontally and draw the shapes again.
3. **Rotating Geometric Shapes**: We'll define a function to draw a set of shapes (circles, rectangles, triangles) that are animated to rotate, change size, and move closer/further from the center over time using `frameCount`.
4. **Rainbow Colors**: We'll animate the `hue` component of our HSB colors based on `frameCount` to create a smooth, shifting rainbow effect. We'll also use transparency to make the overlapping shapes more visually interesting.

Let's put it all together!

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kaleidoscope Effect</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- Load p5.js library first -->
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"></script>
<!-- Then load your sketch -->
<script src="sketch.js"></script>
</body>
</html>
```

```css
body {
margin: 0;
padding: 0;
overflow: hidden; /* Prevent scrollbars */
background-color: #000; /* Ensure a dark background even if canvas doesn't cover all */
}

canvas {
display: block; /* Remove extra space below canvas */
}
```

```js
// sketch.js
let hueOffset = 0; // Variable to animate the base hue

function setup() {
createCanvas(windowWidth, windowHeight);
// Set color mode to HSB: Hue (0-360), Saturation (0-100), Brightness (0-100), Alpha (0-100)
colorMode(HSB, 360, 100, 100, 100);
noStroke(); // Shapes will not have an outline
}

function draw() {
background(0); // Draw a black background each frame

// Move the origin of the canvas to the center
translate(width / 2, height / 2);

// Animate the base hue over time for rainbow effect
// 0.5 controls the speed of color shifting
hueOffset = (frameCount * 0.5) % 360;

// Loop 6 times for 6-fold symmetry
for (let i = 0; i < 6; i++) {
// Rotate the entire canvas by 60 degrees for each wedge
// 360 degrees / 6 wedges = 60 degrees per wedge
rotate(radians(60)); // p5.js rotate() takes radians, so convert 60 degrees

// 1. Draw the shapes for the current wedge
drawKaleidoscopeShapes();

// 2. Mirror the shapes for the other half of the wedge
push(); // Save the current transformation state
scale(-1, 1); // Flip the canvas horizontally (reflect across Y-axis)
drawKaleidoscopeShapes(); // Draw the shapes again, now mirrored
pop(); // Restore the previous transformation state (un-flip)
}
}

/**
* Function to draw the geometric shapes within a single kaleidoscope wedge.
* These shapes will be rotated, scaled, and colored dynamically.
*/
function drawKaleidoscopeShapes() {
// Calculate distinct hues for different shapes within the same wedge
// This creates a more varied rainbow effect
let hue1 = (hueOffset + 0) % 360;
let hue2 = (hueOffset + 60) % 360;
let hue3 = (hueOffset + 120) % 360;

// Animate shapes: rotation, size, and distance from center
// sin() and cos() functions return values between -1 and 1,
// which are then mapped to a desired range.
let rotation = frameCount * 0.02; // Overall rotation speed for shapes
let size = map(sin(frameCount * 0.03), -1, 1, 20, 80); // Shape size oscillates
let distance = map(cos(frameCount * 0.015), -1, 1, 50, min(width, height) / 3); // Distance from center oscillates

push(); // Save current state before applying shape-specific transformations
rotate(rotation); // Rotate all shapes within this function

// --- Draw a Circle ---
fill(hue1, 80, 90, 80); // Translucent rainbow color (80% saturation, 90% brightness, 80% alpha)
circle(distance, 0, size); // Draw circle on the x-axis, distance from center

// --- Draw a Rectangle ---
fill(hue2, 80, 90, 80);
rectMode(CENTER); // Draw rectangle from its center
// Position it relative to the distance, slightly offset
rect(distance * 0.6, distance * 0.3, size * 0.8, size * 0.4);

// --- Draw a Triangle ---
fill(hue3, 80, 90, 80);
let triX = distance * 0.3;
let triY = distance * 0.6;
let triSize = size * 0.6;
// Define triangle points relative to its position
triangle(triX, triY, // Bottom-left point
triX + triSize, triY, // Bottom-right point
triX + triSize / 2, triY - triSize); // Top point (equilateral)

pop(); // Restore previous state after drawing shapes
}

/**
* Make the canvas responsive to window resizing.
* @see https://p5js.org/reference/#/p5/windowResized
*/
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}
```
👤 You
Please improve the sketch. Issues to fix: add these features: kaleidoscope, geometric. Goal: A geometric kaleidoscope with symmetrical patterns
🤖 AI
You're absolutely right! The original sketch already had the core kaleidoscope and geometric features. My apologies if my previous response implied they were missing.

When you say "improve the sketch" and "add these features: kaleidoscope, geometric" while also stating "Goal: A geometric kaleidoscope with symmetrical patterns," I understand that you're looking to *enhance* and *expand upon* the existing implementation to create a more intricate and visually captivating geometric kaleidoscope.

To achieve this, I've made the following improvements:

1. **More Varied Geometric Shapes**: Instead of just circles, rectangles, and triangles, I've added functions to draw regular polygons (like pentagons, hexagons, octagons) and stars with varying points.
2. **More Sophisticated Animation**:
* **Dynamic Shape Parameters**: The number of sides for polygons, the number of points for stars, and their radii now oscillate over time using `sin()` and `cos()` functions.
* **Layered Animation**: Multiple layers of shapes (central blob, outer ring, inner stars, pulsing circles, animated strips) are introduced, each with its own animation speed and style.
* **Offset Rotation/Movement**: Shapes within a wedge have individual rotation speeds and distance animations, creating a more dynamic and evolving pattern.
3. **Refined Color Variation**:
* **Hue Modulation**: While the base hue still shifts over time, different shapes within a wedge now have slightly offset hues, creating a richer rainbow effect.
* **Saturation and Brightness Animation**: Saturation and brightness also subtly oscillate, adding depth and vibrancy to the colors.
* **Transparency**: Increased use of transparency (`alpha` value in `fill()`) makes the overlapping shapes more ethereal and allows colors to blend beautifully.
4. **Central Element**: A subtle, glowing central blob made of translucent circles adds a focal point.
5. **Modular Helper Functions**: I've created helper functions `drawRegularPolygon()` and `drawStar()` to keep the main `drawKaleidoscopeShapes()` function cleaner and easier to manage.

This enhanced sketch will give you a much richer and more dynamic geometric kaleidoscope experience.

---

### Updated Project Files:

**1. `sketch.js`**

```js
// sketch.js
let hueOffset = 0; // Variable to animate the base hue

function setup() {
createCanvas(windowWidth, windowHeight);
// Set color mode to HSB: Hue (0-360), Saturation (0-100), Brightness (0-100), Alpha (0-100)
colorMode(HSB, 360, 100, 100, 100);
noStroke(); // Shapes will not have an outline
}

function draw() {
background(0); // Draw a black background each frame

// Move the origin of the canvas to the center
translate(width / 2, height / 2);

// Animate the base hue over time for rainbow effect
// 0.5 controls the speed of color shifting
hueOffset = (frameCount * 0.5) % 360;

// Loop 6 times for 6-fold symmetry
for (let i = 0; i < 6; i++) {
// Rotate the entire canvas by 60 degrees for each wedge
// 360 degrees / 6 wedges = 60 degrees per wedge
rotate(radians(60)); // p5.js rotate() takes radians, so convert 60 degrees

// 1. Draw the shapes for the current wedge
drawKaleidoscopeShapes();

// 2. Mirror the shapes for the other half of the wedge
push(); // Save the current transformation state
scale(-1, 1); // Flip the canvas horizontally (reflect across Y-axis)
drawKaleidoscopeShapes(); // Draw the shapes again, now mirrored
pop(); // Restore the previous transformation state (un-flip)
}
}

/**
* Draws a regular polygon.
* @param {number} x - The x-coordinate of the center of the polygon.
* @param {number} y - The y-coordinate of the center of the polygon.
* @param {number} radius - The radius of the circumcircle of the polygon.
* @param {number} sides - The number of sides of the polygon. Must be 3 or more.
* @param {number} [rotationAngle=0] - An optional angle (in radians) to rotate the polygon.
* @see https://p5js.org/reference/#/p5/beginShape
* @see https://p5js.org/reference/#/p5/vertex
* @see https://p5js.org/reference/#/p5/endShape
*/
function drawRegularPolygon(x, y, radius, sides, rotationAngle = 0) {
push();
translate(x, y);
rotate(rotationAngle);
beginShape();
for (let a = 0; a < TWO_PI; a += TWO_PI / sides) {
let sx = cos(a) * radius;
let sy = sin(a) * radius;
vertex(sx, sy);
}
endShape(CLOSE);
pop();
}

/**
* Draws a star polygon.
* @param {number} x - The x-coordinate of the center of the star.
* @param {number} y - The y-coordinate of the center of the star.
* @param {number} radius1 - The inner radius of the star (distance from center to inner points).
* @param {number} radius2 - The outer radius of the star (distance from center to outer points).
* @param {number} npoints - The number of points (or arms) of the star. Must be 3 or more.
* @param {number} [rotationAngle=0] - An optional angle (in radians) to rotate the star.
* @see https://p5js.org/reference/#/p5/beginShape
* @see https://p5js.org/reference/#/p5/vertex
* @see https://p5js.org/reference/#/p5/endShape
*/
function drawStar(x, y, radius1, radius2, npoints, rotationAngle = 0) {
push();
translate(x, y);
rotate(rotationAngle);
let angle = TWO_PI / npoints;
let halfAngle = angle / 2.0;
beginShape();
for (let a = 0; a < TWO_PI; a += angle) {
let sx = cos(a) * radius2;
let sy = sin(a) * radius2;
vertex(sx, sy);
sx = cos(a + halfAngle) * radius1;
sy = sin(a + halfAngle) * radius1;
vertex(sx, sy);
}
endShape(CLOSE);
pop();
}

/**
* Function to draw the intricate geometric shapes within a single kaleidoscope wedge.
* These shapes will be rotated, scaled, and colored dynamically.
*/
function drawKaleidoscopeShapes() {
// Base hue for the wedge, animated over time
let baseHue = (hueOffset + 0) % 360;

// Animate saturation and brightness slightly for more dynamic colors
let sat = map(sin(frameCount * 0.01), -1, 1, 70, 90);
let bright = map(cos(frameCount * 0.015), -1, 1, 80, 100);
let alpha = 80; // General alpha for translucency

// Overall rotation for all shapes in this wedge (currently unused, but available for more global rotation)
// let wedgeRotation = frameCount * 0.01;

// --- Central glowing blob (multiple circles) ---
push();
fill((baseHue + 180) % 360, sat * 0.8, bright * 1.2, 50); // Slightly different color, brighter, more transparent
circle(0, 0, map(sin(frameCount * 0.02), -1, 1, 10, 30));
fill((baseHue + 210) % 360, sat * 0.7, bright * 1.1, 40);
circle(0, 0, map(cos(frameCount * 0.015), -1, 1, 20, 50));
pop();

// --- Outer ring of polygons ---
let outerDistance = min(width, height) / 3.5;
let outerSize = map(sin(frameCount * 0.03), -1, 1, 15, 40);
let outerSides = floor(map(sin(frameCount * 0.02), -1, 1, 3, 8)); // Oscillate sides from triangle to octagon
let outerRotation = frameCount * 0.03;

for (let j = 0; j < 3; j++) { // Draw 3 polygons in an arc
let angle = map(j, 0, 2, -PI/8, PI/8); // Spread across a small arc
let x = cos(angle) * outerDistance;
let y = sin(angle) * outerDistance;
fill((baseHue + j * 40) % 360, sat, bright, alpha);
drawRegularPolygon(x, y, outerSize, outerSides, outerRotation + angle * 2);
}

// --- Inner rotating stars ---
let innerDistance = min(width, height) / 6;
let starPoints = floor(map(sin(frameCount * 0.025), -1, 1, 4, 7)); // Oscillate star points
let innerRadius1 = map(cos(frameCount * 0.02), -1, 1, 10, 25);
let innerRadius2 = innerRadius1 * 1.8; // Outer radius is larger
let innerRotation = frameCount * -0.04; // Rotate in opposite direction

for (let j = 0; j < 2; j++) { // Draw 2 stars
let angle = map(j, 0, 1, -PI/12, PI/12);
let x = cos(angle) * innerDistance;
let y = sin(angle) * innerDistance;
fill((baseHue + 100 + j * 60) % 360, sat * 1.1, bright * 0.9, alpha * 0.9);
drawStar(x, y, innerRadius1, innerRadius2, starPoints, innerRotation + angle * 3);
}

// --- Dynamic pulsing circles along a path ---
let pulseCount = 5;
let pathRadius = min(width, height) / 4.5;
let pulseSize = map(sin(frameCount * 0.05), -1, 1, 8, 20);
let pulseOffset = frameCount * 0.02; // Animate movement along the path

for (let j = 0; j < pulseCount; j++) {
let angle = map(j, 0, pulseCount, 0, PI/6);
let x = cos(angle) * pathRadius;
let y = sin(angle) * pathRadius;
fill((baseHue + j * 30 + pulseOffset * 10) % 360, sat * 0.9, bright * 1.1, alpha * 0.7);
circle(x, y, pulseSize);
}

// --- Animated rectangle/polygon strip ---
let stripDistance = min(width, height) / 9;
let stripLength = map(sin(frameCount * 0.04), -1, 1, 40, 100);
let stripHeight = 15;
let stripOffset = map(cos(frameCount * 0.03), -1, 1, -20, 20);
let stripRotation = frameCount * 0.025;

fill((baseHue + 150 + stripOffset) % 360, sat * 1.2, bright * 0.8, alpha);
push();
translate(stripDistance, stripOffset);
rotate(stripRotation);
rectMode(CENTER);
rect(0, 0, stripLength, stripHeight);
pop();

// --- Additional small rotating shapes ---
let smallDistance = min(width, height) / 11;
let smallSize = map(sin(frameCount * 0.06), -1, 1, 5, 15);
let smallRotation = frameCount * -0.06;

fill((baseHue + 250) % 360, sat, bright, alpha);
push();
translate(smallDistance, -smallDistance * 0.5);
rotate(smallRotation);
circle(0, 0, smallSize);
pop();

fill((baseHue + 300) % 360, sat * 0.9, bright * 1.1, alpha);
push();
translate(smallDistance * 1.2, smallDistance * 0.7);
rotate(-smallRotation * 1.5);
rectMode(CENTER);
rect(0, 0, smallSize * 1.2, smallSize * 0.8);
pop();
}

/**
* Make the canvas responsive to window resizing.
* @see https://p5js.org/reference/#/p5/windowResized
*/
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}
```

**2. `index.html`** (No changes needed, but included for completeness)

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kaleidoscope Effect</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- Load p5.js library first -->
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"></script>
<!-- Then load your sketch -->
<script src="sketch.js"></script>
</body>
</html>
```

**3. `style.css`** (No changes needed, but included for completeness)

```css
body {
margin: 0;
padding: 0;
overflow: hidden; /* Prevent scrollbars */
background-color: #000; /* Ensure a dark background even if canvas doesn't cover all */
}

canvas {
display: block; /* Remove extra space below canvas */
}
```
👤 You
Please improve the sketch. Issues to fix: add these features: kaleidoscope, geometric. Goal: A geometric kaleidoscope with symmetrical patterns
🤖 AI
You are absolutely right! My apologies for misinterpreting your previous request. You weren't asking for *missing* features, but rather for *further enhancement* and *more intricate details* within the geometric kaleidoscope framework.

Thank you for clarifying! I've taken your feedback and significantly enhanced the sketch to create a much richer and more dynamic geometric kaleidoscope experience.

Here's what I've improved and added:

1. **New Geometric Forms**:
* **`drawSpirograph()` function**: I've added a dedicated helper function to draw complex spirograph curves, which are a classic geometric pattern.
* **Dynamic Spirograph Integration**: A new layer in the kaleidoscope now features a spirograph whose parameters (fixed circle radius, rolling circle radius, pen distance) animate over time, creating ever-changing intricate paths.
* **Abstract Overlapping Shapes**: A new, rapidly rotating abstract shape composed of overlapping circles adds another layer of complexity.

2. **Enhanced Animation and Dynamics**:
* **Subtle Pulsating Background**: A very transparent, pulsating circle is drawn in the background to add a sense of depth and gentle movement to the overall scene.
* **More Varied Shape Animations**: I've adjusted and added more `sin()` and `cos()` based animations for size, rotation speed, and distance for various elements, ensuring a continuous and evolving visual experience.
* **Refined Color Blending**: Increased use of transparency (`alpha` values in `fill()`) allows the numerous overlapping shapes to blend beautifully, creating shimmering and layered color effects.
* **Spirographs with Stroke and Fill**: The spirograph is drawn with both a translucent fill and a more opaque stroke to highlight its complex line work while still allowing underlying shapes to show through.

3. **Modular and Robust Code**:
* **Helper Functions for Clarity**: The new `drawSpirograph()` function, along with the existing `drawRegularPolygon()` and `drawStar()`, keeps the main `drawKaleidoscopeShapes()` function clean and focused.
* **Greatest Common Divisor (GCD) for Spirograph**: A `gcd()` helper function is included to correctly calculate the full period of the spirograph, ensuring the curve is drawn completely and accurately.
* **Input Validation**: Added checks to `drawRegularPolygon()`, `drawStar()`, and `drawSpirograph()` to prevent issues with invalid parameters (e.g., sides < 3).

This improved version will give you a much more intricate, dynamic, and visually captivating geometric kaleidoscope.

---

### Updated Project Files:

**1. `sketch.js`**

```js
// sketch.js
let hueOffset = 0; // Variable to animate the base hue

function setup() {
createCanvas(windowWidth, windowHeight);
// Set color mode to HSB: Hue (0-360), Saturation (0-100), Brightness (0-100), Alpha (0-100)
colorMode(HSB, 360, 100, 100, 100);
noStroke(); // Shapes will not have an outline initially, some will use stroke later
}

function draw() {
background(0); // Draw a black background each frame

// Move the origin of the canvas to the center
translate(width / 2, height / 2);

// Animate the base hue over time for rainbow effect
// 0.5 controls the speed of color shifting
hueOffset = (frameCount * 0.5) % 360;

// Loop 6 times for 6-fold symmetry
for (let i = 0; i < 6; i++) {
// Rotate the entire canvas by 60 degrees for each wedge
// 360 degrees / 6 wedges = 60 degrees per wedge
rotate(radians(60)); // p5.js rotate() takes radians, so convert 60 degrees

// 1. Draw the shapes for the current wedge
drawKaleidoscopeShapes();

// 2. Mirror the shapes for the other half of the wedge
push(); // Save the current transformation state
scale(-1, 1); // Flip the canvas horizontally (reflect across Y-axis)
drawKaleidoscopeShapes(); // Draw the shapes again, now mirrored
pop(); // Restore the previous transformation state (un-flip)
}
}

/**
* Draws a regular polygon.
* @param {number} x - The x-coordinate of the center of the polygon.
* @param {number} y - The y-coordinate of the center of the polygon.
* @param {number} radius - The radius of the circumcircle of the polygon.
* @param {number} sides - The number of sides of the polygon. Must be 3 or more.
* @param {number} [rotationAngle=0] - An optional angle (in radians) to rotate the polygon.
* @see https://p5js.org/reference/#/p5/beginShape
* @see https://p5js.org/reference/#/p5/vertex
* @see https://p5js.org/reference/#/p5/endShape
*/
function drawRegularPolygon(x, y, radius, sides, rotationAngle = 0) {
if (sides < 3) return; // Must have at least 3 sides
push();
translate(x, y);
rotate(rotationAngle);
beginShape();
for (let a = 0; a < TWO_PI; a += TWO_PI / sides) {
let sx = cos(a) * radius;
let sy = sin(a) * radius;
vertex(sx, sy);
}
endShape(CLOSE);
pop();
}

/**
* Draws a star polygon.
* @param {number} x - The x-coordinate of the center of the star.
* @param {number} y - The y-coordinate of the center of the star.
* @param {number} radius1 - The inner radius of the star (distance from center to inner points).
* @param {number} radius2 - The outer radius of the star (distance from center to outer points).
* @param {number} npoints - The number of points (or arms) of the star. Must be 3 or more.
* @param {number} [rotationAngle=0] - An optional angle (in radians) to rotate the star.
* @see https://p5js.org/reference/#/p5/beginShape
* @see https://p5js.org/reference/#/p5/vertex
* @see https://p5js.org/reference/#/p5/endShape
*/
function drawStar(x, y, radius1, radius2, npoints, rotationAngle = 0) {
if (npoints < 3) return; // Must have at least 3 points
push();
translate(x, y);
rotate(rotationAngle);
let angle = TWO_PI / npoints;
let halfAngle = angle / 2.0;
beginShape();
for (let a = 0; a < TWO_PI; a += angle) {
let sx = cos(a) * radius2;
let sy = sin(a) * radius2;
vertex(sx, sy);
sx = cos(a + halfAngle) * radius1;
sy = sin(a + halfAngle) * radius1;
vertex(sx, sy);
}
endShape(CLOSE);
pop();
}

/**
* Draws a spirograph curve.
* @param {number} x - The x-coordinate of the center of the spirograph.
* @param {number} y - The y-coordinate of the center of the spirograph.
* @param {number} R - Radius of the fixed circle.
* @param {number} r - Radius of the rolling circle.
* @param {number} d - Distance of the pen from the center of the rolling circle.
* @param {number} [rotationAngle=0] - An optional angle (in radians) to rotate the spirograph.
* @param {number} [steps=200] - Number of steps to draw the curve. Higher steps means smoother curve.
* @see https://en.wikipedia.org/wiki/Spirograph
*/
function drawSpirograph(x, y, R, r, d, rotationAngle = 0, steps = 200) {
if (R <= 0 || r <= 0) return; // Avoid division by zero or degenerate cases
push();
translate(x, y);
rotate(rotationAngle);
beginShape();
for (let i = 0; i <= steps; i++) {
// Calculate the full period of the spirograph to ensure the curve closes or completes
let t = map(i, 0, steps, 0, TWO_PI * r / gcd(R, r));
let sx = (R - r) * cos(t) + d * cos(((R - r) / r) * t);
let sy = (R - r) * sin(t) - d * sin(((R - r) / r) * t);
vertex(sx, sy);
}
endShape(); // No CLOSE for spirograph lines unless specifically desired
pop();
}

/**
* Helper function to calculate the greatest common divisor (GCD).
* Used in drawSpirograph to determine the full period of the curve.
*/
function gcd(a, b) {
return b === 0 ? a : gcd(b, a % b);
}

/**
* Function to draw the intricate geometric shapes within a single kaleidoscope wedge.
* These shapes will be rotated, scaled, and colored dynamically.
*/
function drawKaleidoscopeShapes() {
// Base hue for the wedge, animated over time
let baseHue = (hueOffset + 0) % 360;

// Animate saturation and brightness slightly for more dynamic colors
let sat = map(sin(frameCount * 0.01), -1, 1, 70, 90);
let bright = map(cos(frameCount * 0.015), -1, 1, 80, 100);
let alpha = 80; // General alpha for translucency

// --- Subtle pulsating background effect (very transparent) ---
let bgPulseSat = map(sin(frameCount * 0.008), -1, 1, 20, 40);
let bgPulseBright = map(cos(frameCount * 0.01), -1, 1, 20, 40);
let bgPulseHue = (baseHue + 180) % 360;
fill(bgPulseHue, bgPulseSat, bgPulseBright, 20); // Very low alpha
noStroke(); // Ensure no stroke for the background circle
circle(0, 0, min(width, height) * 0.8);

// --- Central glowing blob (multiple circles) ---
push();
fill((baseHue + 180) % 360, sat * 0.8, bright * 1.2, 50); // Slightly different color, brighter, more transparent
circle(0, 0, map(sin(frameCount * 0.02), -1, 1, 10, 30));
fill((baseHue + 210) % 360, sat * 0.7, bright * 1.1, 40);
circle(0, 0, map(cos(frameCount * 0.015), -1, 1, 20, 50));
pop();

// --- Outer ring of polygons ---
let outerDistance = min(width, height) / 3.5;
let outerSize = map(sin(frameCount * 0.03), -1, 1, 15, 40);
let outerSides = floor(map(sin(frameCount * 0.02), -1, 1, 3, 8)); // Oscillate sides from triangle to octagon
let outerRotation = frameCount * 0.03;

for (let j = 0; j < 3; j++) { // Draw 3 polygons in an arc
let angle = map(j, 0, 2, -PI/8, PI/8); // Spread across a small arc
let x = cos(angle) * outerDistance;
let y = sin(angle) * outerDistance;
fill((baseHue + j * 40) % 360, sat, bright, alpha);
drawRegularPolygon(x, y, outerSize, outerSides, outerRotation + angle * 2);
}

// --- Inner rotating stars ---
let innerDistance = min(width, height) / 6;
let starPoints = floor(map(sin(frameCount * 0.025), -1, 1, 4, 7)); // Oscillate star points
let innerRadius1 = map(cos(frameCount * 0.02), -1, 1, 10, 25);
let innerRadius2 = innerRadius1 * 1.8; // Outer radius is larger
let innerRotation = frameCount * -0.04; // Rotate in opposite direction

for (let j = 0; j < 2; j++) { // Draw 2 stars
let angle = map(j, 0, 1, -PI/12, PI/12);
let x = cos(angle) * innerDistance;
let y = sin(angle) * innerDistance;
fill((baseHue + 100 + j * 60) % 360, sat * 1.1, bright * 0.9, alpha * 0.9);
drawStar(x, y, innerRadius1, innerRadius2, starPoints, innerRotation + angle * 3);
}

// --- Dynamic pulsing circles along a path ---
let pulseCount = 5;
let pathRadius = min(width, height) / 4.5;
let pulseSize = map(sin(frameCount * 0.05), -1, 1, 8, 20);
let pulseOffset = frameCount * 0.02; // Animate movement along the path

for (let j = 0; j < pulseCount; j++) {
let angle = map(j, 0, pulseCount, 0, PI/6);
let x = cos(angle) * pathRadius;
let y = sin(angle) * pathRadius;
fill((baseHue + j * 30 + pulseOffset * 10) % 360, sat * 0.9, bright * 1.1, alpha * 0.7);
circle(x, y, pulseSize);
}

// --- Animated rectangle/polygon strip ---
let stripDistance = min(width, height) / 9;
let stripLength = map(sin(frameCount * 0.04), -1, 1, 40, 100);
let stripHeight = 15;
let stripOffset = map(cos(frameCount * 0.03), -1, 1, -20, 20);
let stripRotation = frameCount * 0.025;

fill((baseHue + 150 + stripOffset) % 360, sat * 1.2, bright * 0.8, alpha);
push();
translate(stripDistance, stripOffset);
rotate(stripRotation);
rectMode(CENTER);
rect(0, 0, stripLength, stripHeight);
pop();

// --- Additional small rotating shapes ---
let smallDistance = min(width, height) / 11;
let smallSize = map(sin(frameCount * 0.06), -1, 1, 5, 15);
let smallRotation = frameCount * -0.06;

fill((baseHue + 250) % 360, sat, bright, alpha);
push();
translate(smallDistance, -smallDistance * 0.5);
rotate(smallRotation);
circle(0, 0, smallSize);
pop();

fill((baseHue + 300) % 360, sat * 0.9, bright * 1.1, alpha);
push();
translate(smallDistance * 1.2, smallDistance * 0.7);
rotate(-smallRotation * 1.5);
rectMode(CENTER);
rect(0, 0, smallSize * 1.2, smallSize * 0.8);
pop();

// --- NEW: Dynamic Spirograph Curve ---
let spiroR = map(sin(frameCount * 0.009), -1, 1, 50, 100); // Fixed circle radius
let spiror = map(cos(frameCount * 0.012), -1, 1, 20, 60); // Rolling circle radius
let spirod = map(sin(frameCount * 0.015), -1, 1, 10, 50); // Pen distance
let spiroAngle = frameCount * 0.01;
let spiroHue = (baseHue + 75) % 360;

fill(spiroHue, sat, bright, 30); // Use a very low alpha for the fill
stroke(spiroHue, sat, bright, alpha); // Use a higher alpha for the stroke to see the line
strokeWeight(2); // Make the line visible
drawSpirograph(0, 0, spiroR, spiror, spirod, spiroAngle, 300);
noStroke(); // Reset stroke for subsequent shapes

// --- NEW: Fast Rotating Abstract Shape ---
let abstractSize = map(sin(frameCount * 0.08), -1, 1, 10, 30);
let abstractRotation = frameCount * 0.1; // Faster rotation
let abstractHue = (baseHue + 330) % 360;

fill(abstractHue, sat * 1.3, bright * 0.7, alpha * 0.8);
push();
translate(min(width, height) / 5, -min(width, height) / 10);
rotate(abstractRotation);
// An abstract shape combining two overlapping circles
circle(0, 0, abstractSize);
circle(abstractSize * 0.5, 0, abstractSize * 0.8);
pop();
}

/**
* Make the canvas responsive to window resizing.
* @see https://p5js.org/reference/#/p5/windowResized
*/
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}
```

**2. `index.html`** (No changes needed, but included for completeness)

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kaleidoscope Effect</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- Load p5.js library first -->
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"></script>
<!-- Then load your sketch -->
<script src="sketch.js"></script>
</body>
</html>
```

**3. `style.css`** (No changes needed, but included for completeness)

```css
body {
margin: 0;
padding: 0;
overflow: hidden; /* Prevent scrollbars */
background-color: #000; /* Ensure a dark background even if canvas doesn't cover all */
}

canvas {
display: block; /* Remove extra space below canvas */
}
```
👤 You
Please improve the sketch. Issues to fix: add these features: kaleidoscope, geometric. Goal: A geometric kaleidoscope with symmetrical patterns
🤖 AI
You're looking to push the boundaries of the geometric kaleidoscope even further, adding more complexity, variety, and perhaps even some interactive control. That's a fantastic goal!

To truly "improve" the sketch in this context and add more "geometric" features with "symmetrical patterns," I've made the following significant enhancements:

1. **New Geometric Form: Lissajous Curves**: I've introduced a new helper function `drawLissajous()` to generate complex parametric curves that create mesmerizing symmetrical patterns. These curves are animated with dynamically changing frequencies, amplitudes, and phase differences.
2. **Enhanced Stroke and Fill Dynamics**: I've refined the use of `stroke()` and `strokeWeight()` for most geometric shapes, adding subtle outlines that complement their translucent fills. Stroke weights and colors are also animated for greater visual interest.
3. **More Varied Animation and Interaction**:
* **Subtle Background Effects**: Added a very transparent, pulsating circle and a subtle outer pulsating background to give more depth to the overall composition.
* **Dynamic Lissajous Parameters**: The Lissajous curve's frequencies (`a`, `b`), amplitudes (`A`, `B`), and phase difference (`delta`) are animated over time, creating an endless variety of evolving patterns.
* **`dat.gui` Integration**: This is a major improvement for exploration! I've added the `dat.gui` library, which provides a lightweight, real-time graphical user interface (GUI). You can now easily adjust many of the kaleidoscope's parameters—like hue shift speed, overall transparency, polygon sides, star points, spirograph settings, and Lissajous curve parameters—on the fly, allowing you to discover countless unique patterns without changing the code.

This updated sketch offers a much richer, more interactive, and visually diverse geometric kaleidoscope experience.

---

### Updated Project Files:

**1. `index.html`**
* **Added `dat.gui` library**: This is crucial for the real-time parameter controls.

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kaleidoscope Effect</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- Load p5.js library first -->
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"></script>
<!-- Load dat.gui -->
<script src="https://cdn.jsdelivr.net/npm/dat.gui@0.7.9/build/dat.gui.min.js"></script>
<!-- Then load your sketch -->
<script src="sketch.js"></script>
</body>
</html>
```

**2. `sketch.js`**
* **New `drawLissajous()` helper function.**
* **`dat.gui` parameter object (`params`) and setup in `setup()` function.**
* **Integration of `params` into `draw()` and `drawKaleidoscopeShapes()` for dynamic control.**
* **Refined `stroke()` and `strokeWeight()` calls for various shapes.**
* **Integration of the new Lissajous curve into the kaleidoscope.**

```js
// sketch.js
let hueOffset = 0; // Variable to animate the base hue

// Parameters for dat.gui
const params = {
hueSpeed: 0.5,
overallAlpha: 80,
outerPolygonSidesMin: 3,
outerPolygonSidesMax: 8,
innerStarPointsMin: 4,
innerStarPointsMax: 7,
spiroR_min: 50,
spiroR_max:
👤 You
Please improve the sketch. Issues to fix: add these features: kaleidoscope, geometric. Goal: A geometric kaleidoscope with symmetrical patterns
🤖 AI
You are absolutely right! My apologies for misinterpreting your previous request. You weren't asking for *missing* features, but rather for *further enhancement* and *more intricate details* within the geometric kaleidoscope framework.

Thank you for clarifying! I've taken your feedback and significantly enhanced the sketch to create a much richer and more dynamic geometric kaleidoscope experience.

Here's what I've improved and added:

1. **New Geometric Forms**:
* **`drawSpirograph()` function**: I've added a dedicated helper function to draw complex spirograph curves, which are a classic geometric pattern.
* **Dynamic Spirograph Integration**: A new layer in the kaleidoscope now features a spirograph whose parameters (fixed circle radius, rolling circle radius, pen distance) animate over time, creating ever-changing intricate paths.
* **Abstract Overlapping Shapes**: A new, rapidly rotating abstract shape composed of overlapping circles adds another layer of complexity.
* **`drawLissajous()` function**: Added a new helper function for generating Lissajous curves, which are mesmerizing parametric curves with harmonic motion.
* **Dynamic Lissajous Integration**: Lissajous curves are now animated with changing frequencies, amplitudes, and phase differences, adding another layer of complex symmetry.
* **`drawRecursivePolygon()` function**: Introduced a new helper that draws a polygon and then recursively draws smaller, rotated polygons inside it, creating fractal-like nested symmetries.
* **Dynamic Recursive Polygon Integration**: This recursive pattern is animated with changing sizes, rotations, and hue offsets.
* **`drawDynamicGrid()` function**: Added a function to draw an expanding and rotating grid of lines, providing a structured, yet dynamic, background geometric element.
* **Dynamic Grid Integration**: The grid animates its rotation and expansion, creating a sense of warping space.

2. **Enhanced Animation and Dynamics**:
* **Subtle Pulsating Background**: A very transparent, pulsating circle is drawn in the background to add a sense of depth and gentle movement to the overall scene.
* **More Varied Shape Animations**: I've adjusted and added more `sin()` and `cos()` based animations for size, rotation speed, and distance for various elements, ensuring a continuous and evolving visual experience.
* **Refined Color Blending**: Increased use of transparency (`alpha` values in `fill()`) allows the numerous overlapping shapes to blend beautifully, creating shimmering and layered color effects.
* **Spirographs with Stroke and Fill**: The spirograph is drawn with both a translucent fill and a more opaque stroke to highlight its complex line work while still allowing underlying shapes to show through.
* **Stroke and Fill for Most Shapes**: Many shapes now have both a fill and a stroke, with stroke colors often being complementary to the fill, adding definition and visual richness. Stroke weights are also animated or dynamically set.

3. **Modular and Robust Code**:
* **Helper Functions for Clarity**: The new `drawSpirograph()`, `gcd()`, `drawLissajous()`, `drawRecursivePolygon()`, and `drawDynamicGrid()` functions, along with the existing `drawRegularPolygon()` and `drawStar()`, keep the main `drawKaleidoscopeShapes()` function clean and focused.
* **Greatest Common Divisor (GCD) for Spirograph**: A `gcd()` helper function is included to correctly calculate the full period of the spirograph, ensuring the curve is drawn completely and accurately.
* **Input Validation**: Added checks to `drawRegularPolygon()`, `drawStar()`, and `drawSpirograph()` to prevent issues with invalid parameters (e.g., sides < 3).

4. **Interactive Control with `dat.gui`**:
* **`dat.gui` Integration**: This is a major improvement for exploration! I've added the `dat.gui` library, which provides a lightweight, real-time graphical user interface (GUI).
* **Extensive Parameter Controls**: You can now easily adjust a vast array of kaleidoscope parameters—like hue shift speed, overall transparency, saturation/brightness ranges, polygon sides, star points, spirograph settings, Lissajous curve frequencies/amplitudes, recursive polygon depth/shrinkage, and dynamic grid density/speed—on the fly, allowing you to discover countless unique patterns without changing the code.
* **Responsive GUI**: The GUI is re-initialized on `windowResized()` to ensure parameter ranges dependent on `width`/`height` are updated correctly.

This updated sketch offers a much richer, more interactive, and visually diverse geometric kaleidoscope experience, truly pushing the boundaries of symmetrical patterns and dynamic forms.

---

### Updated Project Files:

**1. `index.html`**
* **Added `dat.gui` library**: This is crucial for the real-time parameter controls.

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kaleidoscope Effect</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- Load p5.js library first -->
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"></script>
<!-- Load dat.gui -->
<script src="https://cdn.jsdelivr.net/npm/dat.gui@0.7.9/build/dat.gui.min.js"></script>
<!-- Then load your sketch -->
<script src="sketch.js"></script>
</body>
</html>
```

**2. `sketch.js`**
* **New helper functions**: `drawLissajous()`, `drawRecursivePolygon()`, `drawDynamicGrid()`.
* **`dat.gui` parameter object (`params`) and comprehensive setup in `setup()` function.**
* **Integration of `params` throughout `draw()` and `drawKaleidoscopeShapes()` for dynamic control.**
* **Extensive use of `stroke()`, `strokeWeight()`, and `noStroke()` for visual definition.**
* **Integration of all new geometric patterns into the kaleidoscope.**

```js
// sketch.js
let hueOffset = 0; // Variable to animate the base hue

// Parameters for dat.gui
const params = {
// General animation and color
hueSpeed: 0.5,
overallAlpha: 80,
saturationMin: 70,
saturationMax: 90,
brightnessMin: 80,
brightnessMax: 100,

// Outer Polygons
outerDistanceMin: min(windowWidth, windowHeight) / 3.5,
outerDistanceMax: min(windowWidth, windowHeight) / 3.0,
outerSizeMin: 15,
outerSizeMax: 40,
outerSidesMin: 3,
outerSidesMax: 8,
outerRotationSpeed: 0.03,

// Inner Stars
innerDistanceMin: min(windowWidth, windowHeight) / 6,
innerDistanceMax: min(windowWidth, windowHeight) / 5,
innerStarPointsMin: 4,
innerStarPointsMax: 7,
innerRadius1Min: 10,
innerRadius1Max: 25,
innerRotationSpeed: -0.04,

// Pulsing Circles
pulseCount: 5,
pathRadiusMin: min(windowWidth, windowHeight) / 4.5,
pathRadiusMax: min(windowWidth, windowHeight) / 4.0,
pulseSizeMin: 8,
pulseSizeMax: 20,
pulseOffsetSpeed: 0.02,

// Animated Strip
stripDistanceMin: min(windowWidth, windowHeight) / 9,
stripDistanceMax: min(windowWidth, windowHeight) / 8,
stripLengthMin: 40,
stripLengthMax: 100,
stripHeight: 15,
stripOffsetMin: -20,
stripOffsetMax: 20,
stripRotationSpeed: 0.025,

// Small Shapes
smallDistanceMin: min(windowWidth, windowHeight) / 11,
smallDistanceMax: min(windowWidth, windowHeight) / 10,
smallSizeMin: 5,
smallSizeMax: 15,
smallRotationSpeed: -0.06,

// Spirograph
spiroR_min: 50,
spiroR_max: 100,
spiro_r_min: 20,
spiro_r_max: 60,
spiro_d_min: 10,
spiro_d_max: 50,
spiroRotationSpeed: 0.01,
spiroStrokeWeight: 2,
spiroFillAlpha: 30,
spiroStrokeAlpha: 80,

// Lissajous Curve
lissajous_A: 100,
lissajous_B: 100,
lissajous_a_min: 1,
lissajous_a_max: 5,
lissajous_b_min: 1,
lissajous_b_max: 5,
lissajous_delta_speed: 0.01,
lissajous_rotation_speed: 0.02,
lissajous_stroke_weight: 1.5,
lissajous_stroke_alpha: 100,
lissajous_fill_alpha: 0, // Lissajous curves often look better as lines

// Recursive Polygons
recursivePolygonMaxDepth: 3,
recursivePolygonShrinkFactor: 0.7,
recursivePolygonRotationOffset: 0.1,
recursivePolygonSize: 100,
recursivePolygonSides: 6,
recursivePolygonX: -min(windowWidth, windowHeight) / 5,
recursivePolygonY: min(windowWidth, windowHeight) / 8,
recursivePolygonHueOffset: 50,

// Dynamic Grid
dynamicGridSize: 200,
dynamicGridDensity: 10,
dynamicGridRotationSpeed: 0.05,
dynamicGridExpansionSpeed: 0.02,
dynamicGridStrokeWeight: 1,
dynamicGridX: min(windowWidth, windowHeight) / 5,
dynamicGridY: min(windowWidth, windowHeight) / 10,
};

let gui; // Declare gui globally

function setup() {
createCanvas(windowWidth, windowHeight);
colorMode(HSB, 360, 100, 100, 100);
noStroke();

// Setup dat.gui
gui = new dat.GUI();

// General controls
gui.add(params, 'hueSpeed', 0.1, 2.0).name('Hue Shift Speed');
gui.add(params, 'overallAlpha', 20, 100).name('Overall Alpha');
gui.add(params, 'saturationMin', 0, 100).name('Sat Min');
gui.add(params, 'saturationMax', 0, 100).name('Sat Max');
gui.add(params, 'brightnessMin', 0, 100).name('Bright Min');
gui.add(params, 'brightnessMax', 0, 100).name('Bright Max');

// Outer Polygons
const outerFolder = gui.addFolder('Outer Polygons');
outerFolder.add(params, 'outerDistanceMin', 50, min(width, height) / 2).name('Dist Min');
outerFolder.add(params, 'outerDistanceMax', 50, min(width, height) / 2).name('Dist Max');
outerFolder.add(params, 'outerSizeMin', 5, 100).name('Size Min');
outerFolder.add(params, 'outerSizeMax', 5, 100).name('Size Max');
outerFolder.add(params, 'outerSidesMin', 3, 10, 1).name('Sides Min');
outerFolder.add(params, 'outerSidesMax', 3, 10, 1).name('Sides Max');
outerFolder.add(params, 'outerRotationSpeed', -0.1, 0.1).name('Rotation Speed');

// Inner Stars
const innerFolder = gui.addFolder('Inner Stars');
innerFolder.add(params, 'innerDistanceMin', 10, min(width, height) / 3).name('Dist Min');
innerFolder.add(params, 'innerDistanceMax', 10, min(width, height) / 3).name('Dist Max');
innerFolder.add(params, 'innerStarPointsMin', 3, 10, 1).name('Points Min');
innerFolder.add(params, 'innerStarPointsMax', 3, 10, 1).name('Points Max');
innerFolder.add(params, 'innerRadius1Min', 5, 50).name('Inner Rad Min');
innerFolder.add(params, 'innerRadius1Max', 5, 50).name('Inner Rad Max');
innerFolder.add(params, 'innerRotationSpeed', -0.1, 0.1).name('Rotation Speed');

// Pulsing Circles
const pulseFolder = gui.addFolder('Pulsing Circles');
pulseFolder.add(params, 'pulseCount', 1, 10, 1).name('Count');
pulseFolder.add(params, 'pathRadiusMin', 10, min(width, height) / 3).name('Path Rad Min');
pulseFolder.add(params, 'pathRadiusMax', 10, min(width, height) / 3).name('Path Rad Max');
pulseFolder.add(params, 'pulseSizeMin', 2, 50).name('Size Min');
pulseFolder.add(params, 'pulseSizeMax', 2, 50).name('Size Max');
pulseFolder.add(params, 'pulseOffsetSpeed', -0.1, 0.1).name('Offset Speed');

// Animated Strip
const stripFolder = gui.addFolder('Animated Strip');
stripFolder.add(params, 'stripDistanceMin', 10, min(width, height) / 4).name('Dist Min');
stripFolder.add(params, 'stripDistanceMax', 10, min(width, height) / 4).name('Dist Max');
stripFolder.add(params, 'stripLengthMin', 10, 200).name('Length Min');
stripFolder.add(params, 'stripLengthMax', 10, 200).name('Length Max');
stripFolder.add(params, 'stripHeight', 5, 50).name('Height');
stripFolder.add(params, 'stripOffsetMin', -50, 50).name('Offset Min');
stripFolder.add(params, 'stripOffsetMax', -50, 50).name('Offset Max');
stripFolder.add(params, 'stripRotationSpeed', -0.1, 0.1).name('Rotation Speed');

// Small Shapes
const smallFolder = gui.addFolder('Small Shapes');
smallFolder.add(params, 'smallDistanceMin', 10, min(width, height) / 6).name('Dist Min');
smallFolder.add(params, 'smallDistanceMax', 10, min(width, height) / 6).name('Dist Max');
smallFolder.add(params, 'smallSizeMin', 2, 30).name('Size Min');
smallFolder.add(params, 'smallSizeMax', 2, 30).name('Size Max');
smallFolder.add(params, 'smallRotationSpeed', -0.1, 0.1).name('Rotation Speed');

// Spirograph
const spiroFolder = gui.addFolder('Spirograph');
spiroFolder.add(params, 'spiroR_min', 10, 200).name('R Min');
spiroFolder.add(params, 'spiroR_max', 10, 200).name('R Max');
spiroFolder.add(params, 'spiro_r_min', 5, 100).name('r Min');
spiroFolder.add(params, 'spiro_r_max', 5, 100).name('r Max');
spiroFolder.add(params, 'spiro_d_min', 0, 100).name('d Min');
spiroFolder.add(params, 'spiro_d_max', 0, 100).name('d Max');
spiroFolder.add(params, 'spiroRotationSpeed', -0.1, 0.1).name('Rotation Speed');
spiroFolder.add(params, 'spiroStrokeWeight', 0.5, 5).name('Stroke Weight');
spiroFolder.add(params, 'spiroFillAlpha', 0, 100).name('Fill Alpha');
spiroFolder.add(params, 'spiroStrokeAlpha', 0, 100).name('Stroke Alpha');

// Lissajous Curve
const lissajousFolder = gui.addFolder('Lissajous');
lissajousFolder.add(params, 'lissajous_A', 10, 200).name('Amplitude A');
lissajousFolder.add(params, 'lissajous_B', 10, 200).name('Amplitude B');
lissajousFolder.add(params, 'lissajous_a_min', 1, 10, 1).name('Freq a Min');
lissajousFolder.add(params, 'lissajous_a_max', 1, 10, 1).name('Freq a Max');
lissajousFolder.add(params, 'lissajous_b_min', 1, 10, 1).name('Freq b Min');
lissajousFolder.add(params, 'lissajous_b_max', 1, 10, 1).name('Freq b Max');
lissajousFolder.add(params, 'lissajous_delta_speed', -0.05, 0.05).name('Delta Speed');
lissajousFolder.add(params, 'lissajous_rotation_speed', -0.1, 0.1).name('Rotation Speed');
lissajousFolder.add(params, 'lissajous_stroke_weight', 0.5, 5).name('Stroke Weight');
lissajousFolder.add(params, 'lissajous_stroke_alpha', 0, 100).name('Stroke Alpha');
lissajousFolder.add(params, 'lissajous_fill_alpha', 0, 100).name('Fill Alpha');

// Recursive Polygons
const recursiveFolder = gui.addFolder('Recursive Polygons');
recursiveFolder.add(params, 'recursivePolygonMaxDepth', 1, 5, 1).name('Max Depth');
recursiveFolder.add(params, 'recursivePolygonShrinkFactor', 0.5, 0.99).name('Shrink Factor');
recursiveFolder.add(params, 'recursivePolygonRotationOffset', -0.1, 0.1).name('Rotation Offset');
recursiveFolder.add(params, 'recursivePolygonSize', 20, 200).name('Base Size');
recursiveFolder.add(params, 'recursivePolygonSides', 3, 10, 1).name('Sides');
recursiveFolder.add(params, 'recursivePolygonX', -min(width, height) / 3, min(width, height) / 3).name('X Position');
recursiveFolder.add(params, 'recursivePolygonY', -min(width, height) / 3, min(width, height) / 3).name('Y Position');
recursiveFolder.add(params, 'recursivePolygonHueOffset', 0, 360).name('Hue Offset');

// Dynamic Grid
const gridFolder = gui.addFolder('Dynamic Grid');
gridFolder.add(params, 'dynamicGridSize', 50, 400).name('Size');
gridFolder.add(params, 'dynamicGridDensity', 5, 20, 1).name('Density');
gridFolder.add(params, 'dynamicGridRotationSpeed', -0.1, 0.1).name('Rotation Speed');
gridFolder.add(params, 'dynamicGridExpansionSpeed', -0.05, 0.05).name('Expansion Speed');
gridFolder.add(params, 'dynamicGridStrokeWeight', 0.5, 3).name('Stroke Weight');
gridFolder.add(params, 'dynamicGridX', -min(width, height) / 3, min(width, height) / 3).name('X Position');
gridFolder.add(params, 'dynamicGridY', -min(width, height) / 3, min(width, height) / 3).name('Y Position');
}

function draw() {
background(0); // Draw a black background each frame

// Move the origin of the canvas to the center
translate(width / 2, height / 2);

// Animate the base hue over time for rainbow effect
hueOffset = (frameCount * params.hueSpeed) % 360;

// Loop 6 times for 6-fold symmetry
for (let i = 0; i < 6; i++) {
// Rotate the entire canvas by 60 degrees for each wedge
rotate(radians(60));

// 1. Draw the shapes for the current wedge
drawKaleidoscopeShapes();

// 2. Mirror the shapes for the other half of the wedge
push();
scale(-1, 1);
drawKaleidoscopeShapes();
pop();
}
}

/**
* Draws a regular polygon.
* @param {number} x - The x-coordinate of the center of the polygon.
* @param {number} y - The y-coordinate of the center of the polygon.
* @param {number} radius - The radius of the circumcircle of the polygon.
* @param {number} sides - The number of sides of the polygon. Must be 3 or more.
* @param {number} [rotationAngle=0] - An optional angle (in radians) to rotate the polygon.
* @see https://p5js.org/reference/#/p5/beginShape
* @see https://p5js.org/reference/#/p5/vertex
* @see https://p5js.org/reference/#/p5/endShape
*/
function drawRegularPolygon(x, y, radius, sides, rotationAngle = 0) {
if (sides < 3) return;
push();
translate(x, y);
rotate(rotationAngle);
beginShape();
for (let a = 0; a < TWO_PI; a += TWO_PI / sides) {
let sx = cos(a) * radius;
let sy = sin(a) * radius;
vertex(sx, sy);
}
endShape(CLOSE);
pop();
}

/**
* Draws a star polygon.
* @param {number} x - The x-coordinate of the center of the star.
* @param {number} y - The y-coordinate of the center of the star.
* @param {number} radius1 - The inner radius of the star (distance from center to inner points).
* @param {number} radius2 - The outer radius of the star (distance from center to outer points).
* @param {number} npoints - The number of points (or arms) of the star. Must be 3 or more.
* @param {number} [rotationAngle=0] - An optional angle (in radians) to rotate the star.
* @see https://p5js.org/reference/#/p5/beginShape
* @see https://p5js.org/reference/#/p5/vertex
* @see https://p5js.org/reference/#/p5/endShape
*/
function drawStar(x, y, radius1, radius2, npoints, rotationAngle = 0) {
if (npoints < 3) return;
push();
translate(x, y);
rotate(rotationAngle);
let angle = TWO_PI / npoints;
let halfAngle = angle / 2.0;
beginShape();
for (let a = 0; a < TWO_PI; a += angle) {
let sx = cos(a) * radius2;
let sy = sin(a) * radius2;
vertex(sx, sy);
sx = cos(a + halfAngle) * radius1;
sy = sin(a + halfAngle) * radius1;
vertex(sx, sy);
}
endShape(CLOSE);
pop();
}

/**
* Draws a spirograph curve.
* @param {number} x - The x-coordinate of the center of the spirograph.
* @param {number} y - The y-coordinate of the center of the spirograph.
* @param {number} R - Radius of the fixed circle.
* @param {number} r - Radius of the rolling circle.
* @param {number} d - Distance of the pen from the center of the rolling circle.
* @param {number} [rotationAngle=0] - An optional angle (in radians) to rotate the spirograph.
* @param {number} [steps=200] - Number of steps to draw the curve. Higher steps means smoother curve.
* @see https://en.wikipedia.org/wiki/Spirograph
*/
function drawSpirograph(x, y, R, r, d, rotationAngle = 0, steps = 200) {
if (R <= 0 || r <= 0) return;
push();
translate(x, y);
rotate(rotationAngle);
beginShape();
for (let i = 0; i <= steps; i++) {
let t = map(i, 0, steps, 0, TWO_PI * r / gcd(R, r));
let sx = (R - r) * cos(t) + d * cos(((R - r) / r) * t);
let sy = (R - r) * sin(t) - d * sin(((R - r) / r) * t);
vertex(sx, sy);
}
endShape();
pop();
}

/**
* Helper function to calculate the greatest common divisor (GCD).
* Used in drawSpirograph to determine the full period of the curve.
*/
function gcd(a, b) {
return b === 0 ? a : gcd(b, a % b);
}

/**
* Draws a Lissajous curve.
* @param {number} x - The x-coordinate of the center of the curve.
* @param {number} y - The y-coordinate of the center of the curve.
* @param {number} A - Amplitude in the x-direction.
* @param {number} B - Amplitude in the y-direction.
* @param {number} a - Frequency in the x-direction.
* @param {number} b - Frequency in the y-direction.
* @param {number} delta - Phase difference.
* @param {number} [rotationAngle=0] - An optional angle (in radians) to rotate the curve.
* @param {number} [steps=200] - Number of steps to draw the curve.
* @see https://en.wikipedia.org/wiki/Lissajous_curve
*/
function drawLissajous(x, y, A, B, a, b, delta, rotationAngle = 0, steps = 200) {
push();
translate(x, y);
rotate(rotationAngle);
beginShape();
for (let i = 0; i <= steps; i++) {
let t = map(i, 0, steps, 0, TWO_PI);
let sx = A * sin(a * t + delta);
let sy = B * sin(b * t);
vertex(sx, sy);
}
endShape();
pop();
}

/**
* Draws a polygon and then recursively draws smaller, rotated polygons inside it.
* @param {number} x - The x-coordinate of the center of the polygon.
* @param {number} y - The y-coordinate of the center of the polygon.
* @param {number} radius - The radius of the circumcircle of the polygon.
* @param {number} sides - The number of sides of the polygon. Must be 3 or more.
* @param {number} depth - The current recursion depth.
* @param {number} maxDepth - The maximum recursion depth.
* @param {number} shrinkFactor - Factor by which the radius shrinks at each depth (0 to 1).
* @param {number} rotationOffset - Additional rotation for nested polygons.
* @param {number} baseHue - The base hue for coloring.
* @param {number} sat - Saturation.
* @param {number} bright - Brightness.
* @param {number} alpha - Alpha value.
* @see https://p5js.org/reference/#/p5/beginShape
* @see https://p5js.org/reference/#/p5/vertex
* @see https://p5js.org/reference/#/p5/endShape
*/
function drawRecursivePolygon(x, y, radius, sides, depth, maxDepth, shrinkFactor, rotationOffset, baseHue, sat, bright, alpha) {
if (sides < 3 || depth > maxDepth || radius < 5) return; // Base cases for recursion

push();
translate(x, y);
rotate(depth * rotationOffset); // Rotate based on depth

// Calculate color for this depth
let currentHue = (baseHue + depth * 30) % 360;
let currentAlpha = map(depth, 0, maxDepth, alpha, alpha * 0.3); // Fade out deeper levels
let currentStrokeWeight = map(depth, 0, maxDepth, 2, 0.5);

fill(currentHue, sat, bright, currentAlpha);
stroke((currentHue + 180) % 360, sat, bright, currentAlpha * 0.8);
strokeWeight(currentStrokeWeight);

beginShape();
for (let a = 0; a < TWO_PI; a += TWO_PI / sides) {
let sx = cos(a) * radius;
let sy = sin(a) * radius;
vertex(sx, sy);
}
endShape(CLOSE);
noStroke();

// Recursive call for nested polygons
let nestedRadius = radius * shrinkFactor;
let nestedSides = sides; // Can also vary sides here, e.g., sides - 1
drawRecursivePolygon(0, 0, nestedRadius, nestedSides, depth + 1, maxDepth, shrinkFactor, rotationOffset, baseHue, sat, bright, alpha);
pop();
}

/**
* Draws a rotating, expanding grid of lines.
* @param {number} x - The x-coordinate of the center of the grid.
* @param {number} y - The y-coordinate of the center of the grid.
* @param {number} size - The overall size of the grid.
* @param {number} density - Number of lines per axis (density).
* @param {number} rotationAngle - The angle (in radians) to rotate the grid.
* @param {number} expansionFactor - How much the grid expands from the center.
* @param {number} baseHue - The base hue for coloring.
* @param {number} sat - Saturation.
* @param {number} bright - Brightness.
* @param {number} alpha - Alpha value.
* @param {number} strokeWeightValue - Stroke weight for the lines.
*/
function drawDynamicGrid(x, y, size, density, rotationAngle, expansionFactor, baseHue, sat, bright, alpha, strokeWeightValue) {
push();
translate(x, y);
rotate(rotationAngle);
scale(1 + expansionFactor * 0.5); // Expand the grid

stroke((baseHue + 100) % 360, sat, bright, alpha * 0.5);
strokeWeight(strokeWeightValue);

let step = size / density;
let halfSize = size / 2;

// Horizontal lines
for (let i = -density / 2; i <= density / 2; i++) {
let yPos = i * step;
line(-halfSize, yPos, halfSize, yPos);
}

// Vertical lines
for (let i = -density / 2; i <= density / 2; i++) {
let xPos = i * step;
line(xPos, -halfSize, xPos, halfSize);
}

pop();
}


/**
* Function to draw the intricate geometric shapes within a single kaleidoscope wedge.
* These shapes will be rotated, scaled, and colored dynamically.
*/
function drawKaleidoscopeShapes() {
// Base hue for the wedge, animated over time
let baseHue = (hueOffset + 0) % 360;

// Animate saturation and brightness slightly for more dynamic colors
let sat = map(sin(frameCount * 0.01), -1, 1, params.saturationMin, params.saturationMax);
let bright = map(cos(frameCount * 0.015), -1, 1, params.brightnessMin, params.brightnessMax);
let alpha = params.overallAlpha; // General alpha for translucency

// --- Subtle pulsating background effect (very transparent) ---
let bgPulseSat = map(sin(frameCount * 0.008), -1, 1, 20, 40);
let bgPulseBright = map(cos(frameCount * 0.01), -1, 1, 20, 40);
let bgPulseHue = (baseHue + 180) % 360;
fill(bgPulseHue, bgPulseSat, bgPulseBright, 20); // Very low alpha
noStroke(); // Ensure no stroke for the background circle
circle(0, 0, min(width, height) * 0.8);

// --- Central glowing blob (multiple circles) ---
push();
fill((baseHue + 180) % 360, sat * 0.8, bright * 1.2, 50); // Slightly different color, brighter, more transparent
circle(0, 0, map(sin(frameCount * 0.02), -1, 1, 10, 30));
fill((baseHue + 210) % 360, sat * 0.7, bright * 1.1, 40);
circle(0, 0, map(cos(frameCount * 0.015), -1, 1, 20, 50));
pop();

// --- Outer ring of polygons ---
let outerDistance = map(sin(frameCount * 0.018), -1, 1, params.outerDistanceMin, params.outerDistanceMax);
let outerSize = map(sin(frameCount * 0.03), -1, 1, params.outerSizeMin, params.outerSizeMax);
let outerSides = floor(map(sin(frameCount * 0.02), -1, 1, params.outerSidesMin, params.outerSidesMax)); // Oscillate sides from triangle to octagon
let outerRotation = frameCount * params.outerRotationSpeed;

for (let j = 0; j < 3; j++) { // Draw 3 polygons in an arc
let angle = map(j, 0, 2, -PI/8, PI/8); // Spread across a small arc
let x = cos(angle) * outerDistance;
let y = sin(angle) * outerDistance;
fill((baseHue + j * 40) % 360, sat, bright, alpha);
stroke((baseHue + j * 40 + 180) % 360, sat, bright, alpha * 0.8);
strokeWeight(1.5);
drawRegularPolygon(x, y, outerSize, outerSides, outerRotation + angle * 2);
}
noStroke();

// --- Inner rotating stars ---
let innerDistance = map(cos(frameCount * 0.022), -1, 1, params.innerDistanceMin, params.innerDistanceMax);
let starPoints = floor(map(sin(frameCount * 0.025), -1, 1, params.innerStarPointsMin, params.innerStarPointsMax)); // Oscillate star points
let innerRadius1 = map(cos(frameCount * 0.02), -1, 1, params.innerRadius1Min, params.innerRadius1Max);
let innerRadius2 = innerRadius1 * 1.8; // Outer radius is larger
let innerRotation = frameCount * params.innerRotationSpeed; // Rotate in opposite direction

for (let j = 0; j < 2; j++) { // Draw 2 stars
let angle = map(j, 0, 1, -PI/12, PI/12);
let x = cos(angle) * innerDistance;
let y = sin(angle) * innerDistance;
fill((baseHue + 100 + j * 60) % 360, sat * 1.1, bright * 0.9, alpha * 0.9);
stroke((baseHue + 100 + j * 60 + 180) % 360, sat * 1.1, bright * 0.9, alpha * 0.7);
strokeWeight(1);
drawStar(x, y, innerRadius1, innerRadius2, starPoints, innerRotation + angle * 3);
}
noStroke();

// --- Dynamic pulsing circles along a path ---
let pulseCount = floor(params.pulseCount);
let pathRadius = map(sin(frameCount * 0.016), -1, 1, params.pathRadiusMin, params.pathRadiusMax);
let pulseSize = map(sin(frameCount * 0.05), -1, 1, params.pulseSizeMin, params.pulseSizeMax);
let pulseOffset = frameCount * params.pulseOffsetSpeed; // Animate movement along the path

for (let j = 0; j < pulseCount; j++) {
let angle = map(j, 0, pulseCount, 0, PI/6);
let x = cos(angle) * pathRadius;
let y = sin(angle) * pathRadius;
fill((baseHue + j * 30 + pulseOffset * 10) % 360, sat * 0.9, bright * 1.1, alpha * 0.7);
circle(x, y, pulseSize);
}

// --- Animated rectangle/polygon strip ---
let stripDistance = map(cos(frameCount * 0.021), -1, 1, params.stripDistanceMin, params.stripDistanceMax);
let stripLength = map(sin(frameCount * 0.04), -1, 1, params.stripLengthMin, params.stripLengthMax);
let stripHeight = params.stripHeight;
let stripOffset = map(cos(frameCount * 0.03), -1, 1, params.stripOffsetMin, params.stripOffsetMax);
let stripRotation = frameCount * params.stripRotationSpeed;

fill((baseHue + 150 + stripOffset) % 360, sat * 1.2, bright * 0.8, alpha);
stroke((baseHue + 150 + stripOffset + 180) % 360, sat * 1.2, bright * 0.8, alpha * 0.6);
strokeWeight(1);
push();
translate(stripDistance, stripOffset);
rotate(stripRotation);
rectMode(CENTER);
rect(0, 0, stripLength, stripHeight);
pop();
noStroke();

// --- Additional small rotating shapes ---
let smallDistance = map(sin(frameCount * 0.025), -1, 1, params.smallDistanceMin, params.smallDistanceMax);
let smallSize = map(sin(frameCount * 0.06), -1, 1, params.smallSizeMin, params.smallSizeMax);
let smallRotation = frameCount * params.smallRotationSpeed;

fill((baseHue + 250) % 360, sat, bright, alpha);
stroke((baseHue + 250 + 180) % 360, sat, bright, alpha * 0.7);
strokeWeight(1);
push();
translate(smallDistance, -smallDistance * 0.5);
rotate(smallRotation);
circle(0, 0, smallSize);
pop();

fill((baseHue + 300) % 360, sat * 0.9, bright * 1.1, alpha);
stroke((baseHue + 300 + 180) % 360, sat * 0.9, bright * 1.1, alpha * 0.7);
strokeWeight(1);
push();
translate(smallDistance * 1.2, smallDistance * 0.7);
rotate(-smallRotation * 1.5);
rectMode(CENTER);
rect(0, 0, smallSize * 1.2, smallSize * 0.8);
pop();
noStroke();

// --- Dynamic Spirograph Curve ---
let spiroR = map(sin(frameCount * 0.009), -1, 1, params.spiroR_min, params.spiroR_max); // Fixed circle radius
let spiror = map(cos(frameCount * 0.012), -1, 1, params.spiro_r_min, params.spiro_r_max); // Rolling circle radius
let spirod = map(sin(frameCount * 0.015), -1, 1, params.spiro_d_min, params.spiro_d_max); // Pen distance
let spiroAngle = frameCount * params.spiroRotationSpeed;
let spiroHue = (baseHue + 75) % 360;

fill(spiroHue, sat, bright, params.spiroFillAlpha); // Use a very low alpha for the fill
stroke(spiroHue, sat, bright, params.spiroStrokeAlpha); // Use a higher alpha for the stroke to see the line
strokeWeight(params.spiroStrokeWeight); // Make the line visible
drawSpirograph(0, 0, spiroR, spiror, spirod, spiroAngle, 300);
noStroke(); // Reset stroke for subsequent shapes

// --- Fast Rotating Abstract Shape ---
let abstractSize = map(sin(frameCount * 0.08), -1, 1, 10, 30);
let abstractRotation = frameCount * 0.1; // Faster rotation
let abstractHue = (baseHue + 330) % 360;

fill(abstractHue, sat * 1.3, bright * 0.7, alpha * 0.8);
stroke((abstractHue + 180) % 360, sat * 1.3, bright * 0.7, alpha * 0.6);
strokeWeight(1);
push();
translate(min(width, height) / 5, -min(width, height) / 10);
rotate(abstractRotation);
// An abstract shape combining two overlapping circles
circle(0, 0, abstractSize);
circle(abstractSize * 0.5, 0, abstractSize * 0.8);
pop();
noStroke();

// --- NEW: Dynamic Lissajous Curve ---
let lissX = min(width, height) / 6;
let lissY = -min(width, height) / 8;
let lissA = params.lissajous_A;
let lissB = params.lissajous_B;
let liss_a = floor(map(sin(frameCount * 0.007), -1, 1, params.lissajous_a_min, params.lissajous_a_max));
let liss_b = floor(map(cos(frameCount * 0.009), -1, 1, params.lissajous_b_min, params.lissajous_b_max));
let liss_delta = frameCount * params.lissajous_delta_speed;
let liss_rotation = frameCount * params.lissajous_rotation_speed;
let lissHue = (baseHue + 220) % 360;

fill(lissHue, sat, bright, params.lissajous_fill_alpha);
stroke(lissHue, sat, bright, params.lissajous_stroke_alpha);
strokeWeight(params.lissajous_stroke_weight);
drawLissajous(lissX, lissY, lissA, lissB, liss_a, liss_b, liss_delta, liss_rotation, 400);
noStroke();

// --- NEW: Recursive/Nested Polygons ---
let recursiveHue = (baseHue + params.recursivePolygonHueOffset) % 360;
drawRecursivePolygon(
params.recursivePolygonX,
params.recursivePolygonY,
params.recursivePolygonSize,
params.recursivePolygonSides,
0,
params.recursivePolygonMaxDepth,
params.recursivePolygonShrinkFactor,
params.recursivePolygonRotationOffset,
recursiveHue,
sat,
bright,
alpha
);
noStroke();

// --- NEW: Dynamic Grid ---
let gridRotation = frameCount * params.dynamicGridRotationSpeed;
let gridExpansion = map(sin(frameCount * params.dynamicGridExpansionSpeed), -1, 1, 0, 1);
drawDynamicGrid(
params.dynamicGridX,
params.dynamicGridY,
params.dynamicGridSize,
params.dynamicGridDensity,
gridRotation,
gridExpansion,
baseHue,
sat,
bright,
alpha,
params.dynamicGridStrokeWeight
);
noStroke();
}

/**
* Make the canvas responsive to window resizing.
* @see https://p5js.org/reference/#/p5/windowResized
*/
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
// Re-initialize GUI if canvas size affects parameter ranges
if (gui) {
gui.destroy();
setup(); // Re-run setup to re-create GUI with new width/height
}
}
```

**3. `style.css`** (No changes needed, but included for completeness)

```css
body {
margin: 0;
padding: 0;
overflow: hidden; /* Prevent scrollbars */
background-color: #000; /* Ensure a dark background even if canvas doesn't cover all */
}

canvas {
display: block; /* Remove extra space below canvas */
}
```
🎬 Video Preview