| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>CSS Box Shadow Generator</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| .color-picker { |
| -webkit-appearance: none; |
| -moz-appearance: none; |
| appearance: none; |
| width: 40px; |
| height: 40px; |
| background-color: transparent; |
| border: none; |
| cursor: pointer; |
| } |
| .color-picker::-webkit-color-swatch { |
| border-radius: 8px; |
| border: 2px solid #e5e7eb; |
| } |
| .color-picker::-moz-color-swatch { |
| border-radius: 8px; |
| border: 2px solid #e5e7eb; |
| } |
| .range-slider::-webkit-slider-thumb { |
| -webkit-appearance: none; |
| width: 18px; |
| height: 18px; |
| border-radius: 50%; |
| background: #3b82f6; |
| cursor: pointer; |
| } |
| .range-slider::-moz-range-thumb { |
| width: 18px; |
| height: 18px; |
| border-radius: 50%; |
| background: #3b82f6; |
| cursor: pointer; |
| } |
| #preview-box { |
| transition: box-shadow 0.2s ease; |
| } |
| #cssOutput { |
| font-family: 'Courier New', monospace; |
| } |
| .shadow-layer { |
| position: relative; |
| margin-bottom: 1rem; |
| } |
| .delete-layer { |
| position: absolute; |
| top: -8px; |
| right: -8px; |
| background: #ef4444; |
| color: white; |
| border-radius: 50%; |
| width: 24px; |
| height: 24px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| cursor: pointer; |
| z-index: 10; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-100 min-h-screen"> |
| <div class="container mx-auto px-4 py-8"> |
| <div class="text-center mb-8"> |
| <h1 class="text-4xl font-bold text-gray-800 mb-2">CSS Box Shadow Generator</h1> |
| <p class="text-gray-600">Create beautiful shadows with this visual tool</p> |
| </div> |
|
|
| <div class="grid grid-cols-1 lg:grid-cols-2 gap-8"> |
| |
| <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">Shadow Controls</h2> |
| <button id="addLayerBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg flex items-center"> |
| <i class="fas fa-plus mr-2"></i> Add Layer |
| </button> |
| </div> |
|
|
| <div id="shadowLayers" class="mb-6"> |
| |
| </div> |
|
|
| <div class="bg-gray-50 p-4 rounded-lg mb-6"> |
| <div class="grid grid-cols-2 gap-4 mb-4"> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Horizontal Offset</label> |
| <input type="range" id="hOffset" min="-50" max="50" value="10" class="w-full range-slider"> |
| <div class="flex justify-between text-xs text-gray-500"> |
| <span>-50px</span> |
| <span id="hOffsetValue">10px</span> |
| <span>50px</span> |
| </div> |
| </div> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Vertical Offset</label> |
| <input type="range" id="vOffset" min="-50" max="50" value="10" class="w-full range-slider"> |
| <div class="flex justify-between text-xs text-gray-500"> |
| <span>-50px</span> |
| <span id="vOffsetValue">10px</span> |
| <span>50px</span> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="grid grid-cols-2 gap-4 mb-4"> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Blur Radius</label> |
| <input type="range" id="blur" min="0" max="100" value="15" class="w-full range-slider"> |
| <div class="flex justify-between text-xs text-gray-500"> |
| <span>0px</span> |
| <span id="blurValue">15px</span> |
| <span>100px</span> |
| </div> |
| </div> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Spread</label> |
| <input type="range" id="spread" min="-50" max="50" value="0" class="w-full range-slider"> |
| <div class="flex justify-between text-xs text-gray-500"> |
| <span>-50px</span> |
| <span id="spreadValue">0px</span> |
| <span>50px</span> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="grid grid-cols-2 gap-4"> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Color</label> |
| <div class="flex items-center"> |
| <input type="color" id="shadowColor" value="#000000" class="color-picker mr-2"> |
| <input type="text" id="shadowColorHex" value="#000000" class="border rounded px-2 py-1 w-24"> |
| </div> |
| </div> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Opacity</label> |
| <input type="range" id="opacity" min="0" max="100" value="75" class="w-full range-slider"> |
| <div class="flex justify-between text-xs text-gray-500"> |
| <span>0%</span> |
| <span id="opacityValue">75%</span> |
| <span>100%</span> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="mt-4"> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Shadow Position</label> |
| <div class="flex space-x-2"> |
| <button id="insetBtn" class="px-3 py-1 border rounded hover:bg-gray-100">Outset</button> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="flex space-x-2"> |
| <button id="copyBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg flex-1 flex items-center justify-center"> |
| <i class="fas fa-copy mr-2"></i> Copy CSS |
| </button> |
| <button id="resetBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded-lg flex-1 flex items-center justify-center"> |
| <i class="fas fa-redo mr-2"></i> Reset |
| </button> |
| </div> |
| </div> |
|
|
| |
| <div class="space-y-6"> |
| <div class="bg-white rounded-xl shadow-md p-6"> |
| <h2 class="text-2xl font-semibold text-gray-800 mb-4">Preview</h2> |
| <div class="flex justify-center"> |
| <div id="previewBox" class="w-48 h-48 bg-white rounded-lg border border-gray-200 flex items-center justify-center"> |
| <p class="text-gray-500">Your shadow appears here</p> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="bg-white rounded-xl shadow-md p-6"> |
| <h2 class="text-2xl font-semibold text-gray-800 mb-4">CSS Output</h2> |
| <div class="bg-gray-800 text-gray-100 p-4 rounded-lg"> |
| <code id="cssOutput" class="block whitespace-pre-wrap">box-shadow: 10px 10px 15px 0px rgba(0, 0, 0, 0.75);</code> |
| </div> |
| </div> |
|
|
| <div class="bg-white rounded-xl shadow-md p-6"> |
| <h2 class="text-2xl font-semibold text-gray-800 mb-4">Presets</h2> |
| <div class="grid grid-cols-2 md:grid-cols-3 gap-3"> |
| <button class="preset-btn p-3 rounded border hover:bg-gray-50" data-preset="0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)"> |
| <div class="w-full h-16 bg-white rounded shadow-sm"></div> |
| <p class="text-xs mt-1">Small</p> |
| </button> |
| <button class="preset-btn p-3 rounded border hover:bg-gray-50" data-preset="0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)"> |
| <div class="w-full h-16 bg-white rounded shadow-md"></div> |
| <p class="text-xs mt-1">Medium</p> |
| </button> |
| <button class="preset-btn p-3 rounded border hover:bg-gray-50" data-preset="0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)"> |
| <div class="w-full h-16 bg-white rounded shadow-lg"></div> |
| <p class="text-xs mt-1">Large</p> |
| </button> |
| <button class="preset-btn p-3 rounded border hover:bg-gray-50" data-preset="0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)"> |
| <div class="w-full h-16 bg-white rounded shadow-xl"></div> |
| <p class="text-xs mt-1">XL</p> |
| </button> |
| <button class="preset-btn p-3 rounded border hover:bg-gray-50" data-preset="0 25px 50px -12px rgba(0, 0, 0, 0.25)"> |
| <div class="w-full h-16 bg-white rounded shadow-2xl"></div> |
| <p class="text-xs mt-1">2XL</p> |
| </button> |
| <button class="preset-btn p-3 rounded border hover:bg-gray-50" data-preset="inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)"> |
| <div class="w-full h-16 bg-white rounded shadow-inner"></div> |
| <p class="text-xs mt-1">Inset</p> |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', function() { |
| |
| const hOffsetInput = document.getElementById('hOffset'); |
| const vOffsetInput = document.getElementById('vOffset'); |
| const blurInput = document.getElementById('blur'); |
| const spreadInput = document.getElementById('spread'); |
| const shadowColorInput = document.getElementById('shadowColor'); |
| const shadowColorHex = document.getElementById('shadowColorHex'); |
| const opacityInput = document.getElementById('opacity'); |
| const insetBtn = document.getElementById('insetBtn'); |
| const previewBox = document.getElementById('previewBox'); |
| const cssOutput = document.getElementById('cssOutput'); |
| const copyBtn = document.getElementById('copyBtn'); |
| const resetBtn = document.getElementById('resetBtn'); |
| const addLayerBtn = document.getElementById('addLayerBtn'); |
| const shadowLayers = document.getElementById('shadowLayers'); |
| const presetBtns = document.querySelectorAll('.preset-btn'); |
| |
| |
| let currentLayerIndex = 0; |
| let shadowLayersData = [{ |
| hOffset: 10, |
| vOffset: 10, |
| blur: 15, |
| spread: 0, |
| color: '#000000', |
| opacity: 75, |
| inset: false |
| }]; |
| |
| |
| updatePreview(); |
| createLayerElements(); |
| |
| |
| hOffsetInput.addEventListener('input', updateShadow); |
| vOffsetInput.addEventListener('input', updateShadow); |
| blurInput.addEventListener('input', updateShadow); |
| spreadInput.addEventListener('input', updateShadow); |
| shadowColorInput.addEventListener('input', updateColor); |
| shadowColorHex.addEventListener('input', updateColor); |
| opacityInput.addEventListener('input', updateShadow); |
| insetBtn.addEventListener('click', toggleInset); |
| copyBtn.addEventListener('click', copyToClipboard); |
| resetBtn.addEventListener('click', resetShadow); |
| addLayerBtn.addEventListener('click', addLayer); |
| |
| presetBtns.forEach(btn => { |
| btn.addEventListener('click', function() { |
| const preset = this.getAttribute('data-preset'); |
| cssOutput.textContent = `box-shadow: ${preset};`; |
| previewBox.style.boxShadow = preset; |
| |
| |
| const layers = preset.split(/(?<=\)),/).map(layer => { |
| layer = layer.trim(); |
| const parts = layer.match(/(inset)?\s*(-?\d+)px\s*(-?\d+)px\s*(\d+)px\s*(-?\d+)px\s*(rgba?\([^)]+\))/); |
| |
| if (!parts) return null; |
| |
| const colorParts = parts[6].match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/); |
| const opacity = colorParts[4] ? Math.round(parseFloat(colorParts[4]) * 100) : 100; |
| |
| return { |
| hOffset: parseInt(parts[2]), |
| vOffset: parseInt(parts[3]), |
| blur: parseInt(parts[4]), |
| spread: parseInt(parts[5]), |
| color: parts[6], |
| opacity: opacity, |
| inset: !!parts[1] |
| }; |
| }).filter(layer => layer !== null); |
| |
| shadowLayersData = layers.length ? layers : [{ |
| hOffset: 10, |
| vOffset: 10, |
| blur: 15, |
| spread: 0, |
| color: '#000000', |
| opacity: 75, |
| inset: false |
| }]; |
| |
| createLayerElements(); |
| updateControlsForLayer(0); |
| }); |
| }); |
| |
| |
| function updateShadow() { |
| const currentLayer = shadowLayersData[currentLayerIndex]; |
| |
| currentLayer.hOffset = parseInt(hOffsetInput.value); |
| currentLayer.vOffset = parseInt(vOffsetInput.value); |
| currentLayer.blur = parseInt(blurInput.value); |
| currentLayer.spread = parseInt(spreadInput.value); |
| currentLayer.opacity = parseInt(opacityInput.value); |
| |
| document.getElementById('hOffsetValue').textContent = `${currentLayer.hOffset}px`; |
| document.getElementById('vOffsetValue').textContent = `${currentLayer.vOffset}px`; |
| document.getElementById('blurValue').textContent = `${currentLayer.blur}px`; |
| document.getElementById('spreadValue').textContent = `${currentLayer.spread}px`; |
| document.getElementById('opacityValue').textContent = `${currentLayer.opacity}%`; |
| |
| updatePreview(); |
| } |
| |
| function updateColor() { |
| const currentLayer = shadowLayersData[currentLayerIndex]; |
| |
| if (this === shadowColorInput) { |
| currentLayer.color = shadowColorInput.value; |
| shadowColorHex.value = shadowColorInput.value; |
| } else { |
| currentLayer.color = shadowColorHex.value; |
| shadowColorInput.value = shadowColorHex.value; |
| } |
| |
| updatePreview(); |
| } |
| |
| function toggleInset() { |
| const currentLayer = shadowLayersData[currentLayerIndex]; |
| currentLayer.inset = !currentLayer.inset; |
| |
| if (currentLayer.inset) { |
| insetBtn.textContent = 'Inset'; |
| insetBtn.classList.add('bg-blue-500', 'text-white'); |
| insetBtn.classList.remove('border', 'hover:bg-gray-100'); |
| } else { |
| insetBtn.textContent = 'Outset'; |
| insetBtn.classList.remove('bg-blue-500', 'text-white'); |
| insetBtn.classList.add('border', 'hover:bg-gray-100'); |
| } |
| |
| updatePreview(); |
| } |
| |
| function updatePreview() { |
| const shadowValue = shadowLayersData.map(layer => { |
| const rgbaColor = hexToRgba(layer.color, layer.opacity / 100); |
| const inset = layer.inset ? 'inset ' : ''; |
| return `${inset}${layer.hOffset}px ${layer.vOffset}px ${layer.blur}px ${layer.spread}px ${rgbaColor}`; |
| }).join(', '); |
| |
| previewBox.style.boxShadow = shadowValue; |
| cssOutput.textContent = `box-shadow: ${shadowValue};`; |
| } |
| |
| function hexToRgba(hex, opacity) { |
| |
| hex = hex.replace('#', ''); |
| |
| |
| let r, g, b; |
| |
| if (hex.length === 3) { |
| r = parseInt(hex.substring(0, 1).repeat(2), 16); |
| g = parseInt(hex.substring(1, 2).repeat(2), 16); |
| b = parseInt(hex.substring(2, 3).repeat(2), 16); |
| } else if (hex.length === 6) { |
| r = parseInt(hex.substring(0, 2), 16); |
| g = parseInt(hex.substring(2, 4), 16); |
| b = parseInt(hex.substring(4, 6), 16); |
| } else { |
| |
| return `rgba(0, 0, 0, ${opacity})`; |
| } |
| |
| return `rgba(${r}, ${g}, ${b}, ${opacity})`; |
| } |
| |
| function copyToClipboard() { |
| const text = cssOutput.textContent; |
| navigator.clipboard.writeText(text).then(() => { |
| const originalText = copyBtn.innerHTML; |
| copyBtn.innerHTML = '<i class="fas fa-check mr-2"></i> Copied!'; |
| setTimeout(() => { |
| copyBtn.innerHTML = originalText; |
| }, 2000); |
| }); |
| } |
| |
| function resetShadow() { |
| shadowLayersData = [{ |
| hOffset: 10, |
| vOffset: 10, |
| blur: 15, |
| spread: 0, |
| color: '#000000', |
| opacity: 75, |
| inset: false |
| }]; |
| |
| currentLayerIndex = 0; |
| createLayerElements(); |
| updateControlsForLayer(0); |
| updatePreview(); |
| } |
| |
| function addLayer() { |
| const newLayer = { |
| hOffset: 5, |
| vOffset: 5, |
| blur: 10, |
| spread: 0, |
| color: '#000000', |
| opacity: 50, |
| inset: false |
| }; |
| |
| shadowLayersData.push(newLayer); |
| currentLayerIndex = shadowLayersData.length - 1; |
| createLayerElements(); |
| updateControlsForLayer(currentLayerIndex); |
| updatePreview(); |
| } |
| |
| function createLayerElements() { |
| shadowLayers.innerHTML = ''; |
| |
| shadowLayersData.forEach((layer, index) => { |
| const layerElement = document.createElement('div'); |
| layerElement.className = 'shadow-layer bg-gray-100 p-4 rounded-lg'; |
| |
| const rgbaColor = hexToRgba(layer.color, layer.opacity / 100); |
| const shadowValue = `${layer.inset ? 'inset ' : ''}${layer.hOffset}px ${layer.vOffset}px ${layer.blur}px ${layer.spread}px ${rgbaColor}`; |
| |
| layerElement.innerHTML = ` |
| <div class="flex items-center justify-between mb-2"> |
| <h3 class="font-medium">Layer ${index + 1}</h3> |
| <div class="flex items-center"> |
| <div class="w-4 h-4 rounded-full mr-2" style="background-color: ${layer.color}; opacity: ${layer.opacity/100}"></div> |
| <span class="text-xs">${shadowValue}</span> |
| </div> |
| </div> |
| <div class="h-4 rounded-full" style="box-shadow: ${shadowValue}; background-color: white;"></div> |
| ${index > 0 ? '<div class="delete-layer"><i class="fas fa-times text-xs"></i></div>' : ''} |
| `; |
| |
| if (index > 0) { |
| layerElement.querySelector('.delete-layer').addEventListener('click', () => { |
| shadowLayersData.splice(index, 1); |
| currentLayerIndex = Math.min(currentLayerIndex, shadowLayersData.length - 1); |
| createLayerElements(); |
| updateControlsForLayer(currentLayerIndex); |
| updatePreview(); |
| }); |
| } |
| |
| layerElement.addEventListener('click', () => { |
| currentLayerIndex = index; |
| updateControlsForLayer(index); |
| }); |
| |
| if (index === currentLayerIndex) { |
| layerElement.classList.add('ring-2', 'ring-blue-500'); |
| } |
| |
| shadowLayers.appendChild(layerElement); |
| }); |
| } |
| |
| function updateControlsForLayer(index) { |
| const layer = shadowLayersData[index]; |
| |
| hOffsetInput.value = layer.hOffset; |
| vOffsetInput.value = layer.vOffset; |
| blurInput.value = layer.blur; |
| spreadInput.value = layer.spread; |
| shadowColorInput.value = layer.color; |
| shadowColorHex.value = layer.color; |
| opacityInput.value = layer.opacity; |
| |
| document.getElementById('hOffsetValue').textContent = `${layer.hOffset}px`; |
| document.getElementById('vOffsetValue').textContent = `${layer.vOffset}px`; |
| document.getElementById('blurValue').textContent = `${layer.blur}px`; |
| document.getElementById('spreadValue').textContent = `${layer.spread}px`; |
| document.getElementById('opacityValue').textContent = `${layer.opacity}%`; |
| |
| if (layer.inset) { |
| insetBtn.textContent = 'Inset'; |
| insetBtn.classList.add('bg-blue-500', 'text-white'); |
| insetBtn.classList.remove('border', 'hover:bg-gray-100'); |
| } else { |
| insetBtn.textContent = 'Outset'; |
| insetBtn.classList.remove('bg-blue-500', 'text-white'); |
| insetBtn.classList.add('border', 'hover:bg-gray-100'); |
| } |
| |
| |
| document.querySelectorAll('.shadow-layer').forEach((el, i) => { |
| if (i === index) { |
| el.classList.add('ring-2', 'ring-blue-500'); |
| } else { |
| el.classList.remove('ring-2', 'ring-blue-500'); |
| } |
| }); |
| } |
| }); |
| </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=MarkTheArtist/css-shadow-box-maker" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |