<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
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.