| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>CSS Gradient Generator</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://cdn.jsdelivr.net/npm/@jaames/iro@5"></script> |
| <style> |
| .gradient-preview { |
| background: linear-gradient(90deg, #ff0000, #0000ff); |
| height: 200px; |
| border-radius: 12px; |
| box-shadow: 0 4px 15px rgba(0,0,0,0.1); |
| } |
| .color-stop-handle { |
| width: 24px; |
| height: 24px; |
| border-radius: 50%; |
| border: 3px solid white; |
| box-shadow: 0 2px 5px rgba(0,0,0,0.2); |
| cursor: pointer; |
| position: absolute; |
| transform: translate(-50%, -50%); |
| top: 50%; |
| } |
| .color-stop-container { |
| position: relative; |
| height: 40px; |
| margin: 20px 0; |
| background: linear-gradient(to right, #fff, #000); |
| border-radius: 8px; |
| } |
| .direction-control { |
| width: 40px; |
| height: 40px; |
| border-radius: 50%; |
| background: white; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| box-shadow: 0 2px 5px rgba(0,0,0,0.1); |
| cursor: pointer; |
| } |
| .direction-control.active { |
| background: #6366f1; |
| color: white; |
| } |
| .code-container { |
| font-family: 'Courier New', monospace; |
| background: #1e293b; |
| color: #f8fafc; |
| padding: 16px; |
| border-radius: 8px; |
| position: relative; |
| } |
| .copy-btn { |
| position: absolute; |
| top: 8px; |
| right: 8px; |
| background: #334155; |
| color: white; |
| border: none; |
| padding: 4px 8px; |
| border-radius: 4px; |
| cursor: pointer; |
| font-size: 12px; |
| } |
| .copy-btn:hover { |
| background: #475569; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-50 min-h-screen"> |
| <div class="container mx-auto px-4 py-12 max-w-4xl"> |
| <div class="text-center mb-10"> |
| <h1 class="text-4xl font-bold text-gray-800 mb-2">CSS Gradient Generator</h1> |
| <p class="text-gray-600">Create beautiful gradients and get the CSS code instantly</p> |
| </div> |
|
|
| <div class="bg-white rounded-xl shadow-lg p-6 mb-8"> |
| <div class="gradient-preview mb-8" id="gradientPreview"></div> |
|
|
| <div class="grid grid-cols-1 md:grid-cols-2 gap-8"> |
| <div> |
| <h3 class="text-lg font-semibold text-gray-800 mb-4">Color Stops</h3> |
| <div class="color-stop-container mb-4" id="colorStopTrack"> |
| |
| </div> |
| <button id="addColorStop" class="bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700 transition"> |
| Add Color Stop |
| </button> |
| </div> |
|
|
| <div> |
| <h3 class="text-lg font-semibold text-gray-800 mb-4">Direction</h3> |
| <div class="grid grid-cols-4 gap-3 mb-6"> |
| <div class="direction-control active" data-direction="to right" id="right"> |
| → |
| </div> |
| <div class="direction-control" data-direction="to left" id="left"> |
| ← |
| </div> |
| <div class="direction-control" data-direction="to bottom" id="bottom"> |
| ↓ |
| </div> |
| <div class="direction-control" data-direction="to top" id="top"> |
| ↑ |
| </div> |
| <div class="direction-control" data-direction="to bottom right" id="bottomRight"> |
| ↘ |
| </div> |
| <div class="direction-control" data-direction="to bottom left" id="bottomLeft"> |
| ↙ |
| </div> |
| <div class="direction-control" data-direction="to top right" id="topRight"> |
| ↗ |
| </div> |
| <div class="direction-control" data-direction="to top left" id="topLeft"> |
| ↖ |
| </div> |
| </div> |
|
|
| <div class="mb-6"> |
| <label class="block text-sm font-medium text-gray-700 mb-2">Custom Angle</label> |
| <input type="range" min="0" max="360" value="90" class="w-full" id="angleSlider"> |
| <div class="text-center text-gray-600 mt-1"> |
| <span id="angleValue">90deg</span> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="bg-white rounded-xl shadow-lg p-6"> |
| <h3 class="text-lg font-semibold text-gray-800 mb-4">CSS Code</h3> |
| <div class="code-container"> |
| <button class="copy-btn" id="copyBtn">Copy</button> |
| <pre id="cssCode">background: linear-gradient(to right, #ff0000, #0000ff);</pre> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', function() { |
| |
| let colorStops = [ |
| { color: '#ff0000', position: 0 }, |
| { color: '#0000ff', position: 100 } |
| ]; |
| |
| let currentDirection = 'to right'; |
| let currentAngle = 90; |
| let isCustomAngle = false; |
| |
| |
| const gradientPreview = document.getElementById('gradientPreview'); |
| const colorStopTrack = document.getElementById('colorStopTrack'); |
| const addColorStopBtn = document.getElementById('addColorStop'); |
| const directionControls = document.querySelectorAll('.direction-control'); |
| const angleSlider = document.getElementById('angleSlider'); |
| const angleValue = document.getElementById('angleValue'); |
| const cssCode = document.getElementById('cssCode'); |
| const copyBtn = document.getElementById('copyBtn'); |
| |
| |
| let colorPickers = []; |
| |
| |
| function renderColorStops() { |
| colorStopTrack.innerHTML = ''; |
| |
| colorStops.forEach((stop, index) => { |
| |
| const handle = document.createElement('div'); |
| handle.className = 'color-stop-handle'; |
| handle.style.left = `${stop.position}%`; |
| handle.style.backgroundColor = stop.color; |
| handle.dataset.index = index; |
| |
| |
| const pickerContainer = document.createElement('div'); |
| pickerContainer.className = 'color-picker-container'; |
| pickerContainer.style.position = 'absolute'; |
| pickerContainer.style.left = `${stop.position}%`; |
| pickerContainer.style.bottom = 'calc(100% + 10px)'; |
| pickerContainer.style.transform = 'translateX(-50%)'; |
| |
| |
| const picker = new iro.ColorPicker(pickerContainer, { |
| width: 180, |
| color: stop.color, |
| layout: [ |
| { component: iro.ui.Wheel }, |
| { component: iro.ui.Slider, options: { sliderType: 'hue' } }, |
| { component: iro.ui.Slider, options: { sliderType: 'alpha' } } |
| ] |
| }); |
| |
| picker.on('color:change', function(color) { |
| colorStops[index].color = color.hexString; |
| updateGradient(); |
| }); |
| |
| colorPickers[index] = picker; |
| |
| |
| makeDraggable(handle, index); |
| |
| |
| if (index > 0 && index < colorStops.length - 1) { |
| const deleteBtn = document.createElement('button'); |
| deleteBtn.innerHTML = '×'; |
| deleteBtn.className = 'absolute -top-6 -right-6 bg-red-500 text-white rounded-full w-5 h-5 flex items-center justify-center text-xs'; |
| deleteBtn.addEventListener('click', (e) => { |
| e.stopPropagation(); |
| removeColorStop(index); |
| }); |
| handle.appendChild(deleteBtn); |
| } |
| |
| colorStopTrack.appendChild(handle); |
| colorStopTrack.appendChild(pickerContainer); |
| }); |
| |
| updateGradient(); |
| } |
| |
| |
| function makeDraggable(element, index) { |
| let isDragging = false; |
| |
| element.addEventListener('mousedown', (e) => { |
| isDragging = true; |
| document.addEventListener('mousemove', onMouseMove); |
| document.addEventListener('mouseup', onMouseUp); |
| }); |
| |
| function onMouseMove(e) { |
| if (!isDragging) return; |
| |
| const rect = colorStopTrack.getBoundingClientRect(); |
| let position = ((e.clientX - rect.left) / rect.width) * 100; |
| |
| |
| position = Math.max(0, Math.min(100, position)); |
| |
| |
| if (index > 0 && position <= colorStops[index - 1].position + 1) { |
| position = colorStops[index - 1].position + 1; |
| } |
| |
| if (index < colorStops.length - 1 && position >= colorStops[index + 1].position - 1) { |
| position = colorStops[index + 1].position - 1; |
| } |
| |
| colorStops[index].position = position; |
| |
| |
| element.style.left = `${position}%`; |
| const pickerContainer = element.nextElementSibling; |
| if (pickerContainer) { |
| pickerContainer.style.left = `${position}%`; |
| } |
| |
| updateGradient(); |
| } |
| |
| function onMouseUp() { |
| isDragging = false; |
| document.removeEventListener('mousemove', onMouseMove); |
| document.removeEventListener('mouseup', onMouseUp); |
| } |
| } |
| |
| |
| addColorStopBtn.addEventListener('click', () => { |
| |
| let insertIndex = 1; |
| let insertPosition = 50; |
| |
| if (colorStops.length > 2) { |
| |
| let maxGap = 0; |
| for (let i = 1; i < colorStops.length; i++) { |
| const gap = colorStops[i].position - colorStops[i - 1].position; |
| if (gap > maxGap) { |
| maxGap = gap; |
| insertIndex = i; |
| insertPosition = (colorStops[i - 1].position + colorStops[i].position) / 2; |
| } |
| } |
| } |
| |
| |
| const prevColor = colorStops[insertIndex - 1].color; |
| const nextColor = colorStops[insertIndex].color; |
| |
| |
| const mixedColor = mixColors(prevColor, nextColor, 0.5); |
| |
| colorStops.splice(insertIndex, 0, { |
| color: mixedColor, |
| position: insertPosition |
| }); |
| |
| renderColorStops(); |
| }); |
| |
| |
| function mixColors(color1, color2, weight) { |
| const d2h = (d) => d.toString(16); |
| const h2d = (h) => parseInt(h, 16); |
| |
| let result = "#"; |
| for(let i = 1; i < 7; i += 2) { |
| const v1 = h2d(color1.substr(i, 2)); |
| const v2 = h2d(color2.substr(i, 2)); |
| const val = d2h(Math.floor(v2 + (v1 - v2) * weight)); |
| |
| while(val.length < 2) { |
| val = '0' + val; |
| } |
| |
| result += val; |
| } |
| |
| return result; |
| } |
| |
| |
| function removeColorStop(index) { |
| if (colorStops.length <= 2) return; |
| colorStops.splice(index, 1); |
| renderColorStops(); |
| } |
| |
| |
| function updateGradient() { |
| |
| colorStops.sort((a, b) => a.position - b.position); |
| |
| |
| let gradientString; |
| if (isCustomAngle) { |
| gradientString = `linear-gradient(${currentAngle}deg, `; |
| } else { |
| gradientString = `linear-gradient(${currentDirection}, `; |
| } |
| |
| gradientString += colorStops.map(stop => `${stop.color} ${stop.position}%`).join(', '); |
| gradientString += ')'; |
| |
| |
| gradientPreview.style.background = gradientString; |
| |
| |
| cssCode.textContent = `background: ${gradientString};`; |
| } |
| |
| |
| directionControls.forEach(control => { |
| control.addEventListener('click', () => { |
| directionControls.forEach(c => c.classList.remove('active')); |
| control.classList.add('active'); |
| |
| currentDirection = control.dataset.direction; |
| isCustomAngle = false; |
| angleSlider.value = getDefaultAngle(currentDirection); |
| currentAngle = parseInt(angleSlider.value); |
| angleValue.textContent = `${currentAngle}deg`; |
| |
| updateGradient(); |
| }); |
| }); |
| |
| |
| function getDefaultAngle(direction) { |
| switch(direction) { |
| case 'to right': return 90; |
| case 'to left': return 270; |
| case 'to bottom': return 180; |
| case 'to top': return 0; |
| case 'to bottom right': return 135; |
| case 'to bottom left': return 225; |
| case 'to top right': return 45; |
| case 'to top left': return 315; |
| default: return 90; |
| } |
| } |
| |
| |
| angleSlider.addEventListener('input', () => { |
| currentAngle = parseInt(angleSlider.value); |
| angleValue.textContent = `${currentAngle}deg`; |
| isCustomAngle = true; |
| |
| |
| directionControls.forEach(c => c.classList.remove('active')); |
| |
| updateGradient(); |
| }); |
| |
| |
| copyBtn.addEventListener('click', () => { |
| const code = cssCode.textContent; |
| navigator.clipboard.writeText(code).then(() => { |
| copyBtn.textContent = 'Copied!'; |
| setTimeout(() => { |
| copyBtn.textContent = 'Copy'; |
| }, 2000); |
| }); |
| }); |
| |
| |
| renderColorStops(); |
| }); |
| </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-gradient-generator" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |