#3920 Dynamic Art
#3920 Dynamic Art
#3920 Large framed picture with white background and a 16 x 32 matrix of circle outlines
Increase to 32 x 32
Add psychedelic colors withing the circles, retaining the white background
From <https://aistudio.google.com/prompts/1iR2EnI4jTh0lx1fEG0NJseVnIMQQn9s3> Google Nano Banana Pro
Google Gemini 3.0 Pro (Thinking)
Create a Web page with the following configurable layout
This is a "Generative Art Grid" implementation. Since you requested a configurable layout, I have designed this using CSS Grid and JavaScript.
It includes a configuration panel that allows you to adjust the number of rows, columns, circle size, and spacing in real-time to match the dimensions of the artwork in your image.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Configurable Art Grid</title>
<style>
:root {
/* Default Configuration Variables */
--cell-size: 40px;
--gap-size: 10px;
--cols: 20;
--rows: 12;
--frame-color: #d2b48c; /* Light wood color */
}
body {
font-family: system-ui, -apple-system, sans-serif;
background-color: #f0f0f0;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 20px;
}
/* --- Controls Section --- */
.controls {
background: white;
padding: 15px 25px;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
margin-bottom: 30px;
display: flex;
gap: 20px;
flex-wrap: wrap;
justify-content: center;
}
.control-group {
display: flex;
flex-direction: column;
}
label {
font-size: 12px;
color: #666;
margin-bottom: 5px;
font-weight: bold;
}
input[type="number"], input[type="range"] {
padding: 5px;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
background: #333;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
align-self: flex-end;
}
button:hover {
background: #555;
}
/* --- The Art Frame --- */
.art-frame {
background-color: white;
/* Simulating the wooden frame border */
border: 15px solid var(--frame-color);
/* Inner padding between frame and dots */
padding: 30px;
/* Shadow to create depth like it's hanging on a wall */
box-shadow:
inset 0 0 20px rgba(0,0,0,0.05), /* Inner shadow */
0 15px 35px rgba(0,0,0,0.2); /* Drop shadow */
max-width: 95vw;
overflow: auto;
}
/* --- The Grid Layout --- */
.grid-container {
display: grid;
/* These variables are controlled by JS */
grid-template-columns: repeat(var(--cols), var(--cell-size));
grid-template-rows: repeat(var(--rows), var(--cell-size));
gap: var(--gap-size);
}
/* --- The Circles (Dots) --- */
.dot {
width: 100%;
height: 100%;
border-radius: 50%;
/* Default background if gradient fails */
background-color: #ccc;
transition: transform 0.2s ease;
}
.dot:hover {
transform: scale(1.1);
cursor: pointer;
}
</style>
</head>
<body>
<div class="controls">
<div class="control-group">
<label for="cols">Columns</label>
<input type="number" id="cols" value="20" min="1" max="50">
</div>
<div class="control-group">
<label for="rows">Rows</label>
<input type="number" id="rows" value="12" min="1" max="50">
</div>
<div class="control-group">
<label for="size">Circle Size (px)</label>
<input type="number" id="size" value="35" min="10" max="100">
</div>
<div class="control-group">
<label for="gap">Gap Size (px)</label>
<input type="number" id="gap" value="10" min="0" max="50">
</div>
<button onclick="generateGrid()">Regenerate Colors</button>
</div>
<div class="art-frame">
<div class="grid-container" id="grid">
</div>
</div>
<script>
const gridEl = document.getElementById('grid');
const inputs = {
cols: document.getElementById('cols'),
rows: document.getElementById('rows'),
size: document.getElementById('size'),
gap: document.getElementById('gap')
};
// Function to create vibrant random gradients similar to the image
function getRandomGradient() {
const hue = Math.floor(Math.random() * 360);
const hue2 = (hue + 40) % 360;
const hue3 = (hue - 40 + 360) % 360;
// Randomly choose between radial, conic, or linear to mimic variety
const type = Math.random();
if (type < 0.33) {
// Swirly Conic Look
return `conic-gradient(from ${Math.random() * 360}deg, hsl(${hue}, 80%, 50%), hsl(${hue2}, 90%, 60%), hsl(${hue}, 80%, 50%))`;
} else if (type < 0.66) {
// Marble Radial Look
return `radial-gradient(circle at 30% 30%, hsl(${hue}, 90%, 60%), hsl(${hue3}, 80%, 40%))`;
} else {
// Striped Linear Look
return `linear-gradient(${Math.random() * 360}deg, hsl(${hue}, 80%, 50%), hsl(${hue2}, 80%, 60%))`;
}
}
function generateGrid() {
// 1. Update CSS Variables based on inputs
const r = inputs.rows.value;
const c = inputs.cols.value;
document.documentElement.style.setProperty('--rows', r);
document.documentElement.style.setProperty('--cols', c);
document.documentElement.style.setProperty('--cell-size', inputs.size.value + 'px');
document.documentElement.style.setProperty('--gap-size', inputs.gap.value + 'px');
// 2. Clear existing grid
gridEl.innerHTML = '';
// 3. Generate new dots
const totalDots = r * c;
for (let i = 0; i < totalDots; i++) {
const dot = document.createElement('div');
dot.className = 'dot';
dot.style.background = getRandomGradient();
gridEl.appendChild(dot);
}
}
// Add event listeners to inputs for live updates
Object.values(inputs).forEach(input => {
input.addEventListener('input', generateGrid);
});
// Initial Generation
generateGrid();
</script>
</body>
</html>
Key Features of this Layout:
CSS Grid System: The core layout uses
display: grid. I used CSS Variables (--cols,--rows, etc.) within the CSS, which are updated dynamically by JavaScript. This makes the layout extremely robust.Generative Styling: To mimic the colorful, marble-like dots in your image, I created a JavaScript function
getRandomGradient(). It randomly appliesconic-gradient(for swirls),radial-gradient(for spheres), orlinear-gradient(for stripes) using HSL color logic to ensure the colors remain vibrant.Frame Styling: The
.art-frameclass includes a solid border andbox-shadowto replicate the physical wooden frame and the wall shadow seen in your photo.Live Configuration: The top panel allows you to instantly change the grid dimensions (e.g., changing from 20x12 to 10x10) or the spacing between the dots without reloading the page.
Add a "Animate" toggle to automatically generate colors, and change the color pallet to a shiny metal variation of the current colors
Here is the updated code.
Changes made:
Shiny Metal Palette: I replaced the previous color logic with a
getMetallicGradientfunction. This creates high-contrast radial gradients with a white "specular highlight" (hotspot) at the top-left of each circle, simulating polished spheres (Gold, Chrome, Copper, Anodized Steel).Animation Toggle: Added a checkbox in the controls. When checked, it triggers an interval that refreshes the colors of the existing grid dots every 250ms, creating a shimmering "active data" look.
Optimization: The animation logic updates the
style.backgroundof existing dots rather than rebuilding the entire HTML structure, ensuring smooth performance.
Support 16 pallets including the first 2 already used
Here is the updated complete solution.
I have added a Palette Selector dropdown containing 16 unique generative styles. The first two are the "Vibrant Marble" (Original) and "Shiny Metal" (Previous), followed by 14 new thematic variations ranging from "Neon Cyberpunk" to "Gas Giants."
The Animate toggle works with all palettes, creating different effects depending on the style selected (e.g., blinking lights for Neon, shifting clouds for Planetary).
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>16-Palette Generative Art Grid</title>
<style>
:root {
--cell-size: 35px;
--gap-size: 8px;
--cols: 20;
--rows: 12;
--frame-color: #2b2b2b;
--bg-color: #e0e0e0;
}
body {
font-family: system-ui, -apple-system, sans-serif;
background-color: var(--bg-color);
color: #333;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 20px;
transition: background-color 0.5s ease;
}
/* --- Controls Section --- */
.controls {
background: white;
padding: 15px 25px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
margin-bottom: 20px;
display: flex;
gap: 15px;
flex-wrap: wrap;
justify-content: center;
align-items: center;
max-width: 900px;
}
.control-group {
display: flex;
flex-direction: column;
}
label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 4px;
font-weight: 700;
color: #555;
}
input[type="number"], select {
padding: 6px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
}
select {
cursor: pointer;
min-width: 150px;
}
/* Toggle Switch Styling */
.toggle-group {
display: flex;
align-items: center;
gap: 8px;
background: #f0f0f0;
padding: 5px 12px;
border-radius: 20px;
border: 1px solid #ddd;
}
button {
background: #222;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
transition: background 0.2s;
}
button:hover {
background: #444;
}
/* --- The Art Frame --- */
.art-frame {
background-color: #1a1a1a;
border: 15px solid var(--frame-color);
padding: 30px;
box-shadow:
inset 0 0 50px rgba(0,0,0,0.5),
0 20px 40px rgba(0,0,0,0.4);
max-width: 95vw;
overflow: auto;
border-radius: 4px;
transition: border-color 0.5s ease;
}
/* --- The Grid Layout --- */
.grid-container {
display: grid;
grid-template-columns: repeat(var(--cols), var(--cell-size));
grid-template-rows: repeat(var(--rows), var(--cell-size));
gap: var(--gap-size);
}
/* --- The Circles (Dots) --- */
.dot {
width: 100%;
height: 100%;
border-radius: 50%;
transition: transform 0.1s;
}
.dot:hover {
transform: scale(1.15);
z-index: 10;
box-shadow: 0 0 10px rgba(255,255,255,0.5);
}
</style>
</head>
<body>
<div class="controls">
<div class="control-group">
<label for="paletteSelect">Color Palette</label>
<select id="paletteSelect" onchange="updateAllColors()">
<option value="1">1. Vibrant Marble (Original)</option>
<option value="2">2. Shiny Metal (Original)</option>
<option value="3">3. Neon Cyberpunk</option>
<option value="4">4. Pastel Dreams</option>
<option value="5">5. Deep Ocean</option>
<option value="6">6. Magma & Ash</option>
<option value="7">7. Noir Grayscale</option>
<option value="8">8. Retro Vaporwave</option>
<option value="9">9. Forest Camo</option>
<option value="10">10. Candy Gloss</option>
<option value="11">11. Gas Giants</option>
<option value="12">12. Matrix Digital</option>
<option value="13">13. Rusty Industrial</option>
<option value="14">14. Frozen Ice</option>
<option value="15">15. Midnight Sky</option>
<option value="16">16. Golden Luxury</option>
</select>
</div>
<div class="control-group">
<label for="cols">Cols</label>
<input type="number" id="cols" value="20" min="1" max="50" style="width: 50px;">
</div>
<div class="control-group">
<label for="rows">Rows</label>
<input type="number" id="rows" value="12" min="1" max="50" style="width: 50px;">
</div>
<div class="control-group">
<label for="size">Size</label>
<input type="number" id="size" value="35" min="10" max="100" style="width: 50px;">
</div>
<div class="toggle-group">
<label for="animateToggle" style="margin:0; cursor:pointer;">Animate</label>
<input type="checkbox" id="animateToggle" style="cursor:pointer;">
</div>
<button onclick="updateAllColors()">Regenerate</button>
</div>
<div class="art-frame">
<div class="grid-container" id="grid"></div>
</div>
<script>
const gridEl = document.getElementById('grid');
const paletteSelect = document.getElementById('paletteSelect');
const inputs = {
cols: document.getElementById('cols'),
rows: document.getElementById('rows'),
size: document.getElementById('size')
};
const animateToggle = document.getElementById('animateToggle');
let animationInterval = null;
// --- Random Helpers ---
const rand = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
const randHue = () => Math.floor(Math.random() * 360);
// --- PALETTE GENERATORS ---
function getGradientStyle(paletteId) {
const id = parseInt(paletteId);
switch(id) {
// 1. VIBRANT MARBLE (Original Request)
case 1: {
const h = randHue();
const type = Math.random();
if (type < 0.33) return `conic-gradient(from ${rand(0,360)}deg, hsl(${h}, 80%, 50%), hsl(${(h+40)%360}, 90%, 60%), hsl(${h}, 80%, 50%))`;
if (type < 0.66) return `radial-gradient(circle at 30% 30%, hsl(${h}, 90%, 60%), hsl(${(h-40+360)%360}, 80%, 40%))`;
return `linear-gradient(${rand(0,360)}deg, hsl(${h}, 80%, 50%), hsl(${(h+40)%360}, 80%, 60%))`;
}
// 2. SHINY METAL (Previous Request)
case 2: {
const metalType = Math.random();
let h, s, l, tint;
if (metalType < 0.25) { h=rand(35,55); s='80%'; l='50%'; tint=`hsl(${h},${s},30%)`; } // Gold
else if (metalType < 0.5) { h=0; s='0%'; l=rand(40,80)+'%'; tint='#000'; } // Silver
else if (metalType < 0.7) { h=rand(10,30); s='60%'; l='50%'; tint=`hsl(${h},${s},20%)`; } // Copper
else { h=randHue(); s='80%'; l='50%'; tint=`hsl(${h},90%,20%)`; } // Anodized
return `radial-gradient(circle at 30% 30%, rgba(255,255,255,0.95) 0%, rgba(255,255,255,0.8) 5%, hsl(${h},${s},${l}) 20%, ${tint} 90%, black 100%)`;
}
// 3. NEON CYBERPUNK
case 3: {
const h = [300, 320, 180, 60][rand(0,3)]; // Pink, Purple, Cyan, Yellow
return `radial-gradient(circle, transparent 30%, hsl(${h}, 100%, 50%) 90%, black 100%), linear-gradient(135deg, #111, #222)`;
}
// 4. PASTEL DREAMS
case 4: {
const h = randHue();
return `radial-gradient(circle at 70% 20%, white 10%, hsl(${h}, 70%, 85%) 50%, hsl(${(h+60)%360}, 70%, 80%) 100%)`;
}
// 5. DEEP OCEAN
case 5: {
const h = rand(170, 240); // Teals and Blues
return `radial-gradient(circle at 50% 50%, hsl(${h}, 70%, 40%), hsl(${h}, 80%, 15%))`;
}
// 6. MAGMA & ASH
case 6: {
if (Math.random() > 0.7) return `radial-gradient(circle, #333 20%, #111 100%)`; // Ash
const h = rand(0, 40); // Red to Orange
return `radial-gradient(circle at ${rand(20,80)}% ${rand(20,80)}%, #fffa 0%, hsl(${h}, 100%, 50%) 40%, #500 80%, #000 100%)`;
}
// 7. NOIR GRAYSCALE
case 7: {
const l = rand(10, 90);
return `linear-gradient(${rand(0,180)}deg, hsl(0,0%,${l}%), hsl(0,0%,${l-10}%))`;
}
// 8. RETRO VAPORWAVE
case 8: {
return `linear-gradient(${rand(0,180)}deg, #ff71ce, #01cdfe)`;
}
// 9. FOREST CAMO
case 9: {
const h = rand(80, 140); // Greens
const s = rand(20, 60);
const l = rand(20, 40);
return `conic-gradient(from ${rand(0,360)}deg, hsl(${h},${s}%,${l}%), hsl(${h},${s}%,${l-10}%), hsl(${h},${s}%,${l}%))`;
}
// 10. CANDY GLOSS
case 10: {
const h = randHue();
return `radial-gradient(circle at 30% 30%, rgba(255,255,255,0.9) 5%, hsl(${h}, 90%, 60%) 20%, hsl(${h}, 100%, 40%) 100%)`;
}
// 11. GAS GIANTS
case 11: {
const h = randHue();
return `linear-gradient(${rand(-20,20)}deg, hsl(${h}, 50%, 50%) 0%, hsl(${h}, 40%, 60%) 20%, hsl(${h}, 60%, 40%) 40%, hsl(${h}, 50%, 50%) 100%)`;
}
// 12. MATRIX DIGITAL
case 12: {
const l = rand(10, 80);
return `radial-gradient(circle, hsl(120, 100%, ${l}%) 0%, black 70%)`;
}
// 13. RUSTY INDUSTRIAL
case 13: {
const h = rand(10, 30);
return `repeating-linear-gradient(45deg, hsl(${h}, 60%, 30%), hsl(${h}, 50%, 25%) 5px, hsl(${h}, 40%, 20%) 10px)`;
}
// 14. FROZEN ICE
case 14: {
return `linear-gradient(${rand(0,360)}deg, rgba(255,255,255,0.8), rgba(200,240,255,0.6)), url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1IiBoZWlnaHQ9IjUiPgo8cmVjdCB3aWR0aD0iNSIgaGVpZ2h0PSI1IiBmaWxsPSIjYWRlOGY0Ii8+Cjwvc3ZnPg==')`;
}
// 15. MIDNIGHT SKY
case 15: {
return `radial-gradient(1px 1px at ${rand(10,90)}% ${rand(10,90)}%, white, transparent), linear-gradient(to bottom, #000022, #111144)`;
}
// 16. GOLDEN LUXURY
case 16: {
return `linear-gradient(${rand(120,160)}deg, #bf953f, #fcf6ba, #b38728, #fbf5b7, #aa771c)`;
}
default: return '#ccc';
}
}
// --- CORE FUNCTIONS ---
function updateAllColors() {
// Update Body/Frame Theme based on palette
const pid = parseInt(paletteSelect.value);
const frame = document.querySelector('.art-frame');
// Subtle theme changes for the frame based on palette
if ([3, 5, 6, 12, 15].includes(pid)) {
frame.style.backgroundColor = "#000"; // Dark BG for glowing types
document.documentElement.style.setProperty('--frame-color', '#111');
} else if ([4, 10, 14, 16].includes(pid)) {
frame.style.backgroundColor = "#fff"; // Light BG for pastel/glass
document.documentElement.style.setProperty('--frame-color', '#d2b48c');
} else {
frame.style.backgroundColor = "#1a1a1a"; // Default Dark
document.documentElement.style.setProperty('--frame-color', '#2b2b2b');
}
const dots = document.getElementsByClassName('dot');
for (let dot of dots) {
dot.style.background = getGradientStyle(paletteSelect.value);
// Reset box shadow for most, apply special for some
dot.style.boxShadow = (pid === 2 || pid === 10) ? "2px 5px 10px rgba(0,0,0,0.4)" : "none";
if(pid === 3) dot.style.boxShadow = "0 0 5px currentColor";
}
}
function generateGrid() {
// Update CSS Variables
document.documentElement.style.setProperty('--rows', inputs.rows.value);
document.documentElement.style.setProperty('--cols', inputs.cols.value);
document.documentElement.style.setProperty('--cell-size', inputs.size.value + 'px');
gridEl.innerHTML = '';
const totalDots = inputs.rows.value * inputs.cols.value;
for (let i = 0; i < totalDots; i++) {
const dot = document.createElement('div');
dot.className = 'dot';
gridEl.appendChild(dot);
}
updateAllColors();
}
function toggleAnimation() {
if (animateToggle.checked) {
// Determine speed based on palette (some look better slow)
const pid = parseInt(paletteSelect.value);
let speed = 250;
if ([11, 14, 16].includes(pid)) speed = 800; // Slower for clouds/ice/gold
if (pid === 12) speed = 100; // Fast for Matrix
clearInterval(animationInterval); // clear previous to reset speed
animationInterval = setInterval(updateAllColors, speed);
} else {
clearInterval(animationInterval);
animationInterval = null;
}
}
// Event Listeners
Object.values(inputs).forEach(input => input.addEventListener('input', generateGrid));
animateToggle.addEventListener('change', toggleAnimation);
// Initial Load
generateGrid();
</script>
</body>
</html>
Support 16 pallets including the first 2 already used
Add the capability for "holes" throughout the matrix (i.e. non-displayed colored circles
Here is the updated solution.
I have added a Density Control slider (0% to 100%).
100%: All circles are visible (default).
Lower values: Randomly introduces "holes" (invisible cells) into the matrix.
Behavior: The holes remain fixed during the color animation loop (so the grid doesn't flicker chaotically), but they are re-rolled if you drag the Density slider or click "Regenerate".
Did not work. Matrix is not displayed. Just a dark background where the matrix should have been.
I apologize. The likely cause of the "blank screen" was a syntax error in the JavaScript code—specifically the long "Frozen Ice" Base64 string I included in the previous step. If that line breaks during copy-pasting or rendering, it crashes the entire script, leaving you with an empty grid.
Here is the fixed, robust version. I have replaced the risky image code with a pure CSS alternative and added a fallback background color so the dots are always visible.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>16-Palette Generative Art Grid</title>
<style>
:root {
--cell-size: 35px;
--gap-size: 8px;
--cols: 20;
--rows: 12;
--frame-color: #2b2b2b;
--bg-color: #e0e0e0;
}
body {
font-family: system-ui, -apple-system, sans-serif;
background-color: var(--bg-color);
color: #333;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 20px;
transition: background-color 0.5s ease;
}
/* --- Controls Section --- */
.controls {
background: white;
padding: 15px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
margin-bottom: 20px;
display: flex;
gap: 15px;
flex-wrap: wrap;
justify-content: center;
align-items: center;
max-width: 950px;
}
.control-group {
display: flex;
flex-direction: column;
}
label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 4px;
font-weight: 700;
color: #555;
}
input[type="number"], select {
padding: 6px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
}
input[type="range"] {
width: 100px;
cursor: pointer;
}
select { cursor: pointer; min-width: 160px; }
.toggle-group {
display: flex;
align-items: center;
gap: 8px;
background: #f0f0f0;
padding: 5px 12px;
border-radius: 20px;
border: 1px solid #ddd;
}
button {
background: #222;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
transition: background 0.2s;
}
button:hover { background: #444; }
/* --- The Art Frame --- */
.art-frame {
background-color: #1a1a1a;
border: 15px solid var(--frame-color);
padding: 30px;
box-shadow: inset 0 0 50px rgba(0,0,0,0.5), 0 20px 40px rgba(0,0,0,0.4);
max-width: 95vw;
overflow: auto;
border-radius: 4px;
transition: border-color 0.5s ease;
}
/* --- The Grid Layout --- */
.grid-container {
display: grid;
/* Explicitly using CSS variables for layout */
grid-template-columns: repeat(var(--cols), var(--cell-size));
grid-template-rows: repeat(var(--rows), var(--cell-size));
gap: var(--gap-size);
}
/* --- The Circles (Dots) --- */
.dot {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #555; /* Fallback color so dots are never invisible */
transition: transform 0.1s, opacity 0.3s;
}
.dot:hover {
transform: scale(1.15);
z-index: 10;
box-shadow: 0 0 10px rgba(255,255,255,0.5);
}
.dot.hidden {
opacity: 0;
pointer-events: none;
}
</style>
</head>
<body>
<div class="controls">
<div class="control-group">
<label for="paletteSelect">Color Palette</label>
<select id="paletteSelect">
<option value="1">1. Vibrant Marble</option>
<option value="2">2. Shiny Metal</option>
<option value="3">3. Neon Cyberpunk</option>
<option value="4">4. Pastel Dreams</option>
<option value="5">5. Deep Ocean</option>
<option value="6">6. Magma & Ash</option>
<option value="7">7. Noir Grayscale</option>
<option value="8">8. Retro Vaporwave</option>
<option value="9">9. Forest Camo</option>
<option value="10">10. Candy Gloss</option>
<option value="11">11. Gas Giants</option>
<option value="12">12. Matrix Digital</option>
<option value="13">13. Rusty Industrial</option>
<option value="14">14. Frozen Ice</option>
<option value="15">15. Midnight Sky</option>
<option value="16">16. Golden Luxury</option>
</select>
</div>
<div class="control-group">
<label for="cols">Cols</label>
<input type="number" id="cols" value="20" min="1" max="50" style="width: 50px;">
</div>
<div class="control-group">
<label for="rows">Rows</label>
<input type="number" id="rows" value="12" min="1" max="50" style="width: 50px;">
</div>
<div class="control-group">
<label for="size">Size</label>
<input type="number" id="size" value="35" min="10" max="100" style="width: 50px;">
</div>
<div class="control-group">
<label for="density">Density</label>
<input type="range" id="density" value="100" min="0" max="100">
</div>
<div class="toggle-group">
<label for="animateToggle" style="margin:0; cursor:pointer;">Animate</label>
<input type="checkbox" id="animateToggle" style="cursor:pointer;">
</div>
<button id="regenBtn">Regenerate</button>
</div>
<div class="art-frame">
<div class="grid-container" id="grid"></div>
</div>
<script>
// Use DOMContentLoaded to ensure elements exist before script runs
document.addEventListener('DOMContentLoaded', () => {
const gridEl = document.getElementById('grid');
const paletteSelect = document.getElementById('paletteSelect');
const animateToggle = document.getElementById('animateToggle');
const regenBtn = document.getElementById('regenBtn');
const inputs = {
cols: document.getElementById('cols'),
rows: document.getElementById('rows'),
size: document.getElementById('size'),
density: document.getElementById('density')
};
let animationInterval = null;
// --- Random Helpers ---
const rand = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
const randHue = () => Math.floor(Math.random() * 360);
// --- PALETTE GENERATORS ---
function getGradientStyle(paletteId) {
const id = parseInt(paletteId);
try {
switch(id) {
case 1: { // Vibrant Marble
const h = randHue();
if (Math.random() < 0.5) return `radial-gradient(circle at 30% 30%, hsl(${h}, 90%, 60%), hsl(${(h-40+360)%360}, 80%, 40%))`;
return `linear-gradient(${rand(0,360)}deg, hsl(${h}, 80%, 50%), hsl(${(h+40)%360}, 80%, 60%))`;
}
case 2: { // Shiny Metal
const type = Math.random();
let h=0, s='0%', l='50%', tint='#000';
if (type < 0.25) { h=45; s='80%'; l='50%'; tint='#530'; } // Gold
else if (type < 0.5) { h=0; s='0%'; l=rand(50,90)+'%'; tint='#000'; } // Silver
else if (type < 0.75) { h=20; s='60%'; l='50%'; tint='#310'; } // Copper
else { h=randHue(); s='80%'; l='50%'; tint='#000'; } // Anodized
return `radial-gradient(circle at 30% 30%, rgba(255,255,255,0.95) 0%, rgba(255,255,255,0.1) 20%, hsl(${h},${s},${l}) 20%, ${tint} 100%)`;
}
case 3: // Neon Cyberpunk
return `radial-gradient(circle, transparent 30%, hsl(${[300,320,180,60][rand(0,3)]}, 100%, 50%) 90%, black 100%), linear-gradient(135deg, #111, #222)`;
case 4: // Pastel Dreams
return `radial-gradient(circle at 70% 20%, white 10%, hsl(${randHue()}, 70%, 85%) 50%, hsl(${randHue()}, 70%, 80%) 100%)`;
case 5: // Deep Ocean
return `radial-gradient(circle at 50% 50%, hsl(${rand(170,240)}, 70%, 40%), hsl(${rand(170,240)}, 80%, 15%))`;
case 6: // Magma
return `radial-gradient(circle at ${rand(30,70)}% ${rand(30,70)}%, #fffa 0%, hsl(${rand(0,40)}, 100%, 50%) 40%, #000 100%)`;
case 7: // Noir
return `linear-gradient(${rand(0,180)}deg, hsl(0,0%,${rand(20,80)}%), hsl(0,0%,${rand(0,50)}%))`;
case 8: // Vaporwave
return `linear-gradient(${rand(0,180)}deg, #ff71ce, #01cdfe)`;
case 9: // Camo
return `conic-gradient(from ${rand(0,360)}deg, #576b4a, #4b5e32, #3a4a28)`;
case 10: // Candy
return `radial-gradient(circle at 30% 30%, rgba(255,255,255,0.9) 0%, hsl(${randHue()}, 90%, 60%) 30%, hsl(${randHue()}, 100%, 40%) 100%)`;
case 11: // Gas Giants
return `linear-gradient(${rand(-20,20)}deg, hsl(${randHue()}, 50%, 50%) 0%, hsl(${randHue()}, 40%, 60%) 100%)`;
case 12: // Matrix
return `radial-gradient(circle, hsl(120, 100%, ${rand(20,80)}%) 0%, black 70%)`;
case 13: // Industrial
return `repeating-linear-gradient(45deg, #555, #666 5px, #444 10px)`;
case 14: // Frozen Ice (Fixed)
return `linear-gradient(${rand(0,360)}deg, rgba(255,255,255,0.9), rgba(180,230,255,0.7)), radial-gradient(circle, #fff, #ace)`;
case 15: // Midnight
return `radial-gradient(2px 2px at ${rand(10,90)}% ${rand(10,90)}%, white, transparent), #002`;
case 16: // Gold
return `linear-gradient(${rand(120,160)}deg, #bf953f, #fcf6ba, #aa771c)`;
default:
return '#666';
}
} catch (e) {
console.error("Gradient error", e);
return '#666';
}
}
// --- CORE LOGIC ---
function updateAllColors() {
const pid = parseInt(paletteSelect.value);
const frame = document.querySelector('.art-frame');
// Update Frame Theme
if ([3, 5, 6, 12, 15].includes(pid)) {
frame.style.backgroundColor = "#000";
document.documentElement.style.setProperty('--frame-color', '#111');
} else if ([4, 10, 14, 16].includes(pid)) {
frame.style.backgroundColor = "#fff";
document.documentElement.style.setProperty('--frame-color', '#d2b48c');
} else {
frame.style.backgroundColor = "#1a1a1a";
document.documentElement.style.setProperty('--frame-color', '#2b2b2b');
}
const dots = document.getElementsByClassName('dot');
for (let dot of dots) {
dot.style.background = getGradientStyle(pid);
dot.style.boxShadow = (pid === 2 || pid === 10) ? "2px 5px 10px rgba(0,0,0,0.4)" : "none";
if(pid === 3) dot.style.boxShadow = "0 0 5px currentColor";
}
}
function updateDensity() {
const dots = document.getElementsByClassName('dot');
const densityVal = parseInt(inputs.density.value);
for (let dot of dots) {
// Re-roll visibility based on slider
const isVisible = Math.random() * 100 < densityVal;
if (isVisible) dot.classList.remove('hidden');
else dot.classList.add('hidden');
}
}
function regenerate() {
// Set CSS variables
const r = inputs.rows.value;
const c = inputs.cols.value;
const s = inputs.size.value;
document.documentElement.style.setProperty('--rows', r);
document.documentElement.style.setProperty('--cols', c);
document.documentElement.style.setProperty('--cell-size', s + 'px');
gridEl.innerHTML = '';
const totalDots = r * c;
for (let i = 0; i < totalDots; i++) {
const dot = document.createElement('div');
dot.className = 'dot';
gridEl.appendChild(dot);
}
updateAllColors();
updateDensity();
}
function toggleAnimation() {
if (animateToggle.checked) {
const pid = parseInt(paletteSelect.value);
let speed = 250;
if ([11, 14, 16].includes(pid)) speed = 800;
if (pid === 12) speed = 100;
clearInterval(animationInterval);
animationInterval = setInterval(updateAllColors, speed);
} else {
clearInterval(animationInterval);
}
}
// --- EVENTS ---
inputs.density.addEventListener('input', updateDensity);
paletteSelect.addEventListener('change', () => {
updateAllColors();
toggleAnimation(); // Restart animation to pick up speed changes
});
regenBtn.addEventListener('click', regenerate);
animateToggle.addEventListener('change', toggleAnimation);
// Allow inputs to trigger regeneration on change
inputs.cols.addEventListener('change', regenerate);
inputs.rows.addEventListener('change', regenerate);
inputs.size.addEventListener('change', regenerate);
// Initial Build
regenerate();
});
</script>
</body>
</html>
Only when density is less than 1, matrix should be redrawn, varying the pattern and holes each cycle
Here is the updated code.
I have modified the Animation Loop. Now, if the Density slider is set to anything less than 100%, the script will re-roll the position of the holes on every animation cycle.
This creates a "static noise" or "shimmering data" effect where the holes dance around the grid, rather than staying fixed.
Key Changes:
Animation Logic: The
setIntervalloop now checksif (density < 100). If true, it callsupdateDensity()every frame, causing the holes to move.CSS Tuning: I changed the
opacitytransition to0.1s(down from 0.3s). This makes the holes appear/disappear instantly (like a redraw) rather than fading slowly, which looks cleaner for this effect.
Add a mask overlay of various shapes (8) that can rotate in front of the matrix to change the "shape of the view"
This is a significant update. To achieve a rotating mask placed in front of the grid without rotating the grid itself, we need a specific HTML structure and some CSS transform tricks.
Key Changes:
HTML Structure: I have added a new container,
<div class="mask-container">, which wraps around the existing<div class="grid-container">.CSS Masking: The
.mask-containernow uses the CSSmask-image(and webkit prefix) property.The Rotation Trick: To rotate the "view" without rotating the dots inside, we apply a positive rotation to the outer
.mask-containerand an equal, negative rotation to the inner.grid-containerto counteract it.SVG Data URIs: I have defined 8 distinct shapes (plus a "None" option) using Base64 encoded SVGs within the JavaScript. This keeps everything in one file and ensures sharp edges.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Generative Art Grid with Rotating Masks</title>
<style>
:root {
--cell-size: 35px;
--gap-size: 8px;
--cols: 20;
--rows: 12;
--frame-color: #2b2b2b;
--bg-color: #e0e0e0;
/* New variables for masking */
--mask-image: none;
--mask-rotate: 0deg;
--grid-counter-rotate: 0deg;
}
body {
font-family: system-ui, -apple-system, sans-serif;
background-color: var(--bg-color);
color: #333;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 20px;
transition: background-color 0.5s ease;
}
/* --- Controls Section --- */
.controls {
background: white;
padding: 15px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
margin-bottom: 20px;
display: flex;
gap: 15px;
flex-wrap: wrap;
justify-content: center;
align-items: center;
max-width: 950px;
}
.control-group {
display: flex;
flex-direction: column;
}
label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 4px;
font-weight: 700;
color: #555;
}
input[type="number"], select, input[type="range"] {
padding: 6px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
}
input[type="range"] { width: 100px; }
select { min-width: 140px; }
.toggle-group {
display: flex;
align-items: center;
gap: 8px;
background: #f0f0f0;
padding: 5px 12px;
border-radius: 20px;
border: 1px solid #ddd;
}
button {
background: #222;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
transition: background 0.2s;
}
button:hover { background: #444; }
/* --- The Art Frame --- */
.art-frame {
background-color: #1a1a1a;
border: 15px solid var(--frame-color);
padding: 30px;
box-shadow: inset 0 0 50px rgba(0,0,0,0.5), 0 20px 40px rgba(0,0,0,0.4);
/* Ensure the mask doesn't spill out of the frame */
overflow: hidden;
border-radius: 4px;
transition: border-color 0.5s ease;
display: flex;
justify-content: center;
align-items: center;
}
/* --- NEW: The Mask Container --- */
/* This wraps the grid and applies the shape mask */
.mask-container {
/* Apply the mask image defined in JS */
-webkit-mask-image: var(--mask-image);
mask-image: var(--mask-image);
-webkit-mask-size: contain;
mask-size: contain;
-webkit-mask-position: center;
mask-position: center;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
/* Apply rotation to the mask container */
transform: rotate(var(--mask-rotate));
/* Smooth rotation transition */
transition: transform 0.1s linear;
/* Ensure it centers correctly in the frame */
display: flex;
justify-content: center;
align-items: center;
}
/* --- The Grid Layout --- */
.grid-container {
display: grid;
grid-template-columns: repeat(var(--cols), var(--cell-size));
grid-template-rows: repeat(var(--rows), var(--cell-size));
gap: var(--gap-size);
/* IMPORTANT: Counter-rotate the grid so the dots stay upright
while the mask container spins around them */
transform: rotate(var(--grid-counter-rotate));
transition: transform 0.1s linear;
}
/* --- The Circles (Dots) --- */
.dot {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #555;
transition: transform 0.1s, opacity 0.1s;
}
.dot:hover {
transform: scale(1.15);
z-index: 10;
box-shadow: 0 0 10px rgba(255,255,255,0.5);
}
.dot.hidden { opacity: 0; pointer-events: none; }
</style>
</head>
<body>
<div class="controls">
<div class="control-group" style="flex-basis: 100%; flex-direction: row; gap: 15px; justify-content: center; border-bottom: 1px solid #eee; padding-bottom: 15px; margin-bottom: 5px;">
<div class="control-group">
<label for="maskSelect">Mask Shape</label>
<select id="maskSelect">
<option value="none">None (Full View)</option>
<option value="circle">Circle</option>
<option value="square">Square</option>
<option value="diamond">Diamond</option>
<option value="triangle">Triangle</option>
<option value="hexagon">Hexagon</option>
<option value="star">Star</option>
<option value="heart">Heart</option>
<option value="ring">Ring</option>
</select>
</div>
<div class="control-group">
<label for="maskRotation">Mask Rotation</label>
<input type="range" id="maskRotation" value="0" min="0" max="360">
</div>
</div>
<div class="control-group">
<label for="paletteSelect">Color Palette</label>
<select id="paletteSelect">
<option value="1">1. Vibrant Marble</option>
<option value="2">2. Shiny Metal</option>
<option value="3">3. Neon Cyberpunk</option>
<option value="4">4. Pastel Dreams</option>
<option value="5">5. Deep Ocean</option>
<option value="6">6. Magma & Ash</option>
<option value="7">7. Noir Grayscale</option>
<option value="8">8. Retro Vaporwave</option>
<option value="9">9. Forest Camo</option>
<option value="10">10. Candy Gloss</option>
<option value="11">11. Gas Giants</option>
<option value="12">12. Matrix Digital</option>
<option value="13">13. Rusty Industrial</option>
<option value="14">14. Frozen Ice</option>
<option value="15">15. Midnight Sky</option>
<option value="16">16. Golden Luxury</option>
</select>
</div>
<div class="control-group">
<label for="cols">Cols</label>
<input type="number" id="cols" value="20" min="1" max="50" style="width: 50px;">
</div>
<div class="control-group">
<label for="rows">Rows</label>
<input type="number" id="rows" value="12" min="1" max="50" style="width: 50px;">
</div>
<div class="control-group">
<label for="size">Size</label>
<input type="number" id="size" value="35" min="10" max="100" style="width: 50px;">
</div>
<div class="control-group">
<label for="density">Density %</label>
<input type="range" id="density" value="100" min="0" max="100">
</div>
<div class="toggle-group">
<label for="animateToggle" style="margin:0; cursor:pointer;">Animate</label>
<input type="checkbox" id="animateToggle" style="cursor:pointer;">
</div>
<button id="regenBtn">Regenerate</button>
</div>
<div class="art-frame">
<div class="mask-container" id="maskContainer">
<div class="grid-container" id="grid"></div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const gridEl = document.getElementById('grid');
const maskContainer = document.getElementById('maskContainer');
const paletteSelect = document.getElementById('paletteSelect');
const maskSelect = document.getElementById('maskSelect');
const maskRotation = document.getElementById('maskRotation');
const animateToggle = document.getElementById('animateToggle');
const regenBtn = document.getElementById('regenBtn');
const inputs = {
cols: document.getElementById('cols'),
rows: document.getElementById('rows'),
size: document.getElementById('size'),
density: document.getElementById('density')
};
let animationInterval = null;
const rand = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
const randHue = () => Math.floor(Math.random() * 360);
// --- MASK SHAPE DEFINITIONS (Base64 Encoded SVGs) ---
// Black = Visible area, Transparent = Hidden area
const maskShapes = {
none: 'none',
circle: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="50" fill="black"/></svg>')`,
square: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect x="15" y="15" width="70" height="70" fill="black"/></svg>')`,
diamond: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><polygon points="50,0 100,50 50,100 0,50" fill="black"/></svg>')`,
triangle: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><polygon points="50,0 100,86.6 0,86.6" fill="black"/></svg>')`,
hexagon: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><polygon points="50,0 93.3,25 93.3,75 50,100 6.7,75 6.7,25" fill="black"/></svg>')`,
star: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><polygon points="50,0 61,35 98,35 68,57 79,91 50,70 21,91 32,57 2,35 39,35" fill="black"/></svg>')`,
heart: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path d="M50,88.9C25.5,63.6,9.4,44.4,9.4,28C9.4,14.6,19.9,4.1,33.3,4.1c7.5,0,14.8,3.5,19.4,9c4.7-5.5,11.9-9,19.4-9C85.5,4.1,96,14.6,96,28c0,16.4-16.1,35.6-40.6,60.9L50,88.9z" fill="black"/></svg>')`,
// Ring is created by a black circle and a smaller white (transparent) circle on top
ring: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="50" fill="black"/><circle cx="50" cy="50" r="25" fill="white"/></svg>')`
};
// --- PALETTE GENERATORS (Shortened for brevity, functionality is same) ---
function getGradientStyle(paletteId) {
const id = parseInt(paletteId);
try {
switch(id) {
case 1: return Math.random()<.5 ? `radial-gradient(circle at 30% 30%, hsl(${randHue()},90%,60%), hsl(${randHue()},80%,40%))` : `linear-gradient(${rand(0,360)}deg, hsl(${randHue()},80%,50%), hsl(${randHue()},80%,60%))`;
case 2: const t=Math.random();let h=0,s='0%',l='50%',b='#000';if(t<.25){h=45;s='80%';b='#530';}else if(t<.5){l=rand(50,90)+'%';}else if(t<.75){h=20;s='60%';b='#310';}else{h=randHue();s='80%';} return `radial-gradient(circle at 30% 30%, rgba(255,255,255,0.95)0%, rgba(255,255,255,0.1)20%, hsl(${h},${s},${l})20%, ${b} 100%)`;
case 3: return `radial-gradient(circle, transparent 30%, hsl(${[300,320,180,60][rand(0,3)]},100%,50%) 90%, black 100%), linear-gradient(135deg, #111, #222)`;
case 4: return `radial-gradient(circle at 70% 20%, white 10%, hsl(${randHue()},70%,85%) 50%, hsl(${randHue()},70%,80%) 100%)`;
case 5: return `radial-gradient(circle at 50% 50%, hsl(${rand(170,240)},70%,40%), hsl(${rand(170,240)},80%,15%))`;
case 6: return `radial-gradient(circle at ${rand(30,70)}% ${rand(30,70)}%, #fffa 0%, hsl(${rand(0,40)},100%,50%) 40%, #000 100%)`;
case 7: return `linear-gradient(${rand(0,180)}deg, hsl(0,0%,${rand(20,80)}%), hsl(0,0%,${rand(0,50)}%))`;
case 8: return `linear-gradient(${rand(0,180)}deg, #ff71ce, #01cdfe)`;
case 9: return `conic-gradient(from ${rand(0,360)}deg, #576b4a, #4b5e32, #3a4a28)`;
case 10: return `radial-gradient(circle at 30% 30%, rgba(255,255,255,0.9)0%, hsl(${randHue()},90%,60%)30%, hsl(${randHue()},100%,40%)100%)`;
case 11: return `linear-gradient(${rand(-20,20)}deg, hsl(${randHue()},50%,50%)0%, hsl(${randHue()},40%,60%)100%)`;
case 12: return `radial-gradient(circle, hsl(120,100%,${rand(20,80)}%)0%, black 70%)`;
case 13: return `repeating-linear-gradient(45deg, #555, #666 5px, #444 10px)`;
case 14: return `linear-gradient(${rand(0,360)}deg, rgba(255,255,255,0.9), rgba(180,230,255,0.7)), radial-gradient(circle, #fff, #ace)`;
case 15: return `radial-gradient(2px 2px at ${rand(10,90)}% ${rand(10,90)}%, white, transparent), #002`;
case 16: return `linear-gradient(${rand(120,160)}deg, #bf953f, #fcf6ba, #aa771c)`;
default: return '#666';
}
} catch (e) { return '#666'; }
}
// --- CORE LOGIC ---
function updateAllColors() {
const pid = parseInt(paletteSelect.value);
const frame = document.querySelector('.art-frame');
if ([3, 5, 6, 12, 15].includes(pid)) {
frame.style.backgroundColor = "#000";
document.documentElement.style.setProperty('--frame-color', '#111');
} else if ([4, 10, 14, 16].includes(pid)) {
frame.style.backgroundColor = "#fff";
document.documentElement.style.setProperty('--frame-color', '#d2b48c');
} else {
frame.style.backgroundColor = "#1a1a1a";
document.documentElement.style.setProperty('--frame-color', '#2b2b2b');
}
const dots = document.getElementsByClassName('dot');
for (let dot of dots) {
dot.style.background = getGradientStyle(pid);
dot.style.boxShadow = (pid === 2 || pid === 10) ? "2px 5px 10px rgba(0,0,0,0.4)" : "none";
if(pid === 3) dot.style.boxShadow = "0 0 5px currentColor";
}
}
function updateDensity() {
const dots = document.getElementsByClassName('dot');
const densityVal = parseInt(inputs.density.value);
for (let dot of dots) {
dot.classList.toggle('hidden', Math.random() * 100 >= densityVal);
}
}
// --- NEW: Mask Update Function ---
function updateMask() {
const shape = maskSelect.value;
const rotation = maskRotation.value;
// 1. Set the mask image variable
document.documentElement.style.setProperty('--mask-image', maskShapes[shape]);
// 2. Rotate the container positive amount
document.documentElement.style.setProperty('--mask-rotate', rotation + 'deg');
// 3. Counter-rotate the grid negative amount so dots stay still
document.documentElement.style.setProperty('--grid-counter-rotate', '-' + rotation + 'deg');
}
function regenerate() {
const r = inputs.rows.value; const c = inputs.cols.value; const s = inputs.size.value;
document.documentElement.style.setProperty('--rows', r);
document.documentElement.style.setProperty('--cols', c);
document.documentElement.style.setProperty('--cell-size', s + 'px');
gridEl.innerHTML = '';
for (let i = 0; i < r * c; i++) {
const dot = document.createElement('div');
dot.className = 'dot';
gridEl.appendChild(dot);
}
updateAllColors();
updateDensity();
// Ensure mask is correct on regeneration
updateMask();
}
function runAnimationStep() {
updateAllColors();
if (parseInt(inputs.density.value) < 100) updateDensity();
}
function toggleAnimation() {
if (animateToggle.checked) {
const pid = parseInt(paletteSelect.value);
let speed = ([11, 14, 16].includes(pid)) ? 800 : (pid === 12 ? 100 : 250);
clearInterval(animationInterval);
animationInterval = setInterval(runAnimationStep, speed);
} else {
clearInterval(animationInterval);
}
}
// --- EVENTS ---
inputs.density.addEventListener('input', updateDensity);
paletteSelect.addEventListener('change', () => { updateAllColors(); toggleAnimation(); });
regenBtn.addEventListener('click', regenerate);
animateToggle.addEventListener('change', toggleAnimation);
['cols', 'rows', 'size'].forEach(k => inputs[k].addEventListener('change', regenerate));
// New Mask Events
maskSelect.addEventListener('change', updateMask);
maskRotation.addEventListener('input', updateMask);
// Initial Build
regenerate();
});
</script>
</body>
</html>
Add a toggle for "Auto Mask Rotation"
Here is the updated solution.
I have added an Auto Rotate toggle next to the mask controls.
How it works: It uses
requestAnimationFrameto smoothly spin the mask (approximately 1 full rotation every 10 seconds).Visual Logic: As the mask spins, the code automatically counter-rotates the grid inside. This creates the illusion that the "viewing window" is spinning, but the dots themselves remain upright and don't get dizzying.
From <https://gemini.google.com/app/144ff61144575b33> Google Gemini 3.0 Pro (Thinking)







Comments
Post a Comment