<h1>Cicada Principle Interactive Demo</h1>
<h2>Explore generative art using prime number patterns</h2>

<div class="tabs">
	<div class="tab active" data-tab="grid">CSS Grid</div>
	<div class="tab" data-tab="canvas">Canvas Particles</div>
	<div class="tab" data-tab="svg">SVG Patterns</div>
</div>

<!-- CSS Grid Demo Content -->
<div id="grid-content" class="content active">
	<div class="controls">
		<div class="control-group">
			<label>
				<input type="checkbox" id="transforms-toggle" /> Enable Transforms
			</label>
		</div>
		<div class="control-group">
			<label>
				<input type="checkbox" id="radius-toggle" /> Enable Border Radius
			</label>
		</div>
		<div class="control-group">
			<label>
				<input type="checkbox" id="animation-toggle" /> Enable Animations
			</label>
		</div>
	</div>

	<div class="grid-container" id="grid"></div>
</div>

<!-- Canvas Particles Demo Content -->
<div id="canvas-content" class="content">
	<div class="canvas-controls">
		<div class="canvas-control">
			<label for="particles">Particles</label>
			<input type="range" id="particles" min="50" max="300" value="150">
		</div>
		<div class="canvas-control">
			<label for="speed">Speed</label>
			<input type="range" id="speed" min="1" max="10" value="3">
		</div>
		<div class="canvas-control">
			<label for="size">Size</label>
			<input type="range" id="size" min="1" max="10" value="4">
		</div>
		<button id="color-button">Change Colors</button>
	</div>

	<canvas id="particleCanvas" width="800" height="400"></canvas>
</div>

<!-- SVG Patterns Demo Content -->
<div id="svg-content" class="content">
	<div class="svg-container">
		<svg id="patternSvg" width="500" height="500" viewBox="0 0 500 500"></svg>
	</div>

	<div class="canvas-controls">
		<button id="generate-pattern">Generate New Pattern</button>
		<button id="toggle-animation">Toggle Animation</button>
	</div>
</div>
body {
	font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
		Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
	background-color: #f5f5f5;
	margin: 0;
	padding: 20px;
	color: #333;
}

h1 {
	text-align: center;
	margin-bottom: 10px;
}

h2 {
	text-align: center;
	font-weight: normal;
	margin-top: 0;
	margin-bottom: 30px;
	color: #666;
	font-size: 1.1rem;
}

.tabs {
	display: flex;
	justify-content: center;
	margin-bottom: 20px;
}

.tab {
	padding: 10px 20px;
	background: #e0e0e0;
	cursor: pointer;
	margin: 0 5px;
	border-radius: 5px 5px 0 0;
}

.tab.active {
	background: white;
	box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.05);
}

.content {
	display: none;
	max-width: 900px;
	margin: 0 auto 40px;
	background: white;
	padding: 20px;
	border-radius: 8px;
	box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

.content.active {
	display: block;
}

.controls {
	display: flex;
	justify-content: center;
	margin-bottom: 20px;
	flex-wrap: wrap;
	gap: 15px;
}

.control-group {
	background: #f9f9f9;
	padding: 10px 15px;
	border-radius: 8px;
	box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}

/* Grid Demo Styles */
.grid-container {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(30px, 1fr));
	grid-gap: 5px;
	max-width: 800px;
	margin: 0 auto;
}

.grid-item {
	aspect-ratio: 1;
	background-color: #ddd;
	border-radius: 4px;
	transition: all 0.3s ease;
}

/* Base cicada principle styles */
.grid-item:nth-child(2n) {
	background-color: rgba(255, 99, 71, 0.7);
}

.grid-item:nth-child(3n) {
	background-color: rgba(65, 105, 225, 0.7);
}

.grid-item:nth-child(5n) {
	background-color: rgba(50, 205, 50, 0.7);
}

.grid-item:nth-child(7n) {
	background-color: rgba(255, 215, 0, 0.7);
}

.grid-item:nth-child(11n) {
	background-color: rgba(138, 43, 226, 0.7);
}

/* Additional transforms based on prime numbers */
.transforms-active .grid-item:nth-child(3n) {
	transform: rotate(10deg);
}

.transforms-active .grid-item:nth-child(5n) {
	transform: scale(0.8);
}

.transforms-active .grid-item:nth-child(7n) {
	transform: translateY(5px);
}

/* Border radius variations */
.radius-active .grid-item:nth-child(2n) {
	border-radius: 50%;
}

.radius-active .grid-item:nth-child(3n) {
	border-radius: 35% 10%;
}

.radius-active .grid-item:nth-child(5n) {
	border-radius: 10% 30% 50% 70%;
}

/* Add animation with prime number durations */
@keyframes pulse {
	0% {
		opacity: 0.7;
	}
	50% {
		opacity: 1;
	}
	100% {
		opacity: 0.7;
	}
}

.animation-active .grid-item:nth-child(2n) {
	animation: pulse 2s infinite;
}

.animation-active .grid-item:nth-child(3n) {
	animation: pulse 3s infinite;
}

.animation-active .grid-item:nth-child(5n) {
	animation: pulse 5s infinite;
}

.animation-active .grid-item:nth-child(7n) {
	animation: pulse 7s infinite;
}

/* Canvas Demo Styles */
canvas {
	display: block;
	margin: 0 auto;
	background: #f5f5f5;
	border-radius: 8px;
}

.canvas-controls {
	display: flex;
	justify-content: center;
	gap: 20px;
	margin: 20px 0;
	flex-wrap: wrap;
}

.canvas-control {
	display: flex;
	flex-direction: column;
	align-items: center;
}

.canvas-control label {
	margin-bottom: 5px;
	font-size: 14px;
}

input[type="range"] {
	width: 150px;
}

button {
	background: #4c84ff;
	color: white;
	border: none;
	padding: 8px 16px;
	border-radius: 4px;
	cursor: pointer;
	font-size: 14px;
	margin: 5px;
}

button:hover {
	background: #3a6fd8;
}

/* SVG Demo Styles */
.svg-container {
	max-width: 500px;
	margin: 0 auto;
	background: #f5f5f5;
	border-radius: 8px;
	overflow: hidden;
}

svg {
	display: block;
	margin: 0 auto;
}
// Tab functionality
document.querySelectorAll(".tab").forEach((tab) => {
	tab.addEventListener("click", () => {
		// Remove active class from all tabs and contents
		document
			.querySelectorAll(".tab")
			.forEach((t) => t.classList.remove("active"));
		document
			.querySelectorAll(".content")
			.forEach((c) => c.classList.remove("active"));

		// Add active class to clicked tab and corresponding content
		tab.classList.add("active");
		document.getElementById(`${tab.dataset.tab}-content`).classList.add("active");
	});
});

// GRID DEMO
// Create grid items
const grid = document.getElementById("grid");
for (let i = 0; i < 143; i++) {
	const item = document.createElement("div");
	item.className = "grid-item";
	grid.appendChild(item);
}

// Control toggles
document
	.getElementById("transforms-toggle")
	.addEventListener("change", function () {
		grid.classList.toggle("transforms-active", this.checked);
	});

document
	.getElementById("radius-toggle")
	.addEventListener("change", function () {
		grid.classList.toggle("radius-active", this.checked);
	});

document
	.getElementById("animation-toggle")
	.addEventListener("change", function () {
		grid.classList.toggle("animation-active", this.checked);
	});

// CANVAS PARTICLES DEMO
const canvas = document.getElementById("particleCanvas");
const ctx = canvas.getContext("2d");
let particles = [];
let animationId;

// Color schemes
const colorSchemes = [
	["#FF6B6B", "#4ECDC4", "#FFE66D", "#1A535C", "#F7FFF7"],
	["#05668D", "#028090", "#00A896", "#02C39A", "#F0F3BD"],
	["#845EC2", "#D65DB1", "#FF6F91", "#FF9671", "#FFC75F"],
	["#00B8A9", "#F8F3D4", "#F6416C", "#FFDE7D", "#8F4068"]
];

let currentColorScheme = 0;

// Particle class with prime number movement patterns
class Particle {
	constructor() {
		this.x = Math.random() * canvas.width;
		this.y = Math.random() * canvas.height;
		this.size =
			document.getElementById("size").value * (Math.random() * 0.5 + 0.5);
		this.color =
			colorSchemes[currentColorScheme][
				Math.floor(Math.random() * colorSchemes[currentColorScheme].length)
			];

		// Prime number based speeds for x and y
		const primes = [2, 3, 5, 7, 11, 13];
		this.speedFactor = document.getElementById("speed").value / 5;
		this.xSpeed =
			((Math.random() - 0.5) *
				this.speedFactor *
				primes[Math.floor(Math.random() * primes.length)]) /
			10;
		this.ySpeed =
			((Math.random() - 0.5) *
				this.speedFactor *
				primes[Math.floor(Math.random() * primes.length)]) /
			10;

		// Prime number based pulse rates and wave patterns
		this.pulseRate = primes[Math.floor(Math.random() * primes.length)];
		this.waveAmplitude = Math.random() * 2 + 1;
		this.waveFrequency = primes[Math.floor(Math.random() * primes.length)] / 100;
		this.timeOffset = Math.random() * 100;
	}

	update() {
		// Move with prime-based speeds
		this.x += this.xSpeed;
		this.y += this.ySpeed;

		// Add a wave pattern based on primes
		this.y +=
			(Math.sin((Date.now() + this.timeOffset) * this.waveFrequency) *
				this.waveAmplitude) /
			10;

		// Bounce off edges
		if (this.x < 0 || this.x > canvas.width) this.xSpeed = -this.xSpeed;
		if (this.y < 0 || this.y > canvas.height) this.ySpeed = -this.ySpeed;

		// Pulse size based on prime cycle
		const pulse = Math.sin(Date.now() / (200 * this.pulseRate));
		this.displaySize = this.size * (1 + pulse * 0.2);
	}

	draw() {
		ctx.beginPath();
		ctx.arc(this.x, this.y, this.displaySize, 0, Math.PI * 2);
		ctx.fillStyle = this.color;
		ctx.fill();
	}
}

// Initialize particles
function initParticles() {
	particles = [];
	const count = document.getElementById("particles").value;
	for (let i = 0; i < count; i++) {
		particles.push(new Particle());
	}
}

// Animation loop
function animateParticles() {
	ctx.clearRect(0, 0, canvas.width, canvas.height);

	for (const particle of particles) {
		particle.update();
		particle.draw();
	}

	animationId = requestAnimationFrame(animateParticles);
}

// Change color scheme
document.getElementById("color-button").addEventListener("click", function () {
	currentColorScheme = (currentColorScheme + 1) % colorSchemes.length;
	particles.forEach((p) => {
		p.color =
			colorSchemes[currentColorScheme][
				Math.floor(Math.random() * colorSchemes[currentColorScheme].length)
			];
	});
});

// Handle slider changes
document.getElementById("particles").addEventListener("change", initParticles);
document.getElementById("speed").addEventListener("change", function () {
	const speed = this.value / 5;
	particles.forEach((p) => {
		p.xSpeed =
			p.xSpeed > 0 ? Math.abs(p.xSpeed) * speed : -Math.abs(p.xSpeed) * speed;
		p.ySpeed =
			p.ySpeed > 0 ? Math.abs(p.ySpeed) * speed : -Math.abs(p.ySpeed) * speed;
	});
});

document.getElementById("size").addEventListener("change", function () {
	const size = this.value;
	particles.forEach((p) => {
		p.size = size * (Math.random() * 0.5 + 0.5);
	});
});

// Start animation when canvas tab is shown
document
	.querySelector('.tab[data-tab="canvas"]')
	.addEventListener("click", function () {
		if (particles.length === 0) {
			initParticles();
		}
		if (!animationId) {
			animateParticles();
		}
	});

// SVG PATTERN DEMO
const svg = document.getElementById("patternSvg");
const primes = [2, 3, 5, 7, 11, 13, 17, 19, 23];
let svgAnimationActive = false;

function generateSVGPattern() {
	// Clear SVG
	while (svg.firstChild) {
		svg.removeChild(svg.firstChild);
	}

	// Create background
	const background = document.createElementNS(
		"http://www.w3.org/2000/svg",
		"rect"
	);
	background.setAttribute("width", "500");
	background.setAttribute("height", "500");
	background.setAttribute("fill", "#f5f5f5");
	svg.appendChild(background);

	// Generate random seed for this pattern
	const seed = Math.floor(Math.random() * 1000);

	// Generate a random color palette
	const paletteIndex = Math.floor(Math.random() * 5);
	const palettes = [
		// Sunset
		["#FF9E00", "#FF0061", "#930CFF", "#45CAFF", "#F6F7F8"],
		// Forest
		["#004D2F", "#00693C", "#00814D", "#A8C97D", "#E3E8D9"],
		// Ocean
		["#05299E", "#5E8CE9", "#A9B8D6", "#86BCBD", "#E2EEF2"],
		// Desert
		["#D96523", "#D64415", "#E19858", "#EBD3A5", "#F4E9D5"],
		// Berry
		["#8B0051", "#C32E67", "#F24380", "#FF6699", "#FFCCDD"]
	];
	const palette = palettes[paletteIndex];

	// Choose a random pattern type
	const patternType = Math.floor(Math.random() * 4);

	// Generate shapes with prime number patterns
	const numShapes = 100 + Math.floor(Math.random() * 150); // Variable number of shapes
	const circles = [];

	for (let i = 0; i < numShapes; i++) {
		// Generate different shapes based on pattern type
		let element;

		if (patternType === 0 || i % 5 === 0) {
			// Circles
			element = document.createElementNS("http://www.w3.org/2000/svg", "circle");
		} else if (patternType === 1 || i % 7 === 0) {
			// Rectangles
			element = document.createElementNS("http://www.w3.org/2000/svg", "rect");
		} else if (patternType === 2 || i % 11 === 0) {
			// Ellipses
			element = document.createElementNS("http://www.w3.org/2000/svg", "ellipse");
		} else {
			// Polygons (triangles)
			element = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
		}

		// Use prime number patterns with the random seed
		const p1 = primes[i % primes.length];
		const p2 = primes[(i + seed) % primes.length];
		const p3 = primes[(i * 2 + seed) % primes.length];

		// Calculate position based on pattern type
		let x, y, size, width, height;

		switch (patternType) {
			case 0: // Spiral
				const angle = i * 0.1 * ((seed % 5) + 1);
				const radius = i * ((seed % 3) + 1);
				x = 250 + Math.cos(angle) * radius;
				y = 250 + Math.sin(angle) * radius;
				break;
			case 1: // Grid with variations
				const cols = 10 + (seed % 10);
				x = (i % cols) * (500 / cols) + ((i * p1) % 20) - 10;
				y = Math.floor(i / cols) * (500 / cols) + ((i * p2) % 20) - 10;
				break;
			case 2: // Radial
				const rings = 5 + (seed % 5);
				const ring = Math.floor(i / 20) % rings;
				const segmentAngle = (i % 20) * ((Math.PI * 2) / 20);
				const ringRadius = (ring + 1) * (400 / rings / 2);
				x = 250 + Math.cos(segmentAngle) * ringRadius;
				y = 250 + Math.sin(segmentAngle) * ringRadius;
				break;
			case 3: // Random clusters
				const clusterCount = 3 + (seed % 5);
				const clusterIndex = i % clusterCount;
				const clusterX = 100 + (clusterIndex * 300) / clusterCount;
				const clusterY =
					100 + (((seed + clusterIndex) % clusterCount) * 300) / clusterCount;
				x = clusterX + Math.cos(i * p1 * 0.1) * 80;
				y = clusterY + Math.sin(i * p2 * 0.1) * 80;
				break;
		}

		// Size based on prime factors and pattern
		size = 5 + ((i * seed) % 25);
		if (i % p1 === 0) size *= 1.5;
		if (i % p2 === 0) size *= 0.7;
		size = Math.max(2, Math.min(40, size)); // Constrain size

		// Width and height for non-circular elements
		width = size * (1 + (i % p3) / p3);
		height = size * (1 + (i % p1) / p1);

		// Color based on prime position and selected palette
		const colorIndex = (i * seed) % palette.length;
		const color = palette[colorIndex];
		const opacity = 0.2 + ((i * seed) % 8) / 10; // Variable opacity

		// Set attributes based on shape type
		if (element.tagName === "circle") {
			element.setAttribute("cx", x);
			element.setAttribute("cy", y);
			element.setAttribute("r", size);
		} else if (element.tagName === "rect") {
			element.setAttribute("x", x - width / 2);
			element.setAttribute("y", y - height / 2);
			element.setAttribute("width", width);
			element.setAttribute("height", height);

			// Add rounded corners to some rectangles
			if (i % 3 === 0) {
				element.setAttribute("rx", size / 4);
				element.setAttribute("ry", size / 4);
			}
		} else if (element.tagName === "ellipse") {
			element.setAttribute("cx", x);
			element.setAttribute("cy", y);
			element.setAttribute("rx", width / 2);
			element.setAttribute("ry", height / 2);
		} else if (element.tagName === "polygon") {
			// Create triangle points
			const points = [];
			const sides = 3 + (i % 4); // 3 to 6 sides

			for (let j = 0; j < sides; j++) {
				const angle = j * ((Math.PI * 2) / sides);
				const px = x + Math.cos(angle) * size;
				const py = y + Math.sin(angle) * size;
				points.push(`${px},${py}`);
			}

			element.setAttribute("points", points.join(" "));
		}

		// Apply common attributes
		element.setAttribute("fill", color);
		element.setAttribute("fill-opacity", opacity);

		// Add occasional rotation
		if (i % p3 === 0) {
			const rotation = (i * seed) % 360;
			element.setAttribute("transform", `rotate(${rotation} ${x} ${y})`);
		}

		// Store original values for animation
		element.dataset.originalX = x;
		element.dataset.originalY = y;
		element.dataset.originalSize = size;
		element.dataset.animationOffset = i * 0.01;
		element.dataset.p1 = p1;
		element.dataset.p2 = p2;

		svg.appendChild(element);
		circles.push(element);
	}

	return circles;
}

let svgCircles = generateSVGPattern();
let svgAnimationId;

function animateSVG(timestamp) {
	svgCircles.forEach((element) => {
		const originalX = parseFloat(element.dataset.originalX);
		const originalY = parseFloat(element.dataset.originalY);
		const originalSize = parseFloat(element.dataset.originalSize);
		const offset = parseFloat(element.dataset.animationOffset);
		const p1 = parseFloat(element.dataset.p1);
		const p2 = parseFloat(element.dataset.p2);

		// Create organic movement with sine/cosine and prime numbers
		const time = timestamp * 0.001;
		const xShift = Math.sin(time * 0.5 + offset * p1) * 10;
		const yShift = Math.cos(time * 0.5 + offset * p2) * 10;
		const sizeShift = Math.sin(time * 0.2 + offset * p1 * p2) * 2;

		// Apply animations based on element type
		if (element.tagName === "circle") {
			element.setAttribute("cx", originalX + xShift);
			element.setAttribute("cy", originalY + yShift);
			element.setAttribute("r", originalSize + sizeShift);
		} else if (element.tagName === "rect") {
			const width = originalSize * 1.2 + sizeShift;
			const height = originalSize * 0.8 + sizeShift;
			element.setAttribute("x", originalX + xShift - width / 2);
			element.setAttribute("y", originalY + yShift - height / 2);
			element.setAttribute("width", width);
			element.setAttribute("height", height);
		} else if (element.tagName === "ellipse") {
			element.setAttribute("cx", originalX + xShift);
			element.setAttribute("cy", originalY + yShift);
			element.setAttribute("rx", originalSize * 0.8 + sizeShift);
			element.setAttribute("ry", originalSize * 1.2 + sizeShift);
		} else if (element.tagName === "polygon") {
			// For polygons, we need to recalculate the points
			const sides = 3 + (parseInt(element.dataset.p1) % 4); // Get the number of sides
			const points = [];

			for (let j = 0; j < sides; j++) {
				const angle = j * ((Math.PI * 2) / sides);
				const adjustedSize = originalSize + sizeShift;
				const px = originalX + xShift + Math.cos(angle + time * 0.2) * adjustedSize;
				const py = originalY + yShift + Math.sin(angle + time * 0.2) * adjustedSize;
				points.push(`${px},${py}`);
			}

			element.setAttribute("points", points.join(" "));
		}

		// Occasionally rotate elements
		if (parseInt(element.dataset.p1) % 3 === 0) {
			const rotation = (time * 10) % 360;
			element.setAttribute(
				"transform",
				`rotate(${rotation} ${originalX + xShift} ${originalY + yShift})`
			);
		}
	});

	svgAnimationId = requestAnimationFrame(animateSVG);
}

// Generate new pattern
document
	.getElementById("generate-pattern")
	.addEventListener("click", function () {
		// Ensure we're working with the current SVG reference
		const svgElement = document.getElementById("patternSvg");

		// Clear existing SVG content
		while (svgElement.firstChild) {
			svgElement.removeChild(svgElement.firstChild);
		}

		// Generate new pattern and store the circles
		svgCircles = generateSVGPattern();

		// Handle animation state
		if (svgAnimationActive) {
			if (svgAnimationId) {
				cancelAnimationFrame(svgAnimationId);
			}
			svgAnimationId = requestAnimationFrame(animateSVG);
		}

		console.log("Generated new pattern with", svgCircles.length, "circles");
	});

// Toggle animation
document
	.getElementById("toggle-animation")
	.addEventListener("click", function () {
		svgAnimationActive = !svgAnimationActive;
		if (svgAnimationActive) {
			this.textContent = "Pause Animation";
			svgAnimationId = requestAnimationFrame(animateSVG);
		} else {
			this.textContent = "Start Animation";
			cancelAnimationFrame(svgAnimationId);
			// Reset positions
			svgCircles.forEach((circle) => {
				circle.setAttribute("cx", circle.dataset.originalX);
				circle.setAttribute("cy", circle.dataset.originalY);
				circle.setAttribute("r", circle.dataset.originalSize);
			});
		}
	});

// Start the SVG tab when selected
document
	.querySelector('.tab[data-tab="svg"]')
	.addEventListener("click", function () {
		if (svgCircles.length === 0) {
			svgCircles = generateSVGPattern();
		}
	});
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.