Source: app.js

/**
 * Configuration object for application-wide settings
 * Centralizes magic numbers and configuration values
 * @constant {Object}
 */
const CONFIG = {
    canvas: {
        dpi: 300,
        width: 8.5 * 300, // 8.5in at 300dpi
        height: 11 * 300, // 11in at 300dpi
    },
    grid: {
        rows: 4,
        columns: 5,
        topMargin: 100,
        bottomMargin: 300,
        patternSize: 400,
        gutterRatio: 0.1, // 10% of pattern size
        patternRatio: 0.8  // 80% of space for pattern
    },
    style: {
        strokeWidth: 4,
        strokeColor: '#000'
    }
};

/**
 * Canvas Setup and Configuration
 * This section initializes a high-resolution canvas optimized for both screen display and printing.
 */
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

canvas.width = CONFIG.canvas.width;
canvas.height = CONFIG.canvas.height;

/**
 * Utility Functions Module
 * Pure functions for common operations
 * @namespace Utils
 */
const Utils = {
    /**
     * Generates a random number within a range
     * @param {number} min - Minimum value
     * @param {number} max - Maximum value
     * @returns {number}
     */
    random: (min, max) => Math.random() * (max - min) + min,
    
    /**
     * Generates a random integer within a range
     * @param {number} min - Minimum value
     * @param {number} max - Maximum value
     * @returns {number}
     */
    randomInt: (min, max) => Math.floor(Utils.random(min, max)),
    
    /**
     * Randomly selects an item from an array
     * @param {Array} arr - Source array
     * @returns {*}
     */
    randomChoice: arr => arr[Math.floor(Math.random() * arr.length)],
    
    /**
     * Fisher-Yates shuffle implementation
     * @param {Array} arr - Array to shuffle
     * @returns {Array} New shuffled array
     */
    shuffle: arr => [...arr].sort(() => Math.random() - 0.5),
    
    /**
     * Creates a range array of numbers
     * @param {number} start - Start value
     * @param {number} end - End value
     * @returns {Array<number>}
     */
    range: (start, end) => Array.from(
        { length: end - start }, 
        (_, i) => start + i
    )
};

/**
 * Drawing Context Manager
 * Handles canvas context state management using functional composition
 * @namespace ContextManager
 */
const ContextManager = {
    /**
     * Executes a drawing operation with saved context state
     * @param {Function} drawFn - Drawing function to execute
     * @returns {Function}
     */
    withContext: drawFn => (...args) => {
        ctx.save();
        drawFn(...args);
        ctx.restore();
    },

    /**
     * Applies a translation transformation
     * @param {number} x - X translation
     * @param {number} y - Y translation
     * @returns {Function}
     */
    withTranslation: (x, y) => drawFn => (...args) => {
        ctx.translate(x, y);
        drawFn(...args);
    },

    /**
     * Applies a rotation transformation around a point
     * @param {number} angle - Rotation angle in radians
     * @param {number} x - Center X coordinate
     * @param {number} y - Center Y coordinate
     * @returns {Function}
     */
    withRotation: (angle, x, y) => drawFn => (...args) => {
        ctx.translate(x, y);
        ctx.rotate(angle);
        ctx.translate(-x, -y);
        drawFn(...args);
    }
};

/**
 * Shape Factory Pattern
 * @namespace Shapes
 */
const Shapes = {
    /**
     * Creates a circle with optional fill
     * @param {number} x - Center X coordinate
     * @param {number} y - Center Y coordinate
     * @param {number} size - Reference size for radius calculation
     * @param {Object} params - Optional parameters for customization
     * @param {number} [params.radius] - Optional explicit radius
     * @param {boolean} [params.fill] - Whether to fill the circle
     */
    circle: (x, y, size, params = {}) => {
        const radius = params.radius || size/3;
        ctx.beginPath();
        ctx.arc(x, y, radius, 0, Math.PI * 2);
        params.fill ? ctx.fill() : ctx.stroke();
    },
    
    line: (x1, y1, x2, y2) => {
        ctx.beginPath();
        ctx.moveTo(x1, y1);
        ctx.lineTo(x2, y2);
        ctx.stroke();
    },
    
    triangle: (x, y, size) => {
        ctx.beginPath();
        ctx.moveTo(x, y + size);
        ctx.lineTo(x + size/2, y);
        ctx.lineTo(x + size, y + size);
        ctx.closePath();
        ctx.stroke();
    },

    square: (x, y, size) => {
        ctx.strokeRect(x, y, size, size);
    }
};

/**
 * Pattern Generator System
 * @namespace Patterns
 */
const Patterns = {
    /**
     * Creates a pattern generator with transformation capabilities
     * @param {Function} patternFn - Base pattern drawing function
     * @returns {Function} Enhanced pattern generator
     */
    createGenerator: patternFn => {
        return ContextManager.withContext((x, y, size) => {
            const rotation = Math.PI/2 * Utils.randomInt(0, 4);
            if (rotation > 0) {
                ContextManager.withRotation(
                    rotation,
                    x + size/2,
                    y + size/2
                )(patternFn)(x, y, size);
            } else {
                patternFn(x, y, size);
            }
        });
    },

    /**
     * Collection of base pattern implementations
     * @type {Array<Function>}
     */
    types: [
        /**
         * Grid-based pattern strategy
         * Demonstrates use of nested loops for regular grid generation
         * @param {number} x - Starting X coordinate
         * @param {number} y - Starting Y coordinate
         * @param {number} size - Pattern size
         */
        (x, y, size) => {
            const spacing = size/3;
            for(let i = 0; i < 3; i++) {
                for(let j = 0; j < 3; j++) {
                    if((i + j) % 2 === 0) { // Checkerboard pattern
                        Shapes.circle(
                            x + spacing/2 + i * spacing,
                            y + spacing/2 + j * spacing,
                            spacing/2
                        );
                    }
                }
            }
        },

        // Nested squares
        (x, y, size) => {
            for(let i = 3; i > 0; i--) {
                const offset = (3 - i) * size/6;
                const squareSize = size - offset * 2;
                Shapes.square(x + offset, y + offset, squareSize);
            }
        },

        // Simple flower pattern
        (x, y, size) => {
            const center = size/2;
            const radius = size/4;
            
            // Center circle
            Shapes.circle(x + center, y + center, size/6);
            
            // Petals
            for(let i = 0; i < 6; i++) {
                const angle = (i / 6) * Math.PI * 2;
                const petalX = x + center + Math.cos(angle) * radius;
                const petalY = y + center + Math.sin(angle) * radius;
                Shapes.circle(petalX, petalY, size/6);
            }
        },

        // Triangles in a row
        (x, y, size) => {
            const triSize = size/3;
            for(let i = 0; i < 3; i++) {
                Shapes.triangle(
                    x + i * triSize,
                    y + (i % 2) * triSize/2,
                    triSize
                );
            }
        },

        // Simple grid of squares
        (x, y, size) => {
            const gridSize = size/2;
            for(let i = 0; i < 2; i++) {
                for(let j = 0; j < 2; j++) {
                    Shapes.square(
                        x + i * gridSize + size/8,
                        y + j * gridSize + size/8,
                        gridSize * 0.75
                    );
                }
            }
        },

        // Alternating circles and squares
        (x, y, size) => {
            const spacing = size/2;
            for(let i = 0; i < 2; i++) {
                for(let j = 0; j < 2; j++) {
                    if((i + j) % 2 === 0) {
                        Shapes.circle(
                            x + spacing/2 + i * spacing,
                            y + spacing/2 + j * spacing,
                            spacing/3
                        );
                    } else {
                        Shapes.square(
                            x + i * spacing + spacing/6,
                            y + j * spacing + spacing/6,
                            spacing * 2/3
                        );
                    }
                }
            }
        },

        // Simple star pattern
        (x, y, size) => {
            const center = size/2;
            // Horizontal and vertical lines
            Shapes.line(x, y + center, x + size, y + center);
            Shapes.line(x + center, y, x + center, y + size);
            // Diagonal lines
            Shapes.line(x, y, x + size, y + size);
            Shapes.line(x + size, y, x, y + size);
        },

        // Nested arcs
        (x, y, size) => {
            const center = size/2;
            for(let i = 1; i <= 4; i++) {
                const radius = (size/2) * (i/4);
                ctx.beginPath();
                ctx.arc(x + center, y + center, radius, 0, Math.PI);
                ctx.stroke();
            }
        },

        // Quarter circles in corners
        (x, y, size) => {
            const radius = size/2;
            // Top left
            ctx.beginPath();
            ctx.arc(x, y, radius, 0, Math.PI/2);
            ctx.stroke();
            // Top right
            ctx.beginPath();
            ctx.arc(x + size, y, radius, Math.PI/2, Math.PI);
            ctx.stroke();
            // Bottom right
            ctx.beginPath();
            ctx.arc(x + size, y + size, radius, Math.PI, Math.PI * 3/2);
            ctx.stroke();
            // Bottom left
            ctx.beginPath();
            ctx.arc(x, y + size, radius, Math.PI * 3/2, Math.PI * 2);
            ctx.stroke();
        },

        // Concentric circles
        (x, y, size) => {
            const center = size/2;
            for(let i = 1; i <= 3; i++) {
                Shapes.circle(
                    x + center,
                    y + center,
                    size,
                    {radius: (size/2) * (i/3)}
                );
            }
        },

        // Nested diamonds
        (x, y, size) => {
            const center = size/2;
            for(let i = 1; i <= 3; i++) {
                const offset = (size/2) * (i/3);
                ctx.beginPath();
                ctx.moveTo(x + center, y + center - offset);
                ctx.lineTo(x + center + offset, y + center);
                ctx.lineTo(x + center, y + center + offset);
                ctx.lineTo(x + center - offset, y + center);
                ctx.closePath();
                ctx.stroke();
            }
        },

        // Radiating arcs
        (x, y, size) => {
            const center = size/2;
            const radius = size/3;
            for(let i = 0; i < 4; i++) {
                const startAngle = (Math.PI/2) * i;
                ctx.beginPath();
                ctx.arc(x + center, y + center, radius, startAngle, startAngle + Math.PI/2);
                ctx.stroke();
            }
        },

        // Stacked semicircles
        (x, y, size) => {
            const width = size * 0.8;
            for(let i = 0; i < 3; i++) {
                ctx.beginPath();
                ctx.arc(
                    x + size/2,
                    y + (size/3) * (i + 1),
                    width/2,
                    0,
                    Math.PI,
                    i % 2 === 0
                );
                ctx.stroke();
            }
        }
    ]
};

/**
 * Layout System
 * Handles grid layout and composition
 * @namespace Layout
 */
const Layout = {
    /**
     * Calculates layout metrics for the grid
     * @returns {Object} Layout calculations
     */
    calculateMetrics: () => {
        const availableHeight = CONFIG.canvas.height - 
            (CONFIG.grid.topMargin + CONFIG.grid.bottomMargin);
        
        const totalPatternHeight = CONFIG.grid.rows * CONFIG.grid.patternSize;
        const totalGapHeight = availableHeight - totalPatternHeight;
        const rowGap = totalGapHeight / (CONFIG.grid.rows - 1);
        
        const totalWidth = CONFIG.grid.patternSize * CONFIG.grid.columns;
        const xOffset = (CONFIG.canvas.width - totalWidth) / 2;
        
        return { rowGap, xOffset };
    },

    /**
     * Draws a row of patterns
     */
    drawRow: (xOffset, y, size) => {
        const gutter = size * CONFIG.grid.gutterRatio;
        const patternSize = size * CONFIG.grid.patternRatio;
        
        const patterns = Utils.shuffle(patternGenerators);
        Utils.range(0, CONFIG.grid.columns).forEach(i => {
            const xPos = xOffset + i * size + gutter;
            const yPos = y + gutter;
            patterns[i](xPos, yPos, patternSize);
        });
    },

    /**
     * Draws the complete grid
     */
    drawGrid: () => {
        ctx.clearRect(0, 0, CONFIG.canvas.width, CONFIG.canvas.height);
        
        ctx.strokeStyle = CONFIG.style.strokeColor;
        ctx.lineWidth = CONFIG.style.strokeWidth;
        
        const { rowGap, xOffset } = Layout.calculateMetrics();
        
        Utils.range(0, CONFIG.grid.rows).forEach(i => {
            const y = CONFIG.grid.topMargin + 
                i * (CONFIG.grid.patternSize + rowGap);
            Layout.drawRow(xOffset, y, CONFIG.grid.patternSize);
        });
    }
};

// Generate pattern instances
const patternGenerators = Utils.range(0, 10)
    .map(() => Patterns.createGenerator(
        Utils.randomChoice(Patterns.types)
    ));

// Initialize and set up interaction
Layout.drawGrid();
canvas.addEventListener('click', Layout.drawGrid);