#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:

  1. 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.

  2. Generative Styling: To mimic the colorful, marble-like dots in your image, I created a JavaScript function getRandomGradient(). It randomly applies conic-gradient (for swirls), radial-gradient (for spheres), or linear-gradient (for stripes) using HSL color logic to ensure the colors remain vibrant.

  3. Frame Styling: The .art-frame class includes a solid border and box-shadow to replicate the physical wooden frame and the wall shadow seen in your photo.

  4. 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:

  1. Shiny Metal Palette: I replaced the previous color logic with a getMetallicGradient function. 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).

  2. 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.

  3. Optimization: The animation logic updates the style.background of existing dots rather than rebuilding the entire HTML structure, ensuring smooth performance.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Metallic Art Grid</title>
    <style>
        :root {
            --cell-size: 40px;
            --gap-size: 10px;
            --cols: 20;
            --rows: 12;
            --frame-color: #2b2b2b; /* Darker, industrial frame */
        }

        body {
            font-family: system-ui, -apple-system, sans-serif;
            background-color: #e0e0e0;
            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;
            align-items: center;
        }

        .control-group {
            display: flex;
            flex-direction: column;
        }

        /* Toggle Switch Styling */
        .toggle-group {
            display: flex;
            align-items: center;
            gap: 10px;
            background: #f4f4f4;
            padding: 5px 15px;
            border-radius: 20px;
            border: 1px solid #ddd;
        }

        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;
        }

        button:hover {
            background: #555;
        }

        /* --- The Art Frame --- */
        .art-frame {
            background-color: #1a1a1a; /* Dark background enhances shiny colors */
            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;
        }

        /* --- 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%;
            /* Shadow adds to the 3D sphere illusion */
            box-shadow: 2px 5px 10px rgba(0,0,0,0.5);
            transition: transform 0.1s;
        }

        .dot:hover {
            transform: scale(1.1);
            cursor: pointer;
            filter: brightness(1.2);
        }

    </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>

        <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()">Refresh Colors</button>
    </div>

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

    <script>
        const gridEl = document.getElementById('grid');
        const animateToggle = document.getElementById('animateToggle');
        const inputs = {
            cols: document.getElementById('cols'),
            rows: document.getElementById('rows'),
            size: document.getElementById('size'),
            gap: document.getElementById('gap')
        };

        let animationInterval = null;

        // Function to create "Shiny Metal" gradients
        function getMetallicGradient() {
            // Determine the "base" metal type
            const metalType = Math.random();
            let hue, sat, light, tint;

            if (metalType < 0.25) {
                // GOLD / BRASS
                hue = Math.floor(Math.random() * 20) + 35; // 35-55
                sat = '80%';
                light = '50%';
                tint = `hsl(${hue}, ${sat}, 30%)`; // darker shadow
            } else if (metalType < 0.50) {
                // SILVER / CHROME / STEEL
                hue = Math.floor(Math.random() * 360);
                sat = '0%'; // No saturation for pure greyscale
                light = Math.floor(Math.random() * 40) + 40 + '%'; // 40-80%
                tint = '#000';
            } else if (metalType < 0.70) {
                // COPPER / BRONZE
                hue = Math.floor(Math.random() * 20) + 10; 
                sat = '60%';
                light = '50%';
                tint = `hsl(${hue}, ${sat}, 20%)`;
            } else {
                // ANODIZED COLORS (Shiny Blue, Purple, Green, Red)
                hue = Math.floor(Math.random() * 360);
                sat = '80%';
                light = '50%';
                tint = `hsl(${hue}, 90%, 20%)`;
            }

            const baseColor = `hsl(${hue}, ${sat}, ${light})`;
            
            // Radial Gradient construction for 3D Sphere look:
            // 1. White Hotspot (Reflection) at top left
            // 2. Base Color
            // 3. Dark shadow at bottom right
            return `radial-gradient(
                circle at 30% 30%, 
                rgba(255,255,255,0.95) 0%, 
                rgba(255,255,255,0.8) 5%, 
                ${baseColor} 20%, 
                ${tint} 90%, 
                black 100%
            )`;
        }

        // Updates colors of EXISTING dots (Efficient for animation)
        function updateAllColors() {
            const dots = document.getElementsByClassName('dot');
            for (let dot of dots) {
                dot.style.background = getMetallicGradient();
            }
        }

        // Rebuilds the DOM (Used when changing Rows/Cols)
        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');
            document.documentElement.style.setProperty('--gap-size', inputs.gap.value + 'px');

            // Clear and Rebuild
            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';
                dot.style.background = getMetallicGradient();
                gridEl.appendChild(dot);
            }
        }

        // Animation Loop Logic
        function toggleAnimation() {
            if (animateToggle.checked) {
                // Update colors every 250ms
                animationInterval = setInterval(updateAllColors, 250);
            } else {
                clearInterval(animationInterval);
                animationInterval = null;
            }
        }

        // Event Listeners
        Object.values(inputs).forEach(input => {
            input.addEventListener('input', generateGrid);
        });

        animateToggle.addEventListener('change', toggleAnimation);

        // Initial Generation
        generateGrid();
    </script>
</body>
</html>

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".

<!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 with Holes</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: 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;
        }
        
        /* Density Slider */
        input[type="range"] {
            width: 100px;
            cursor: pointer;
        }
        
        select {
            cursor: pointer;
            min-width: 160px;
        }

        /* 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, opacity 0.3s; /* Smooth fade for holes */
        }

        .dot:hover {
            transform: scale(1.15);
            z-index: 10;
            box-shadow: 0 0 10px rgba(255,255,255,0.5);
        }

        /* Class for "Holes" */
        .dot.hidden {
            opacity: 0;
            pointer-events: none; /* Mouse passes through holes */
        }

    </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</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" title="Adjust the number of holes">
        </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="regenerate()">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 animateToggle = document.getElementById('animateToggle');
        
        const inputs = {
            cols: document.getElementById('cols'),
            rows: document.getElementById('rows'),
            size: document.getElementById('size'),
            density: document.getElementById('density') // NEW
        };

        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 (Same as previous) ---
        function getGradientStyle(paletteId) {
            const id = parseInt(paletteId);
            switch(id) {
                case 1: { // Vibrant Marble
                    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%))`;
                }
                case 2: { // Shiny Metal
                    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%)`; } 
                    else if (metalType < 0.5) { h=0; s='0%'; l=rand(40,80)+'%'; tint='#000'; } 
                    else if (metalType < 0.7) { h=rand(10,30); s='60%'; l='50%'; tint=`hsl(${h},${s},20%)`; } 
                    else { h=randHue(); s='80%'; l='50%'; tint=`hsl(${h},90%,20%)`; } 
                    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%)`;
                }
                case 3: { // Neon Cyberpunk
                    const h = [300, 320, 180, 60][rand(0,3)];
                    return `radial-gradient(circle, transparent 30%, hsl(${h}, 100%, 50%) 90%, black 100%), linear-gradient(135deg, #111, #222)`;
                }
                case 4: { // Pastel Dreams
                    const h = randHue();
                    return `radial-gradient(circle at 70% 20%, white 10%, hsl(${h}, 70%, 85%) 50%, hsl(${(h+60)%360}, 70%, 80%) 100%)`;
                }
                case 5: { // Deep Ocean
                    const h = rand(170, 240);
                    return `radial-gradient(circle at 50% 50%, hsl(${h}, 70%, 40%), hsl(${h}, 80%, 15%))`;
                }
                case 6: { // Magma & Ash
                    if (Math.random() > 0.7) return `radial-gradient(circle, #333 20%, #111 100%)`;
                    const h = rand(0, 40);
                    return `radial-gradient(circle at ${rand(20,80)}% ${rand(20,80)}%, #fffa 0%, hsl(${h}, 100%, 50%) 40%, #500 80%, #000 100%)`;
                }
                case 7: { // Noir Grayscale
                    const l = rand(10, 90);
                    return `linear-gradient(${rand(0,180)}deg, hsl(0,0%,${l}%), hsl(0,0%,${l-10}%))`;
                }
                case 8: { // Retro Vaporwave
                    return `linear-gradient(${rand(0,180)}deg, #ff71ce, #01cdfe)`;
                }
                case 9: { // Forest Camo
                    const h = rand(80, 140); 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}%))`;
                }
                case 10: { // Candy Gloss
                    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%)`;
                }
                case 11: { // Gas Giants
                    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%)`;
                }
                case 12: { // Matrix Digital
                    const l = rand(10, 80);
                    return `radial-gradient(circle, hsl(120, 100%, ${l}%) 0%, black 70%)`;
                }
                case 13: { // Rusty Industrial
                    const h = rand(10, 30);
                    return `repeating-linear-gradient(45deg, hsl(${h}, 60%, 30%), hsl(${h}, 50%, 25%) 5px, hsl(${h}, 40%, 20%) 10px)`;
                }
                case 14: { // Frozen Ice
                    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==')`;
                }
                case 15: { // Midnight Sky
                    return `radial-gradient(1px 1px at ${rand(10,90)}% ${rand(10,90)}%, white, transparent), linear-gradient(to bottom, #000022, #111144)`;
                }
                case 16: { // Golden Luxury
                    return `linear-gradient(${rand(120,160)}deg, #bf953f, #fcf6ba, #b38728, #fbf5b7, #aa771c)`;
                }
                default: return '#ccc';
            }
        }

        // --- CORE FUNCTIONS ---

        function updateAllColors() {
            // Update Body/Frame Theme
            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) {
                // Apply color logic
                dot.style.background = getGradientStyle(paletteSelect.value);
                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";
            }
        }

        // Separate function to handle "Holes" so they don't jitter during animation
        function updateDensity() {
            const dots = document.getElementsByClassName('dot');
            const densityVal = parseInt(inputs.density.value);
            
            for (let dot of dots) {
                // We use opacity 0 instead of display:none to keep the grid structure
                const isVisible = Math.random() * 100 < densityVal;
                if (isVisible) {
                    dot.classList.remove('hidden');
                } else {
                    dot.classList.add('hidden');
                }
            }
        }

        function regenerate() {
            // Update Grid 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);
            }
            
            // Apply initial colors and holes
            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);
                animationInterval = null;
            }
        }

        // Event Listeners
        // Re-roll holes when density slider changes
        inputs.density.addEventListener('input', updateDensity);
        
        // Re-generate grid structure when dimension inputs change
        ['cols', 'rows', 'size'].forEach(key => {
            inputs[key].addEventListener('input', regenerate);
        });

        animateToggle.addEventListener('change', toggleAnimation);

        // Initial Load
        regenerate();
    </script>
</body>
</html>


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:

  1. Animation Logic: The setInterval loop now checks if (density < 100). If true, it calls updateDensity() every frame, causing the holes to move.

  2. CSS Tuning: I changed the opacity transition to 0.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.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dynamic Density 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;
            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; 
            /* Fast opacity transition for "redraw" effect, smooth transform for hover */
            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">
            <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>
        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'; } 
                            else if (type < 0.5) { h=0; s='0%'; l=rand(50,90)+'%'; tint='#000'; } 
                            else if (type < 0.75) { h=20; s='60%'; l='50%'; tint='#310'; } 
                            else { h=randHue(); s='80%'; l='50%'; tint='#000'; } 
                            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
                            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) { 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) {
                    // Randomly decide visibility based on current density slider
                    const isVisible = Math.random() * 100 < densityVal;
                    if (isVisible) dot.classList.remove('hidden');
                    else dot.classList.add('hidden');
                }
            }

            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 = '';
                const totalDots = r * c;

                for (let i = 0; i < totalDots; i++) {
                    const dot = document.createElement('div');
                    dot.className = 'dot';
                    gridEl.appendChild(dot);
                }
                
                updateAllColors();
                updateDensity();
            }

            // --- ANIMATION LOOP ---
            function runAnimationStep() {
                // 1. Update Colors (Always)
                updateAllColors();

                // 2. If Density < 100%, re-roll the holes position (Vary the pattern)
                const densityVal = parseInt(inputs.density.value);
                if (densityVal < 100) {
                    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(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));

            // Initial Build
            regenerate();
        });
    </script>
</body>
</html>


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:

  1. HTML Structure: I have added a new container, <div class="mask-container">, which wraps around the existing <div class="grid-container">.

  2. CSS Masking: The .mask-container now uses the CSS mask-image (and webkit prefix) property.

  3. The Rotation Trick: To rotate the "view" without rotating the dots inside, we apply a positive rotation to the outer .mask-container and an equal, negative rotation to the inner .grid-container to counteract it.

  4. 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 requestAnimationFrame to 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.

<!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 Auto-Rotating Masks</title>
    <style>
        :root {
            --cell-size: 35px;
            --gap-size: 8px;
            --cols: 20;
            --rows: 12;
            --frame-color: #2b2b2b;
            --bg-color: #e0e0e0;
            /* Masking variables */
            --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);
            overflow: hidden; 
            border-radius: 4px;
            transition: border-color 0.5s ease;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        /* --- Mask Container --- */
        .mask-container {
            -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));
            /* Note: We removed the transition duration for rotation here 
               to allow smooth frame-by-frame animation without lag */
            
            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);
            
            /* Counter-rotate the grid so dots stay upright */
            transform: rotate(var(--grid-counter-rotate));
        }

        /* --- 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 Angle</label>
                <input type="range" id="maskRotation" value="0" min="0" max="360">
            </div>
            <div class="toggle-group" style="align-self: flex-end; margin-bottom: 2px;">
                <label for="autoRotateToggle" style="margin:0; cursor:pointer;">Auto Rotate</label>
                <input type="checkbox" id="autoRotateToggle" style="cursor:pointer;">
            </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 Colors</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 paletteSelect = document.getElementById('paletteSelect');
            const maskSelect = document.getElementById('maskSelect');
            const maskRotation = document.getElementById('maskRotation');
            const animateToggle = document.getElementById('animateToggle');
            const autoRotateToggle = document.getElementById('autoRotateToggle'); // NEW
            const regenBtn = document.getElementById('regenBtn');
            
            const inputs = {
                cols: document.getElementById('cols'),
                rows: document.getElementById('rows'),
                size: document.getElementById('size'),
                density: document.getElementById('density')
            };

            let colorAnimationInterval = null;
            let rotationAnimationFrame = null; // NEW
            let currentRotationAngle = 0;

            const rand = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
            const randHue = () => Math.floor(Math.random() * 360);

            // --- MASK SHAPES ---
            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: `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 ---
            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);
                }
            }

            function updateMask() {
                const shape = maskSelect.value;
                // Read from slider, unless auto-rotation is overriding it
                const rotation = parseFloat(maskRotation.value);
                
                document.documentElement.style.setProperty('--mask-image', maskShapes[shape]);
                document.documentElement.style.setProperty('--mask-rotate', rotation + 'deg');
                // Counter rotate to keep dots upright
                document.documentElement.style.setProperty('--grid-counter-rotate', '-' + rotation + 'deg');
            }

            // --- ANIMATION LOOPS ---

            // Loop 1: Color Animation (Interval)
            function runColorStep() {
                updateAllColors();
                if (parseInt(inputs.density.value) < 100) updateDensity();
            }

            function toggleColorAnimation() {
                if (animateToggle.checked) {
                    const pid = parseInt(paletteSelect.value);
                    let speed = ([11, 14, 16].includes(pid)) ? 800 : (pid === 12 ? 100 : 250);
                    clearInterval(colorAnimationInterval);
                    colorAnimationInterval = setInterval(runColorStep, speed);
                } else {
                    clearInterval(colorAnimationInterval);
                }
            }

            // Loop 2: Rotation Animation (Frame)
            function runRotationStep() {
                currentRotationAngle = (currentRotationAngle + 0.4) % 360; // Speed control
                maskRotation.value = currentRotationAngle;
                updateMask();
                rotationAnimationFrame = requestAnimationFrame(runRotationStep);
            }

            function toggleRotationAnimation() {
                if (autoRotateToggle.checked) {
                    // Start from current slider value
                    currentRotationAngle = parseFloat(maskRotation.value);
                    runRotationStep();
                } else {
                    cancelAnimationFrame(rotationAnimationFrame);
                }
            }

            // --- EVENTS ---
            inputs.density.addEventListener('input', updateDensity);
            
            paletteSelect.addEventListener('change', () => { 
                updateAllColors(); 
                toggleColorAnimation(); 
            });
            
            animateToggle.addEventListener('change', toggleColorAnimation);
            autoRotateToggle.addEventListener('change', toggleRotationAnimation); // NEW
            
            // Mask events
            maskSelect.addEventListener('change', updateMask);
            maskRotation.addEventListener('input', () => {
                // If user drags slider, update mask immediately
                updateMask();
                // If dragging while auto-rotate is on, update the tracking variable
                if(autoRotateToggle.checked) currentRotationAngle = parseFloat(maskRotation.value);
            });

            // Regeneration
            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();
                updateMask();
            }
            regenBtn.addEventListener('click', regenerate);
            ['cols', 'rows', 'size'].forEach(k => inputs[k].addEventListener('change', regenerate));

            // Initial Build
            regenerate();
        });
    </script>
</body>
</html>

From <https://gemini.google.com/app/144ff61144575b33>  Google Gemini 3.0 Pro (Thinking)

Comments

Popular posts from this blog

#2892 Example of ChatGPT o1 Prompt

#3105 Federal and State Spending Idiocy