| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Shape Generator</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <style> |
| .shape-canvas { |
| width: 256px; |
| height: 256px; |
| background-color: #f3f4f6; |
| position: relative; |
| overflow: hidden; |
| } |
| |
| .shape { |
| position: absolute; |
| background-color: #3b82f6; |
| } |
| |
| .circle { |
| border-radius: 50%; |
| } |
| |
| .triangle { |
| width: 0; |
| height: 0; |
| background-color: transparent !important; |
| border-left: solid transparent; |
| border-right: solid transparent; |
| border-bottom: solid #3b82f6; |
| } |
| |
| .download-btn { |
| transition: all 0.3s ease; |
| } |
| |
| .download-btn:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); |
| } |
| |
| @keyframes pulse { |
| 0%, 100% { |
| opacity: 1; |
| } |
| 50% { |
| opacity: 0.5; |
| } |
| } |
| |
| .generating { |
| animation: pulse 1.5s infinite; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-50 min-h-screen"> |
| <div class="container mx-auto px-4 py-12"> |
| <div class="text-center mb-12"> |
| <h1 class="text-4xl font-bold text-gray-800 mb-2">Shape Generator</h1> |
| <p class="text-lg text-gray-600 max-w-2xl mx-auto"> |
| Generate random shapes (circles, squares, and triangles) with customizable parameters. |
| Visualize and download your generated shapes. |
| </p> |
| </div> |
| |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-8"> |
| |
| <div class="bg-white rounded-xl shadow-md p-6"> |
| <h2 class="text-2xl font-semibold text-gray-800 mb-6">Generation Settings</h2> |
| |
| <div class="space-y-6"> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Shape Type</label> |
| <div class="grid grid-cols-3 gap-2"> |
| <button id="btn-circle" class="shape-btn py-2 px-4 rounded-md bg-blue-100 text-blue-700 font-medium border border-blue-200 hover:bg-blue-200 transition"> |
| Circle |
| </button> |
| <button id="btn-square" class="shape-btn py-2 px-4 rounded-md bg-blue-100 text-blue-700 font-medium border border-blue-200 hover:bg-blue-200 transition"> |
| Square |
| </button> |
| <button id="btn-triangle" class="shape-btn py-2 px-4 rounded-md bg-blue-100 text-blue-700 font-medium border border-blue-200 hover:bg-blue-200 transition"> |
| Triangle |
| </button> |
| </div> |
| </div> |
| |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Randomness</label> |
| <div class="flex items-center space-x-2"> |
| <input type="range" id="randomness" min="1" max="100" value="50" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"> |
| <span id="randomness-value" class="text-sm font-medium text-gray-700 w-10 text-center">50%</span> |
| </div> |
| </div> |
| |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Batch Size</label> |
| <select id="batch-size" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"> |
| <option value="1">1 shape</option> |
| <option value="5">5 shapes</option> |
| <option value="10">10 shapes</option> |
| <option value="20">20 shapes</option> |
| <option value="50">50 shapes</option> |
| <option value="100">100 shapes</option> |
| </select> |
| </div> |
| |
| <div class="pt-4"> |
| <button id="generate-btn" class="w-full py-3 px-4 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition flex items-center justify-center"> |
| <span>Generate Shapes</span> |
| <svg id="generate-spinner" class="hidden animate-spin -mr-1 ml-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> |
| <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> |
| <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> |
| </svg> |
| </button> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="bg-white rounded-xl shadow-md p-6"> |
| <div class="flex justify-between items-center mb-6"> |
| <h2 class="text-2xl font-semibold text-gray-800">Shape Preview</h2> |
| <div class="flex space-x-2"> |
| <button id="download-btn" class="download-btn py-2 px-4 bg-green-600 hover:bg-green-700 text-white text-sm font-medium rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition flex items-center"> |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /> |
| </svg> |
| Download |
| </button> |
| <button id="clear-btn" class="py-2 px-4 bg-gray-200 hover:bg-gray-300 text-gray-700 text-sm font-medium rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 transition"> |
| Clear |
| </button> |
| </div> |
| </div> |
| |
| <div class="flex flex-col items-center"> |
| <div class="shape-canvas mb-4 rounded-lg border border-gray-200" id="canvas"> |
| <div class="absolute inset-0 flex items-center justify-center text-gray-400" id="empty-state"> |
| No shape generated yet |
| </div> |
| </div> |
| |
| <div class="w-full bg-gray-100 rounded-full h-2.5"> |
| <div id="progress-bar" class="bg-blue-600 h-2.5 rounded-full" style="width: 0%"></div> |
| </div> |
| <p class="text-sm text-gray-500 mt-2" id="progress-text">Ready to generate</p> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="mt-12 bg-white rounded-xl shadow-md p-6"> |
| <h2 class="text-2xl font-semibold text-gray-800 mb-6">Generated Shapes Gallery</h2> |
| |
| <div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4" id="gallery"> |
| <div class="text-center py-8 text-gray-400" id="gallery-empty"> |
| Your generated shapes will appear here |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', function() { |
| |
| const canvas = document.getElementById('canvas'); |
| const emptyState = document.getElementById('empty-state'); |
| const generateBtn = document.getElementById('generate-btn'); |
| const generateSpinner = document.getElementById('generate-spinner'); |
| const downloadBtn = document.getElementById('download-btn'); |
| const clearBtn = document.getElementById('clear-btn'); |
| const progressBar = document.getElementById('progress-bar'); |
| const progressText = document.getElementById('progress-text'); |
| const randomnessSlider = document.getElementById('randomness'); |
| const randomnessValue = document.getElementById('randomness-value'); |
| const batchSizeSelect = document.getElementById('batch-size'); |
| const gallery = document.getElementById('gallery'); |
| const galleryEmpty = document.getElementById('gallery-empty'); |
| const shapeBtns = document.querySelectorAll('.shape-btn'); |
| |
| |
| let currentShapeType = 'circle'; |
| let generatedShapes = []; |
| |
| |
| shapeBtns.forEach(btn => { |
| btn.addEventListener('click', function() { |
| |
| shapeBtns.forEach(b => { |
| b.classList.remove('bg-blue-600', 'text-white'); |
| b.classList.add('bg-blue-100', 'text-blue-700', 'border-blue-200'); |
| }); |
| |
| |
| this.classList.remove('bg-blue-100', 'text-blue-700', 'border-blue-200'); |
| this.classList.add('bg-blue-600', 'text-white'); |
| |
| |
| currentShapeType = this.id.replace('btn-', ''); |
| }); |
| }); |
| |
| randomnessSlider.addEventListener('input', function() { |
| randomnessValue.textContent = `${this.value}%`; |
| }); |
| |
| generateBtn.addEventListener('click', generateShapes); |
| downloadBtn.addEventListener('click', downloadShapes); |
| clearBtn.addEventListener('click', clearCanvas); |
| |
| |
| document.getElementById('btn-circle').click(); |
| |
| |
| function generateShapes() { |
| const batchSize = parseInt(batchSizeSelect.value); |
| const randomness = parseInt(randomnessSlider.value) / 100; |
| |
| |
| generateBtn.disabled = true; |
| generateSpinner.classList.remove('hidden'); |
| generateBtn.classList.add('generating'); |
| progressText.textContent = 'Generating shapes...'; |
| |
| |
| clearCanvas(false); |
| |
| |
| let generatedCount = 0; |
| generatedShapes = []; |
| |
| const generateInterval = setInterval(() => { |
| if (generatedCount >= batchSize) { |
| clearInterval(generateInterval); |
| generateBtn.disabled = false; |
| generateSpinner.classList.add('hidden'); |
| generateBtn.classList.remove('generating'); |
| progressBar.style.width = '100%'; |
| progressText.textContent = `Generated ${batchSize} shapes`; |
| return; |
| } |
| |
| generatedCount++; |
| const progress = (generatedCount / batchSize) * 100; |
| progressBar.style.width = `${progress}%`; |
| progressText.textContent = `Generating ${generatedCount} of ${batchSize} shapes`; |
| |
| |
| const shape = createShape(currentShapeType, randomness); |
| generatedShapes.push(shape); |
| |
| |
| if (generatedCount === 1 && batchSize === 1) { |
| displayShape(shape); |
| } |
| |
| |
| if (batchSize > 1) { |
| addToGallery(shape, generatedCount); |
| } |
| |
| }, 100); |
| } |
| |
| function createShape(type, randomness) { |
| const canvasSize = 256; |
| const minSize = 40; |
| const maxSize = 120; |
| |
| |
| const baseSize = minSize + (maxSize - minSize) * (1 - randomness); |
| const randomFactor = (maxSize - minSize) * randomness; |
| const size = Math.floor(baseSize + Math.random() * randomFactor); |
| |
| |
| const padding = 20; |
| const maxPos = canvasSize - size - padding; |
| const x = padding + Math.floor(Math.random() * Math.max(1, maxPos - padding)); |
| const y = padding + Math.floor(Math.random() * Math.max(1, maxPos - padding)); |
| |
| |
| const shape = document.createElement('div'); |
| shape.className = 'shape'; |
| |
| |
| switch(type) { |
| case 'circle': |
| shape.classList.add('circle'); |
| shape.style.width = `${size}px`; |
| shape.style.height = `${size}px`; |
| shape.style.left = `${x}px`; |
| shape.style.top = `${y}px`; |
| break; |
| |
| case 'square': |
| shape.style.width = `${size}px`; |
| shape.style.height = `${size}px`; |
| shape.style.left = `${x}px`; |
| shape.style.top = `${y}px`; |
| break; |
| |
| case 'triangle': |
| shape.classList.add('triangle'); |
| shape.style.borderLeftWidth = `${size/2}px`; |
| shape.style.borderRightWidth = `${size/2}px`; |
| shape.style.borderBottomWidth = `${size}px`; |
| shape.style.left = `${x}px`; |
| shape.style.top = `${y}px`; |
| break; |
| } |
| |
| |
| const hue = Math.floor(Math.random() * 60) + 200; |
| const saturation = 80 + Math.floor(Math.random() * 20); |
| const lightness = 50 + Math.floor(Math.random() * 20); |
| shape.style.backgroundColor = `hsl(${hue}, ${saturation}%, ${lightness}%)`; |
| |
| return { |
| element: shape, |
| type: type, |
| x: x, |
| y: y, |
| size: size, |
| timestamp: new Date().toISOString() |
| }; |
| } |
| |
| function displayShape(shape) { |
| emptyState.classList.add('hidden'); |
| canvas.appendChild(shape.element); |
| } |
| |
| function addToGallery(shape, index) { |
| if (galleryEmpty) galleryEmpty.remove(); |
| |
| const galleryItem = document.createElement('div'); |
| galleryItem.className = 'bg-gray-50 rounded-lg p-2 border border-gray-200'; |
| |
| const preview = document.createElement('div'); |
| preview.className = 'w-full h-24 bg-gray-100 rounded relative overflow-hidden mb-2'; |
| |
| const shapeClone = shape.element.cloneNode(true); |
| |
| if (shape.type === 'circle' || shape.type === 'square') { |
| shapeClone.style.width = `${shape.size / 2}px`; |
| shapeClone.style.height = `${shape.size / 2}px`; |
| shapeClone.style.left = `${shape.x / 4}px`; |
| shapeClone.style.top = `${shape.y / 4}px`; |
| } else if (shape.type === 'triangle') { |
| shapeClone.style.borderLeftWidth = `${shape.size / 4}px`; |
| shapeClone.style.borderRightWidth = `${shape.size / 4}px`; |
| shapeClone.style.borderBottomWidth = `${shape.size / 2}px`; |
| shapeClone.style.left = `${shape.x / 4}px`; |
| shapeClone.style.top = `${shape.y / 4}px`; |
| } |
| |
| preview.appendChild(shapeClone); |
| |
| const info = document.createElement('div'); |
| info.className = 'text-xs text-gray-600'; |
| info.innerHTML = ` |
| <div class="font-medium">${shape.type.charAt(0).toUpperCase() + shape.type.slice(1)} #${index}</div> |
| <div>Size: ${shape.size}px</div> |
| `; |
| |
| galleryItem.appendChild(preview); |
| galleryItem.appendChild(info); |
| gallery.appendChild(galleryItem); |
| } |
| |
| function downloadShapes() { |
| if (generatedShapes.length === 0) { |
| alert('No shapes to download. Generate some shapes first.'); |
| return; |
| } |
| |
| |
| let csvContent = "data:text/csv;charset=utf-8,"; |
| csvContent += "id,type,x,y,size,timestamp\n"; |
| |
| generatedShapes.forEach((shape, index) => { |
| csvContent += `shape_${index},${shape.type},${shape.x},${shape.y},${shape.size},${shape.timestamp}\n`; |
| }); |
| |
| |
| const encodedUri = encodeURI(csvContent); |
| const link = document.createElement("a"); |
| link.setAttribute("href", encodedUri); |
| link.setAttribute("download", "generated_shapes.csv"); |
| document.body.appendChild(link); |
| |
| |
| link.click(); |
| document.body.removeChild(link); |
| } |
| |
| function clearCanvas(clearGallery = true) { |
| |
| while (canvas.firstChild && canvas.firstChild !== emptyState) { |
| canvas.removeChild(canvas.firstChild); |
| } |
| |
| emptyState.classList.remove('hidden'); |
| |
| |
| if (clearGallery) { |
| gallery.innerHTML = ''; |
| gallery.appendChild(galleryEmpty); |
| generatedShapes = []; |
| progressBar.style.width = '0%'; |
| progressText.textContent = 'Ready to generate'; |
| } |
| } |
| }); |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Gabz8646/ggt" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |