Please deduct the code again and there is some error I can see some more text it looks like a code off of the window of the preview - Initial Deployment
f49c1ee
verified
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>UI Customization Tool</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> | |
/* Custom CSS for elements that can't be done with Tailwind */ | |
.sidebar { | |
transition: all 0.3s ease; | |
} | |
.sidebar.collapsed { | |
transform: translateX(-90%); | |
} | |
.sidebar.collapsed:hover { | |
transform: translateX(0); | |
} | |
.color-picker { | |
width: 30px; | |
height: 30px; | |
border-radius: 50%; | |
cursor: pointer; | |
transition: transform 0.2s; | |
} | |
.color-picker:hover { | |
transform: scale(1.2); | |
} | |
.gradient-preview { | |
width: 100%; | |
height: 60px; | |
border-radius: 8px; | |
margin-top: 10px; | |
} | |
.modal { | |
display: none; | |
position: fixed; | |
z-index: 100; | |
left: 0; | |
top: 0; | |
width: 100%; | |
height: 100%; | |
background-color: rgba(0,0,0,0.5); | |
} | |
.modal-content { | |
background-color: #f8fafc; | |
margin: 5% auto; | |
padding: 20px; | |
border-radius: 10px; | |
width: 80%; | |
max-width: 700px; | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
} | |
.code-editor { | |
width: 100%; | |
height: 300px; | |
font-family: 'Courier New', monospace; | |
padding: 10px; | |
border-radius: 5px; | |
border: 1px solid #cbd5e1; | |
resize: none; | |
} | |
.canvas-container { | |
position: relative; | |
border: 2px dashed #94a3b8; | |
background-color: #f1f5f9; | |
overflow: auto; | |
} | |
.resize-handle { | |
position: absolute; | |
width: 10px; | |
height: 10px; | |
background-color: #3b82f6; | |
border-radius: 50%; | |
z-index: 10; | |
} | |
.resize-handle.nw { top: -5px; left: -5px; cursor: nw-resize; } | |
.resize-handle.ne { top: -5px; right: -5px; cursor: ne-resize; } | |
.resize-handle.sw { bottom: -5px; left: -5px; cursor: sw-resize; } | |
.resize-handle.se { bottom: -5px; right: -5px; cursor: se-resize; } | |
.toolbar { | |
transition: all 0.3s ease; | |
} | |
.element-controls { | |
position: absolute; | |
top: -40px; | |
left: 0; | |
background-color: white; | |
padding: 5px; | |
border-radius: 5px; | |
box-shadow: 0 2px 5px rgba(0,0,0,0.2); | |
display: none; | |
} | |
.canvas-element:hover .element-controls { | |
display: block; | |
} | |
.tab-button { | |
transition: all 0.2s ease; | |
} | |
.tab-button.active { | |
background-color: #3b82f6; | |
color: white; | |
} | |
.uiverse-modal { | |
max-height: 80vh; | |
overflow-y: auto; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-100 h-screen flex flex-col"> | |
<!-- Top Navigation --> | |
<header class="bg-white shadow-sm py-2 px-4 flex justify-between items-center"> | |
<h1 class="text-xl font-bold text-gray-800">UI Customization Tool</h1> | |
<div class="flex space-x-2"> | |
<button id="htmlBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg transition"> | |
<i class="fas fa-code mr-2"></i>HTML | |
</button> | |
<button id="cssBtn" class="bg-purple-500 hover:bg-purple-600 text-white px-4 py-2 rounded-lg transition"> | |
<i class="fab fa-css3-alt mr-2"></i>CSS | |
</button> | |
<button id="saveBtn" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg transition"> | |
<i class="fas fa-save mr-2"></i>Save | |
</button> | |
<button id="resetBtn" class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg transition"> | |
<i class="fas fa-trash-alt mr-2"></i>Reset | |
</button> | |
</div> | |
</header> | |
<div class="flex flex-1 overflow-hidden"> | |
<!-- Sidebar --> | |
<div id="sidebar" class="sidebar w-64 bg-gray-800 text-white h-full flex flex-col transition-all duration-300"> | |
<div class="p-4 flex justify-between items-center bg-gray-900"> | |
<h2 class="text-lg font-semibold">Elements</h2> | |
<button id="toggleSidebar" class="text-gray-400 hover:text-white"> | |
<i class="fas fa-chevron-left"></i> | |
</button> | |
</div> | |
<div class="flex-1 overflow-y-auto p-4"> | |
<div class="mb-6"> | |
<h3 class="text-sm uppercase font-medium text-gray-400 mb-2">Basic Elements</h3> | |
<div class="space-y-2"> | |
<button class="element-btn w-full text-left px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded flex items-center" data-type="button"> | |
<i class="fas fa-square mr-2"></i> Button | |
</button> | |
<button class="element-btn w-full text-left px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded flex items-center" data-type="input"> | |
<i class="fas fa-font mr-2"></i> Input | |
</button> | |
<button class="element-btn w-full text-left px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded flex items-center" data-type="card"> | |
<i class="fas fa-id-card mr-2"></i> Card | |
</button> | |
</div> | |
</div> | |
<div class="mb-6"> | |
<h3 class="text-sm uppercase font-medium text-gray-400 mb-2">Containers</h3> | |
<div class="space-y-2"> | |
<button class="element-btn w-full text-left px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded flex items-center" data-type="div"> | |
<i class="fas fa-square-full mr-2"></i> Div | |
</button> | |
<button class="element-btn w-full text-left px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded flex items-center" data-type="section"> | |
<i class="fas fa-border-all mr-2"></i> Section | |
</button> | |
</div> | |
</div> | |
<div class="mb-6"> | |
<h3 class="text-sm uppercase font-medium text-gray-400 mb-2">Advanced</h3> | |
<div class="space-y-2"> | |
<button id="uiverseBtn" class="w-full text-left px-3 py-2 bg-indigo-700 hover:bg-indigo-600 rounded flex items-center"> | |
<i class="fas fa-magic mr-2"></i> Uiverse.io | |
</button> | |
<button class="element-btn w-full text-left px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded flex items-center" data-type="custom"> | |
<i class="fas fa-code mr-2"></i> Custom HTML | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Main Content --> | |
<div class="flex-1 flex flex-col overflow-hidden"> | |
<!-- Toolbar --> | |
<div class="toolbar bg-white border-b p-2 flex items-center space-x-4 overflow-x-auto"> | |
<div class="flex items-center space-x-2"> | |
<span class="text-sm font-medium">Position:</span> | |
<input type="number" id="posX" placeholder="X" class="w-12 px-2 py-1 border rounded"> | |
<input type="number" id="posY" placeholder="Y" class="w-12 px-2 py-1 border rounded"> | |
</div> | |
<div class="flex items-center space-x-2"> | |
<span class="text-sm font-medium">Size:</span> | |
<input type="number" id="width" placeholder="W" class="w-12 px-2 py-1 border rounded"> | |
<input type="number" id="height" placeholder="H" class="w-12 px-2 py-1 border rounded"> | |
</div> | |
<div class="flex items-center space-x-2"> | |
<span class="text-sm font-medium">Rotation:</span> | |
<input type="number" id="rotation" placeholder="deg" class="w-16 px-2 py-1 border rounded"> | |
</div> | |
<div class="flex items-center space-x-2"> | |
<span class="text-sm font-medium">Color:</span> | |
<input type="color" id="colorPicker" class="w-8 h-8 cursor-pointer"> | |
</div> | |
<div class="flex items-center space-x-2"> | |
<span class="text-sm font-medium">BG:</span> | |
<input type="color" id="bgColorPicker" class="w-8 h-8 cursor-pointer"> | |
</div> | |
<div class="flex items-center space-x-2"> | |
<button id="gradientBtn" class="px-2 py-1 bg-gray-200 rounded text-sm">Gradient</button> | |
</div> | |
<div class="flex items-center space-x-2"> | |
<span class="text-sm font-medium">Opacity:</span> | |
<input type="range" id="opacity" min="0" max="100" value="100" class="w-20"> | |
</div> | |
<div class="flex items-center space-x-2"> | |
<span class="text-sm font-medium">Z-index:</span> | |
<input type="number" id="zIndex" value="1" class="w-12 px-2 py-1 border rounded"> | |
</div> | |
<div class="flex items-center space-x-2"> | |
<button id="deleteBtn" class="px-2 py-1 bg-red-500 text-white rounded text-sm"> | |
<i class="fas fa-trash-alt"></i> | |
</button> | |
</div> | |
<div class="flex items-center space-x-2"> | |
<button id="duplicateBtn" class="px-2 py-1 bg-blue-500 text-white rounded text-sm"> | |
<i class="fas fa-copy"></i> | |
</button> | |
</div> | |
<div class="flex items-center space-x-2"> | |
<button id="newWindowBtn" class="px-2 py-1 bg-green-500 text-white rounded text-sm"> | |
<i class="fas fa-plus"></i> New Window | |
</button> | |
</div> | |
</div> | |
<!-- Canvas --> | |
<div id="canvasContainer" class="canvas-container flex-1 m-4 bg-white relative overflow-auto"> | |
<div id="canvas" class="relative w-full h-full min-h-[500px]"> | |
<!-- Elements will be added here --> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- HTML Modal --> | |
<div id="htmlModal" class="modal"> | |
<div class="modal-content"> | |
<div class="flex justify-between items-center mb-4"> | |
<h2 class="text-xl font-bold">HTML Editor</h2> | |
<button id="closeHtmlModal" class="text-gray-500 hover:text-gray-700"> | |
<i class="fas fa-times"></i> | |
</button> | |
</div> | |
<textarea id="htmlEditor" class="code-editor" placeholder="Paste your HTML code here..."></textarea> | |
<div class="flex justify-end mt-4 space-x-2"> | |
<button id="cancelHtmlBtn" class="px-4 py-2 bg-gray-300 hover:bg-gray-400 rounded">Cancel</button> | |
<button id="applyHtmlBtn" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded">Apply</button> | |
</div> | |
</div> | |
</div> | |
<!-- CSS Modal --> | |
<div id="cssModal" class="modal"> | |
<div class="modal-content"> | |
<div class="flex justify-between items-center mb-4"> | |
<h2 class="text-xl font-bold">CSS Editor</h2> | |
<button id="closeCssModal" class="text-gray-500 hover:text-gray-700"> | |
<i class="fas fa-times"></i> | |
</button> | |
</div> | |
<textarea id="cssEditor" class="code-editor" placeholder="Paste your CSS code here..."></textarea> | |
<div class="flex justify-end mt-4 space-x-2"> | |
<button id="cancelCssBtn" class="px-4 py-2 bg-gray-300 hover:bg-gray-400 rounded">Cancel</button> | |
<button id="applyCssBtn" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded">Apply</button> | |
</div> | |
</div> | |
</div> | |
<!-- Gradient Modal --> | |
<div id="gradientModal" class="modal"> | |
<div class="modal-content"> | |
<div class="flex justify-between items-center mb-4"> | |
<h2 class="text-xl font-bold">Gradient Editor</h2> | |
<button id="closeGradientModal" class="text-gray-500 hover:text-gray-700"> | |
<i class="fas fa-times"></i> | |
</button> | |
</div> | |
<div class="grid grid-cols-2 gap-4"> | |
<div> | |
<label class="block text-sm font-medium mb-1">Gradient Type</label> | |
<select id="gradientType" class="w-full p-2 border rounded"> | |
<option value="linear">Linear</option> | |
<option value="radial">Radial</option> | |
</select> | |
</div> | |
<div id="linearOptions"> | |
<label class="block text-sm font-medium mb-1">Direction</label> | |
<select id="gradientDirection" class="w-full p-2 border rounded"> | |
<option value="to right">Left to Right</option> | |
<option value="to bottom">Top to Bottom</option> | |
<option value="to bottom right">Diagonal</option> | |
<option value="135deg">135°</option> | |
</select> | |
</div> | |
</div> | |
<div class="mt-4"> | |
<label class="block text-sm font-medium mb-2">Color Stops</label> | |
<div id="colorStops" class="space-y-2"> | |
<div class="flex items-center space-x-2"> | |
<input type="color" value="#3b82f6" class="color-picker"> | |
<input type="range" min="0" max="100" value="0" class="flex-1"> | |
<span>0%</span> | |
<button class="remove-stop px-2 py-1 bg-red-500 text-white rounded text-sm"> | |
<i class="fas fa-times"></i> | |
</button> | |
</div> | |
<div class="flex items-center space-x-2"> | |
<input type="color" value="#8b5cf6" class="color-picker"> | |
<input type="range" min="0" max="100" value="100" class="flex-1"> | |
<span>100%</span> | |
<button class="remove-stop px-2 py-1 bg-red-500 text-white rounded text-sm"> | |
<i class="fas fa-times"></i> | |
</button> | |
</div> | |
</div> | |
<button id="addStopBtn" class="mt-2 px-3 py-1 bg-gray-200 hover:bg-gray-300 rounded text-sm"> | |
<i class="fas fa-plus mr-1"></i> Add Color Stop | |
</button> | |
</div> | |
<div class="mt-4"> | |
<label class="block text-sm font-medium mb-1">Preview</label> | |
<div id="gradientPreview" class="gradient-preview" style="background: linear-gradient(to right, #3b82f6, #8b5cf6);"></div> | |
</div> | |
<div class="flex justify-end mt-4 space-x-2"> | |
<button id="cancelGradientBtn" class="px-4 py-2 bg-gray-300 hover:bg-gray-400 rounded">Cancel</button> | |
<button id="applyGradientBtn" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded">Apply</button> | |
</div> | |
</div> | |
</div> | |
<!-- Uiverse Modal --> | |
<div id="uiverseModal" class="modal"> | |
<div class="modal-content uiverse-modal"> | |
<div class="flex justify-between items-center mb-4"> | |
<h2 class="text-xl font-bold">Uiverse Elements</h2> | |
<button id="closeUiverseModal" class="text-gray-500 hover:text-gray-700"> | |
<i class="fas fa-times"></i> | |
</button> | |
</div> | |
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> | |
<!-- Sample Uiverse elements - in a real app these would be fetched from the API --> | |
<div class="uiverse-element border rounded p-4"> | |
<h3 class="font-medium mb-2">Bitter Parrot Button</h3> | |
<div class="mb-3"> | |
<button class="px-4 py-2 bg-gradient-to-r from-purple-500 to-pink-500 text-white rounded-full shadow-lg hover:shadow-xl transition duration-300"> | |
Hover me | |
</button> | |
</div> | |
<div class="flex justify-between"> | |
<button class="copy-code px-2 py-1 bg-blue-500 text-white rounded text-sm"> | |
<i class="fas fa-copy mr-1"></i> Copy | |
</button> | |
<button class="add-to-canvas px-2 py-1 bg-green-500 text-white rounded text-sm"> | |
<i class="fas fa-plus mr-1"></i> Add | |
</button> | |
</div> | |
</div> | |
<div class="uiverse-element border rounded p-4"> | |
<h3 class="font-medium mb-2">Cool Input</h3> | |
<div class="mb-3"> | |
<div class="relative"> | |
<input type="text" class="w-full px-4 py-2 border-b-2 border-gray-300 focus:border-blue-500 outline-none transition" placeholder="Type something..."> | |
</div> | |
</div> | |
<div class="flex justify-between"> | |
<button class="copy-code px-2 py-1 bg-blue-500 text-white rounded text-sm"> | |
<i class="fas fa-copy mr-1"></i> Copy | |
</button> | |
<button class="add-to-canvas px-2 py-1 bg-green-500 text-white rounded text-sm"> | |
<i class="fas fa-plus mr-1"></i> Add | |
</button> | |
</div> | |
</div> | |
<div class="uiverse-element border rounded p-4"> | |
<h3 class="font-medium mb-2">Animated Card</h3> | |
<div class="mb-3"> | |
<div class="w-full max-w-sm bg-white border border-gray-200 rounded-lg shadow hover:scale-105 transition duration-300 p-4"> | |
<h4 class="text-lg font-semibold">Card Title</h4> | |
<p class="text-gray-600">Hover to see animation</p> | |
</div> | |
</div> | |
<div class="flex justify-between"> | |
<button class="copy-code px-2 py-1 bg-blue-500 text-white rounded text-sm"> | |
<i class="fas fa-copy mr-1"></i> Copy | |
</button> | |
<button class="add-to-canvas px-2 py-1 bg-green-500 text-white rounded text-sm"> | |
<i class="fas fa-plus mr-1"></i> Add | |
</button> | |
</div> | |
</div> | |
</div> | |
<div class="flex justify-center mt-6"> | |
<button class="px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded"> | |
Load More | |
</button> | |
</div> | |
</div> | |
</div> | |
<!-- Custom Element Modal --> | |
<div id="customElementModal" class="modal"> | |
<div class="modal-content"> | |
<div class="flex justify-between items-center mb-4"> | |
<h2 class="text-xl font-bold">Custom Element</h2> | |
<button id="closeCustomModal" class="text-gray-500 hover:text-gray-700"> | |
<i class="fas fa-times"></i> | |
</button> | |
</div> | |
<textarea id="customElementEditor" class="code-editor" placeholder="Enter your custom HTML here..."></textarea> | |
<div class="flex justify-end mt-4 space-x-2"> | |
<button id="cancelCustomBtn" class="px-4 py-2 bg-gray-300 hover:bg-gray-400 rounded">Cancel</button> | |
<button id="applyCustomBtn" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded">Add to Canvas</button> | |
</div> | |
</div> | |
</div> | |
<script> | |
// DOM Elements | |
const htmlBtn = document.getElementById('htmlBtn'); | |
const cssBtn = document.getElementById('cssBtn'); | |
const saveBtn = document.getElementById('saveBtn'); | |
const resetBtn = document.getElementById('resetBtn'); | |
const htmlModal = document.getElementById('htmlModal'); | |
const cssModal = document.getElementById('cssModal'); | |
const gradientModal = document.getElementById('gradientModal'); | |
const uiverseModal = document.getElementById('uiverseModal'); | |
const customElementModal = document.getElementById('customElementModal'); | |
const closeHtmlModal = document.getElementById('closeHtmlModal'); | |
const closeCssModal = document.getElementById('closeCssModal'); | |
const closeGradientModal = document.getElementById('closeGradientModal'); | |
const closeUiverseModal = document.getElementById('closeUiverseModal'); | |
const closeCustomModal = document.getElementById('closeCustomModal'); | |
const cancelHtmlBtn = document.getElementById('cancelHtmlBtn'); | |
const cancelCssBtn = document.getElementById('cancelCssBtn'); | |
const cancelGradientBtn = document.getElementById('cancelGradientBtn'); | |
const cancelCustomBtn = document.getElementById('cancelCustomBtn'); | |
const applyHtmlBtn = document.getElementById('applyHtmlBtn'); | |
const applyCssBtn = document.getElementById('applyCssBtn'); | |
const applyGradientBtn = document.getElementById('applyGradientBtn'); | |
const applyCustomBtn = document.getElementById('applyCustomBtn'); | |
const htmlEditor = document.getElementById('htmlEditor'); | |
const cssEditor = document.getElementById('cssEditor'); | |
const customElementEditor = document.getElementById('customElementEditor'); | |
const canvas = document.getElementById('canvas'); | |
const gradientBtn = document.getElementById('gradientBtn'); | |
const toggleSidebar = document.getElementById('toggleSidebar'); | |
const sidebar = document.getElementById('sidebar'); | |
const elementBtns = document.querySelectorAll('.element-btn'); | |
const uiverseBtn = document.getElementById('uiverseBtn'); | |
const deleteBtn = document.getElementById('deleteBtn'); | |
const duplicateBtn = document.getElementById('duplicateBtn'); | |
const newWindowBtn = document.getElementById('newWindowBtn'); | |
const posX = document.getElementById('posX'); | |
const posY = document.getElementById('posY'); | |
const width = document.getElementById('width'); | |
const height = document.getElementById('height'); | |
const rotation = document.getElementById('rotation'); | |
const colorPicker = document.getElementById('colorPicker'); | |
const bgColorPicker = document.getElementById('bgColorPicker'); | |
const opacity = document.getElementById('opacity'); | |
const zIndex = document.getElementById('zIndex'); | |
const gradientType = document.getElementById('gradientType'); | |
const gradientDirection = document.getElementById('gradientDirection'); | |
const colorStops = document.getElementById('colorStops'); | |
const addStopBtn = document.getElementById('addStopBtn'); | |
const gradientPreview = document.getElementById('gradientPreview'); | |
// State | |
let selectedElement = null; | |
let isDragging = false; | |
let isResizing = false; | |
let resizeHandle = null; | |
let startX, startY, startWidth, startHeight, startLeft, startTop; | |
// Initialize | |
function init() { | |
// Set default color picker values | |
colorPicker.value = '#000000'; | |
bgColorPicker.value = '#ffffff'; | |
// Add event listeners | |
setupEventListeners(); | |
// Load saved data if available | |
loadSavedData(); | |
} | |
// Set up all event listeners | |
function setupEventListeners() { | |
// Modal buttons | |
htmlBtn.addEventListener('click', () => htmlModal.style.display = 'block'); | |
cssBtn.addEventListener('click', () => cssModal.style.display = 'block'); | |
saveBtn.addEventListener('click', saveDesign); | |
resetBtn.addEventListener('click', resetCanvas); | |
// Close modals | |
closeHtmlModal.addEventListener('click', () => htmlModal.style.display = 'none'); | |
closeCssModal.addEventListener('click', () => cssModal.style.display = 'none'); | |
closeGradientModal.addEventListener('click', () => gradientModal.style.display = 'none'); | |
closeUiverseModal.addEventListener('click', () => uiverseModal.style.display = 'none'); | |
closeCustomModal.addEventListener('click', () => customElementModal.style.display = 'none'); | |
// Cancel buttons | |
cancelHtmlBtn.addEventListener('click', () => htmlModal.style.display = 'none'); | |
cancelCssBtn.addEventListener('click', () => cssModal.style.display = 'none'); | |
cancelGradientBtn.addEventListener('click', () => gradientModal.style.display = 'none'); | |
cancelCustomBtn.addEventListener('click', () => customElementModal.style.display = 'none'); | |
// Apply buttons | |
applyHtmlBtn.addEventListener('click', applyHtml); | |
applyCssBtn.addEventListener('click', applyCss); | |
applyGradientBtn.addEventListener('click', applyGradient); | |
applyCustomBtn.addEventListener('click', addCustomElement); | |
// Gradient editor | |
gradientBtn.addEventListener('click', () => gradientModal.style.display = 'block'); | |
gradientType.addEventListener('change', updateGradientUI); | |
gradientDirection.addEventListener('change', updateGradientPreview); | |
// Color stops | |
addStopBtn.addEventListener('click', addColorStop); | |
colorStops.addEventListener('click', (e) => { | |
if (e.target.classList.contains('remove-stop')) { | |
if (colorStops.children.length > 2) { | |
e.target.parentElement.remove(); | |
updateGradientPreview(); | |
} | |
} else if (e.target.classList.contains('color-picker')) { | |
e.target.addEventListener('change', updateGradientPreview); | |
} else if (e.target.type === 'range') { | |
e.target.addEventListener('input', () => { | |
e.target.nextElementSibling.textContent = e.target.value + '%'; | |
updateGradientPreview(); | |
}); | |
} | |
}); | |
// Sidebar | |
toggleSidebar.addEventListener('click', toggleSidebarCollapse); | |
// Element buttons | |
elementBtns.forEach(btn => { | |
btn.addEventListener('click', () => { | |
const type = btn.dataset.type; | |
if (type === 'custom') { | |
customElementModal.style.display = 'block'; | |
customElementEditor.value = ''; | |
} else { | |
addElementToCanvas(type); | |
} | |
}); | |
}); | |
// Uiverse button | |
uiverseBtn.addEventListener('click', () => uiverseModal.style.display = 'block'); | |
// Uiverse modal interactions | |
document.querySelectorAll('.add-to-canvas').forEach(btn => { | |
btn.addEventListener('click', () => { | |
const element = btn.closest('.uiverse-element'); | |
const preview = element.querySelector('button, input, div'); | |
if (preview) { | |
const clone = preview.cloneNode(true); | |
addElementToCanvas(clone); | |
uiverseModal.style.display = 'none'; | |
} | |
}); | |
}); | |
// Canvas interactions | |
canvas.addEventListener('mousedown', startDrag); | |
document.addEventListener('mousemove', handleDrag); | |
document.addEventListener('mouseup', stopDrag); | |
// Toolbar controls | |
posX.addEventListener('change', updateElementPosition); | |
posY.addEventListener('change', updateElementPosition); | |
width.addEventListener('change', updateElementSize); | |
height.addEventListener('change', updateElementSize); | |
rotation.addEventListener('change', updateElementRotation); | |
colorPicker.addEventListener('change', updateElementColor); | |
bgColorPicker.addEventListener('change', updateElementBackground); | |
opacity.addEventListener('input', updateElementOpacity); | |
zIndex.addEventListener('change', updateElementZIndex); | |
deleteBtn.addEventListener('click', deleteSelectedElement); | |
duplicateBtn.addEventListener('click', duplicateSelectedElement); | |
newWindowBtn.addEventListener('click', addNewWindow); | |
} | |
// Toggle sidebar collapse | |
function toggleSidebarCollapse() { | |
sidebar.classList.toggle('collapsed'); | |
const icon = toggleSidebar.querySelector('i'); | |
if (sidebar.classList.contains('collapsed')) { | |
icon.classList.remove('fa-chevron-left'); | |
icon.classList.add('fa-chevron-right'); | |
} else { | |
icon.classList.remove('fa-chevron-right'); | |
icon.classList.add('fa-chevron-left'); | |
} | |
} | |
// Add element to canvas | |
function addElementToCanvas(type, html = null) { | |
let element; | |
if (html) { | |
// Create a temporary div to parse the HTML | |
const tempDiv = document.createElement('div'); | |
tempDiv.innerHTML = html; | |
element = tempDiv.firstChild; | |
} else { | |
// Create standard elements based on type | |
switch (type) { | |
case 'button': | |
element = document.createElement('button'); | |
element.textContent = 'Button'; | |
element.className = 'px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition'; | |
break; | |
case 'input': | |
element = document.createElement('input'); | |
element.type = 'text'; | |
element.placeholder = 'Type something...'; | |
element.className = 'px-3 py-2 border rounded'; | |
break; | |
case 'card': | |
element = document.createElement('div'); | |
element.className = 'bg-white rounded-lg shadow-md p-4 w-64'; | |
element.innerHTML = ` | |
<h3 class="text-lg font-semibold mb-2">Card Title</h3> | |
<p class="text-gray-600">This is a sample card element.</p> | |
`; | |
break; | |
case 'div': | |
element = document.createElement('div'); | |
element.className = 'bg-gray-200 border border-gray-300 p-4'; | |
element.textContent = 'Div Container'; | |
break; | |
case 'section': | |
element = document.createElement('section'); | |
element.className = 'bg-white border border-gray-300 p-4'; | |
element.textContent = 'Section Container'; | |
break; | |
default: | |
element = document.createElement('div'); | |
element.textContent = 'New Element'; | |
} | |
} | |
// Set default styles for positioning | |
element.className += ' absolute cursor-move canvas-element'; | |
element.style.left = '50px'; | |
element.style.top = '50px'; | |
element.style.width = 'auto'; | |
element.style.minWidth = '100px'; | |
element.style.minHeight = '40px'; | |
// Add resize handles | |
addResizeHandles(element); | |
// Add element to canvas | |
canvas.appendChild(element); | |
// Select the new element | |
selectElement(element); | |
} | |
// Add custom element from modal | |
function addCustomElement() { | |
const html = customElementEditor.value.trim(); | |
if (html) { | |
addElementToCanvas('custom', html); | |
customElementModal.style.display = 'none'; | |
} | |
} | |
// Add resize handles to an element | |
function addResizeHandles(element) { | |
const positions = ['nw', 'ne', 'sw', 'se']; | |
positions.forEach(pos => { | |
const handle = document.createElement('div'); | |
handle.className = `resize-handle ${pos}`; | |
handle.dataset.position = pos; | |
element.appendChild(handle); | |
// Add event listeners for resizing | |
handle.addEventListener('mousedown', (e) => { | |
e.stopPropagation(); | |
isResizing = true; | |
resizeHandle = pos; | |
selectedElement = element; | |
// Store initial dimensions and position | |
startWidth = parseInt(getComputedStyle(element).width); | |
startHeight = parseInt(getComputedStyle(element).height); | |
startLeft = parseInt(getComputedStyle(element).left); | |
startTop = parseInt(getComputedStyle(element).top); | |
// Update toolbar inputs | |
updateToolbarWithElementData(element); | |
}); | |
}); | |
} | |
// Start dragging an element | |
function startDrag(e) { | |
if (e.target.classList.contains('resize-handle')) { | |
return; // Let the resize handle handle this | |
} | |
const element = e.target.closest('.canvas-element'); | |
if (element) { | |
e.preventDefault(); | |
isDragging = true; | |
selectedElement = element; | |
// Store initial position | |
startX = e.clientX; | |
startY = e.clientY; | |
startLeft = parseInt(getComputedStyle(element).left); | |
startTop = parseInt(getComputedStyle(element).top); | |
// Update toolbar inputs | |
updateToolbarWithElementData(element); | |
} | |
} | |
// Handle dragging | |
function handleDrag(e) { | |
if (isDragging && selectedElement) { | |
const dx = e.clientX - startX; | |
const dy = e.clientY - startY; | |
selectedElement.style.left = `${startLeft + dx}px`; | |
selectedElement.style.top = `${startTop + dy}px`; | |
// Update position inputs | |
posX.value = startLeft + dx; | |
posY.value = startTop + dy; | |
} else if (isResizing && selectedElement) { | |
const dx = e.clientX - startX; | |
const dy = e.clientY - startY; | |
let newWidth = startWidth; | |
let newHeight = startHeight; | |
let newLeft = startLeft; | |
let newTop = startTop; | |
switch (resizeHandle) { | |
case 'nw': | |
newWidth = startWidth - dx; | |
newHeight = startHeight - dy; | |
newLeft = startLeft + dx; | |
newTop = startTop + dy; | |
break; | |
case 'ne': | |
newWidth = startWidth + dx; | |
newHeight = startHeight - dy; | |
newTop = startTop + dy; | |
break; | |
case 'sw': | |
newWidth = startWidth - dx; | |
newHeight = startHeight + dy; | |
newLeft = startLeft + dx; | |
break; | |
case 'se': | |
newWidth = startWidth + dx; | |
newHeight = startHeight + dy; | |
break; | |
} | |
// Apply minimum dimensions | |
newWidth = Math.max(newWidth, 30); | |
newHeight = Math.max(newHeight, 30); | |
selectedElement.style.width = `${newWidth}px`; | |
selectedElement.style.height = `${newHeight}px`; | |
if (newLeft !== startLeft) selectedElement.style.left = `${newLeft}px`; | |
if (newTop !== startTop) selectedElement.style.top = `${newTop}px`; | |
// Update size inputs | |
width.value = newWidth; | |
height.value = newHeight; | |
if (newLeft !== startLeft) posX.value = newLeft; | |
if (newTop !== startTop) posY.value = newTop; | |
} | |
} | |
// Stop dragging | |
function stopDrag() { | |
isDragging = false; | |
isResizing = false; | |
resizeHandle = null; | |
} | |
// Select an element | |
function selectElement(element) { | |
// Remove selection from all elements | |
document.querySelectorAll('.canvas-element').forEach(el => { | |
el.classList.remove('ring-2', 'ring-blue-500'); | |
}); | |
// Select the new element | |
element.classList.add('ring-2', 'ring-blue-500'); | |
selectedElement = element; | |
// Update toolbar with element data | |
updateToolbarWithElementData(element); | |
} | |
// Update toolbar inputs with element data | |
function updateToolbarWithElementData(element) { | |
const style = getComputedStyle(element); | |
// Position | |
posX.value = parseInt(style.left) || 0; | |
posY.value = parseInt(style.top) || 0; | |
// Size | |
width.value = parseInt(style.width) || 0; | |
height.value = parseInt(style.height) || 0; | |
// Rotation | |
const transform = style.transform; | |
if (transform && transform !== 'none') { | |
const values = transform.match(/matrix\((.+)\)/)[1].split(', '); | |
const a = parseFloat(values[0]); | |
const b = parseFloat(values[1]); | |
const angle = Math.round(Math.atan2(b, a) * (180 / Math.PI)); | |
rotation.value = angle; | |
} else { | |
rotation.value = 0; | |
} | |
// Color | |
colorPicker.value = rgbToHex(style.color); | |
// Background color | |
if (style.background && style.background !== 'none' && !style.background.includes('gradient')) { | |
bgColorPicker.value = rgbToHex(style.backgroundColor); | |
} | |
// Opacity | |
opacity.value = Math.round(parseFloat(style.opacity) * 100); | |
// Z-index | |
zIndex.value = style.zIndex; | |
} | |
// Convert RGB to HEX | |
function rgbToHex(rgb) { | |
if (!rgb) return '#000000'; | |
// Check if it's already hex | |
if (rgb.startsWith('#')) return rgb; | |
// Extract RGB values | |
const match = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); | |
if (!match) return '#000000'; | |
const r = parseInt(match[1]); | |
const g = parseInt(match[2]); | |
const b = parseInt(match[3]); | |
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); | |
} | |
// Update element position from toolbar | |
function updateElementPosition() { | |
if (selectedElement) { | |
selectedElement.style.left = `${posX.value}px`; | |
selectedElement.style.top = `${posY.value}px`; | |
} | |
} | |
// Update element size from toolbar | |
function updateElementSize() { | |
if (selectedElement) { | |
selectedElement.style.width = `${width.value}px`; | |
selectedElement.style.height = `${height.value}px`; | |
} | |
} | |
// Update element rotation from toolbar | |
function updateElementRotation() { | |
if (selectedElement) { | |
selectedElement.style.transform = `rotate(${rotation.value}deg)`; | |
} | |
} | |
// Update element color from toolbar | |
function updateElementColor() { | |
if (selectedElement) { | |
selectedElement.style.color = colorPicker.value; | |
} | |
} | |
// Update element background from toolbar | |
function updateElementBackground() { | |
if (selectedElement) { | |
selectedElement.style.background = bgColorPicker.value; | |
} | |
} | |
// Update element opacity from toolbar | |
function updateElementOpacity() { | |
if (selectedElement) { | |
selectedElement.style.opacity = opacity.value / 100; | |
} | |
} | |
// Update element z-index from toolbar | |
function updateElementZIndex() { | |
if (selectedElement) { | |
selectedElement.style.zIndex = zIndex.value; | |
} | |
} | |
// Delete selected element | |
function deleteSelectedElement() { | |
if (selectedElement) { | |
selectedElement.remove(); | |
selectedElement = null; | |
} | |
} | |
// Duplicate selected element | |
function duplicateSelectedElement() { | |
if (selectedElement) { | |
const clone = selectedElement.cloneNode(true); | |
canvas.appendChild(clone); | |
// Offset the clone slightly | |
const left = parseInt(getComputedStyle(selectedElement).left) + 20; | |
const top = parseInt(getComputedStyle(selectedElement).top) + 20; | |
clone.style.left = `${left}px`; | |
clone.style.top = `${top}px`; | |
// Add resize handles to the clone | |
addResizeHandles(clone); | |
// Select the new clone | |
selectElement(clone); | |
} | |
} | |
// Add new window | |
function addNewWindow() { | |
const window = document.createElement('div'); | |
window.className = 'absolute bg-white border border-gray-300 shadow-lg rounded-lg overflow-hidden canvas-element'; | |
window.style.width = '400px'; | |
window.style.height = '300px'; | |
window.style.left = '100px'; | |
window.style.top = '100px'; | |
// Window header | |
const header = document.createElement('div'); | |
header.className = 'bg-gray-800 text-white p-2 flex justify-between items-center'; | |
header.innerHTML = ` | |
<span>New Window</span> | |
<button class="close-window text-white hover:text-gray-300"> | |
<i class="fas fa-times"></i> | |
</button> | |
`; | |
// Window content | |
const content = document.createElement('div'); | |
content.className = 'p-4 flex-1 overflow-auto'; | |
content.textContent = 'Window content goes here...'; | |
// Add elements to window | |
window.appendChild(header); | |
window.appendChild(content); | |
// Add to canvas | |
canvas.appendChild(window); | |
// Add resize handles | |
addResizeHandles(window); | |
// Select the new window | |
selectElement(window); | |
// Add close event | |
header.querySelector('.close-window').addEventListener('click', () => { | |
window.remove(); | |
}); | |
} | |
// Apply HTML from modal | |
function applyHtml() { | |
const html = htmlEditor.value.trim(); | |
if (html) { | |
// Clear canvas | |
canvas.innerHTML = ''; | |
// Add the HTML to canvas | |
canvas.innerHTML = html; | |
// Add canvas-element class and resize handles to all top-level children | |
Array.from(canvas.children).forEach(child => { | |
child.classList.add('canvas-element'); | |
addResizeHandles(child); | |
}); | |
// Close modal | |
htmlModal.style.display = 'none'; | |
} | |
} | |
// Apply CSS from modal | |
function applyCss() { | |
const css = cssEditor.value.trim(); | |
if (css) { | |
// Create a style element or use existing one | |
let styleElement = document.getElementById('custom-css'); | |
if (!styleElement) { | |
styleElement = document.createElement('style'); | |
styleElement.id = 'custom-css'; | |
document.head.appendChild(styleElement); | |
} | |
// Set CSS | |
styleElement.textContent = css; | |
// Close modal | |
cssModal.style.display = 'none'; | |
} | |
} | |
// Add color stop to gradient editor | |
function addColorStop() { | |
const stopDiv = document.createElement('div'); | |
stopDiv.className = 'flex items-center space-x-2'; | |
// Random color for new stop | |
const randomColor = '#' + Math.floor(Math.random()*16777215).toString(16); | |
const position = Math.floor(Math.random() * 50) + 25; // Between 25-75% | |
stopDiv.innerHTML = ` | |
<input type="color" value="${randomColor}" class="color-picker"> | |
<input type="range" min="0" max="100" value="${position}" class="flex-1"> | |
<span>${position}%</span> | |
<button class="remove-stop px-2 py-1 bg-red-500 text-white rounded text-sm"> | |
<i class="fas fa-times"></i> | |
</button> | |
`; | |
colorStops.appendChild(stopDiv); | |
// Add event listeners | |
stopDiv.querySelector('.color-picker').addEventListener('change', updateGradientPreview); | |
stopDiv.querySelector('input[type="range"]').addEventListener('input', function() { | |
this.nextElementSibling.textContent = this.value + '%'; | |
updateGradientPreview(); | |
}); | |
updateGradientPreview(); | |
} | |
// Update gradient UI based on type | |
function updateGradientUI() { | |
const type = gradientType.value; | |
if (type === 'linear') { | |
document.getElementById('linearOptions').style.display = 'block'; | |
} else { | |
document.getElementById('linearOptions').style.display = 'none'; | |
} | |
updateGradientPreview(); | |
} | |
// Update gradient preview | |
function updateGradientPreview() { | |
const type = gradientType.value; | |
const stops = Array.from(colorStops.querySelectorAll('.flex.items-center')).map(div => { | |
const color = div.querySelector('.color-picker').value; | |
const position = div.querySelector('input[type="range"]').value; | |
return `${color} ${position}%`; | |
}).join(', '); | |
if (type === 'linear') { | |
const direction = gradientDirection.value; | |
gradientPreview.style.background = `linear-gradient(${direction}, ${stops})`; | |
} else { | |
gradientPreview.style.background = `radial-gradient(circle, ${stops})`; | |
} | |
} | |
// Apply gradient to selected element | |
function applyGradient() { | |
if (selectedElement) { | |
const type = gradientType.value; | |
const stops = Array.from(colorStops.querySelectorAll('.flex.items-center')).map(div => { | |
const color = div.querySelector('.color-picker').value; | |
const position = div.querySelector('input[type="range"]').value; | |
return `${color} ${position}%`; | |
}).join(', '); | |
if (type === 'linear') { | |
const direction = gradientDirection.value; | |
selectedElement.style.background = `linear-gradient(${direction}, ${stops})`; | |
} else { | |
selectedElement.style.background = `radial-gradient(circle, ${stops})`; | |
} | |
// Close modal | |
gradientModal.style.display = 'none'; | |
} | |
} | |
// Save design to localStorage | |
function saveDesign() { | |
if (canvas) { | |
const html = canvas.innerHTML; | |
const css = document.getElementById('custom-css')?.textContent || ''; | |
localStorage.setItem('uiDesigner_html', html); | |
localStorage.setItem('uiDesigner_css', css); | |
alert('Design saved successfully!'); | |
} | |
} | |
// Load saved data from localStorage | |
function loadSavedData() { | |
const savedHtml = localStorage.getItem('uiDesigner_html'); | |
const savedCss = localStorage.getItem('uiDesigner_css'); | |
if (savedHtml) { | |
canvas.innerHTML = savedHtml; | |
// Add canvas-element class and resize handles to all top-level children | |
Array.from(canvas.children).forEach(child => { | |
child.classList.add('canvas-element'); | |
addResizeHandles(child); | |
}); | |
} | |
if (savedCss) { | |
let styleElement = document.getElementById('custom-css'); | |
if (!styleElement) { | |
styleElement = document.createElement('style'); | |
styleElement.id = 'custom-css'; | |
document.head.appendChild(styleElement); | |
} | |
styleElement.textContent = savedCss; | |
// Update editors | |
cssEditor.value = savedCss; | |
} | |
} | |
// Reset canvas | |
function resetCanvas() { | |
if (confirm('Are you sure you want to reset the canvas? This cannot be undone.')) { | |
canvas.innerHTML = ''; | |
// Remove custom CSS | |
const styleElement = document.getElementById('custom-css'); | |
if (styleElement) { | |
styleElement.remove(); | |
} | |
// Clear localStorage | |
localStorage.removeItem('uiDesigner_html'); | |
localStorage.removeItem('uiDesigner_css'); | |
// Clear editors | |
htmlEditor.value = ''; | |
cssEditor.value = ''; | |
selectedElement = null; | |
} | |
} | |
// Initialize the app | |
init(); | |
</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=basheer1414/ui-customization-tool" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |