MarkTheArtist's picture
Add 2 files
a4b9c0e verified
<!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">
<!-- Color stops will be added here -->
</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() {
// Initial color stops
let colorStops = [
{ color: '#ff0000', position: 0 },
{ color: '#0000ff', position: 100 }
];
let currentDirection = 'to right';
let currentAngle = 90;
let isCustomAngle = false;
// DOM elements
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');
// Initialize color pickers
let colorPickers = [];
// Render color stops
function renderColorStops() {
colorStopTrack.innerHTML = '';
colorStops.forEach((stop, index) => {
// Create color stop handle
const handle = document.createElement('div');
handle.className = 'color-stop-handle';
handle.style.left = `${stop.position}%`;
handle.style.backgroundColor = stop.color;
handle.dataset.index = index;
// Create color picker container
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%)';
// Initialize color picker
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;
// Make handle draggable
makeDraggable(handle, index);
// Add delete button
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();
}
// Make color stop handles draggable
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;
// Constrain between 0 and 100
position = Math.max(0, Math.min(100, position));
// Don't allow crossing other stops
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;
// Update handle 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);
}
}
// Add new color stop
addColorStopBtn.addEventListener('click', () => {
// Find where to insert the new stop
let insertIndex = 1;
let insertPosition = 50;
if (colorStops.length > 2) {
// Find the largest gap between stops
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;
}
}
}
// Generate a color between the two adjacent stops
const prevColor = colorStops[insertIndex - 1].color;
const nextColor = colorStops[insertIndex].color;
// Simple color mixing (this could be improved)
const mixedColor = mixColors(prevColor, nextColor, 0.5);
colorStops.splice(insertIndex, 0, {
color: mixedColor,
position: insertPosition
});
renderColorStops();
});
// Helper function to mix two colors
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;
}
// Remove color stop
function removeColorStop(index) {
if (colorStops.length <= 2) return;
colorStops.splice(index, 1);
renderColorStops();
}
// Update gradient preview and code
function updateGradient() {
// Sort color stops by position (just in case)
colorStops.sort((a, b) => a.position - b.position);
// Create gradient string
let gradientString;
if (isCustomAngle) {
gradientString = `linear-gradient(${currentAngle}deg, `;
} else {
gradientString = `linear-gradient(${currentDirection}, `;
}
gradientString += colorStops.map(stop => `${stop.color} ${stop.position}%`).join(', ');
gradientString += ')';
// Update preview
gradientPreview.style.background = gradientString;
// Update code
cssCode.textContent = `background: ${gradientString};`;
}
// Direction controls
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();
});
});
// Helper to get default angle for direction
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;
}
}
// Angle slider
angleSlider.addEventListener('input', () => {
currentAngle = parseInt(angleSlider.value);
angleValue.textContent = `${currentAngle}deg`;
isCustomAngle = true;
// Deactivate direction buttons
directionControls.forEach(c => c.classList.remove('active'));
updateGradient();
});
// Copy code button
copyBtn.addEventListener('click', () => {
const code = cssCode.textContent;
navigator.clipboard.writeText(code).then(() => {
copyBtn.textContent = 'Copied!';
setTimeout(() => {
copyBtn.textContent = 'Copy';
}, 2000);
});
});
// Initialize
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>