Spaces:
Running
Running
| <html lang="ru"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Интерактивное дерево с плавным перетаскиванием</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> | |
| .tree-node { | |
| transition: all 0.2s ease; | |
| position: relative; | |
| } | |
| .tree-node:hover { | |
| background-color: #f3f4f6; | |
| } | |
| .tree-node.dragging { | |
| opacity: 0.5; | |
| background-color: #e5e7eb; | |
| } | |
| .tree-node-placeholder { | |
| border: 2px dashed #3b82f6; | |
| background-color: #eff6ff; | |
| height: 40px; | |
| margin: 4px 0; | |
| border-radius: 4px; | |
| transition: all 0.1s ease; | |
| } | |
| .nested { | |
| display: none; | |
| transition: all 0.3s ease; | |
| } | |
| .nested.active { | |
| display: block; | |
| animation: fadeIn 0.3s ease-in-out; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(-10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .drag-handle { | |
| cursor: move; | |
| opacity: 0.5; | |
| transition: opacity 0.2s ease; | |
| } | |
| .drag-handle:hover { | |
| opacity: 1; | |
| } | |
| .drag-ghost { | |
| position: absolute; | |
| z-index: 1000; | |
| opacity: 0.8; | |
| pointer-events: none; | |
| background: white; | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); | |
| border-radius: 0.375rem; | |
| border: 1px solid #e5e7eb; | |
| transform: translate(0, 0); | |
| transition: transform 0.1s ease; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 font-sans p-6"> | |
| <div class="max-w-4xl mx-auto"> | |
| <div class="bg-white rounded-xl shadow-md overflow-hidden"> | |
| <div class="p-6"> | |
| <h1 class="text-2xl font-bold text-gray-800 mb-6">Интерактивное дерево с плавным перетаскиванием</h1> | |
| <div class="flex space-x-4 mb-6"> | |
| <button id="addRootNode" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg flex items-center space-x-2"> | |
| <i class="fas fa-plus"></i> | |
| <span>Добавить корневой элемент</span> | |
| </button> | |
| <button id="expandAll" class="bg-gray-200 hover:bg-gray-300 text-gray-700 px-4 py-2 rounded-lg flex items-center space-x-2"> | |
| <i class="fas fa-expand"></i> | |
| <span>Развернуть все</span> | |
| </button> | |
| <button id="collapseAll" class="bg-gray-200 hover:bg-gray-300 text-gray-700 px-4 py-2 rounded-lg flex items-center space-x-2"> | |
| <i class="fas fa-compress"></i> | |
| <span>Свернуть все</span> | |
| </button> | |
| </div> | |
| <div id="treeContainer" class="border border-gray-200 rounded-lg p-4 min-h-40"> | |
| <!-- Дерево будет сгенерировано здесь --> | |
| <div id="treeRoot" class="space-y-1"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Модальное окно для добавления/редактирования узла --> | |
| <div id="nodeModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> | |
| <div class="bg-white rounded-lg p-6 max-w-md w-full"> | |
| <div class="flex justify-between items-start mb-4"> | |
| <h3 class="text-lg font-medium text-gray-900" id="modalTitle">Добавить узел</h3> | |
| <button id="closeNodeModal" class="text-gray-400 hover:text-gray-500"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="nodeName" class="block text-sm font-medium text-gray-700 mb-1">Название узла</label> | |
| <input type="text" id="nodeName" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
| </div> | |
| <div class="flex justify-end space-x-3"> | |
| <button id="cancelNode" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"> | |
| Отмена | |
| </button> | |
| <button id="saveNode" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"> | |
| Сохранить | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Инициализация дерева | |
| let treeData = [ | |
| { | |
| id: 1, | |
| name: "Корневой элемент 1", | |
| children: [ | |
| { | |
| id: 2, | |
| name: "Дочерний элемент 1.1", | |
| children: [ | |
| { | |
| id: 3, | |
| name: "Вложенный элемент 1.1.1", | |
| children: [] | |
| }, | |
| { | |
| id: 4, | |
| name: "Вложенный элемент 1.1.2", | |
| children: [] | |
| } | |
| ] | |
| }, | |
| { | |
| id: 5, | |
| name: "Дочерний элемент 1.2", | |
| children: [] | |
| } | |
| ] | |
| }, | |
| { | |
| id: 6, | |
| name: "Корневой элемент 2", | |
| children: [] | |
| } | |
| ]; | |
| // Переменные для управления состоянием | |
| let nextId = 7; | |
| let currentNodeId = null; | |
| let currentParentId = null; | |
| let isEditing = false; | |
| let draggedNode = null; | |
| let draggedNodeParent = null; | |
| let draggedNodeIndex = null; | |
| let dragGhost = null; | |
| let lastDragPosition = { x: 0, y: 0 }; | |
| let placeholder = null; | |
| let isDragging = false; | |
| let targetParentId = null; | |
| let targetIndex = null; | |
| // DOM элементы | |
| const treeRoot = document.getElementById('treeRoot'); | |
| const nodeModal = document.getElementById('nodeModal'); | |
| const nodeNameInput = document.getElementById('nodeName'); | |
| const modalTitle = document.getElementById('modalTitle'); | |
| const addRootNodeBtn = document.getElementById('addRootNode'); | |
| const expandAllBtn = document.getElementById('expandAll'); | |
| const collapseAllBtn = document.getElementById('collapseAll'); | |
| // Инициализация дерева при загрузке | |
| document.addEventListener('DOMContentLoaded', () => { | |
| renderTree(); | |
| setupEventListeners(); | |
| }); | |
| // Рендер дерева | |
| function renderTree(data = treeData, parentElement = treeRoot, level = 0) { | |
| parentElement.innerHTML = ''; | |
| if (data.length === 0 && parentElement === treeRoot) { | |
| parentElement.innerHTML = '<p class="text-gray-500 italic">Дерево пустое</p>'; | |
| return; | |
| } | |
| data.forEach((node, index) => { | |
| const hasChildren = node.children && node.children.length > 0; | |
| const nodeElement = document.createElement('div'); | |
| nodeElement.className = 'tree-node pl-' + (level * 4); | |
| nodeElement.dataset.id = node.id; | |
| nodeElement.dataset.parentId = parentElement.dataset.id || 'root'; | |
| nodeElement.dataset.index = index; | |
| nodeElement.innerHTML = ` | |
| <div class="flex items-center py-2 px-3 rounded-lg border border-gray-200"> | |
| <div class="flex items-center flex-1"> | |
| <span class="drag-handle mr-2 text-gray-400 cursor-move"> | |
| <i class="fas fa-grip-vertical"></i> | |
| </span> | |
| ${hasChildren ? ` | |
| <button class="toggle-node mr-2 text-gray-500 hover:text-gray-700" data-id="${node.id}"> | |
| <i class="fas fa-caret-right"></i> | |
| </button> | |
| ` : '<span class="w-6"></span>'} | |
| <span class="node-name">${node.name}</span> | |
| </div> | |
| <div class="flex space-x-2"> | |
| <button class="edit-node p-1 text-blue-500 hover:text-blue-700" data-id="${node.id}"> | |
| <i class="fas fa-edit"></i> | |
| </button> | |
| <button class="add-child p-1 text-green-500 hover:text-green-700" data-id="${node.id}"> | |
| <i class="fas fa-plus"></i> | |
| </button> | |
| <button class="delete-node p-1 text-red-500 hover:text-red-700" data-id="${node.id}"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </div> | |
| </div> | |
| ${hasChildren ? ` | |
| <div class="nested pl-4" data-parent-id="${node.id}"></div> | |
| ` : ''} | |
| `; | |
| parentElement.appendChild(nodeElement); | |
| // Рендерим детей, если они есть | |
| if (hasChildren) { | |
| const nestedContainer = nodeElement.querySelector('.nested'); | |
| renderTree(node.children, nestedContainer, level + 1); | |
| } | |
| }); | |
| } | |
| // Настройка обработчиков событий | |
| function setupEventListeners() { | |
| // Кнопки модального окна | |
| document.getElementById('closeNodeModal').addEventListener('click', closeNodeModal); | |
| document.getElementById('cancelNode').addEventListener('click', closeNodeModal); | |
| document.getElementById('saveNode').addEventListener('click', saveNode); | |
| // Кнопки управления деревом | |
| addRootNodeBtn.addEventListener('click', () => { | |
| openNodeModal(null, null, false); | |
| }); | |
| expandAllBtn.addEventListener('click', expandAllNodes); | |
| collapseAllBtn.addEventListener('click', collapseAllNodes); | |
| // Делегирование событий для динамически созданных элементов | |
| treeRoot.addEventListener('click', (e) => { | |
| const target = e.target.closest('.toggle-node'); | |
| if (target) { | |
| toggleNode(target.dataset.id); | |
| return; | |
| } | |
| const editBtn = e.target.closest('.edit-node'); | |
| if (editBtn) { | |
| openNodeModal(editBtn.dataset.id, null, true); | |
| return; | |
| } | |
| const addChildBtn = e.target.closest('.add-child'); | |
| if (addChildBtn) { | |
| openNodeModal(null, addChildBtn.dataset.id, false); | |
| return; | |
| } | |
| const deleteBtn = e.target.closest('.delete-node'); | |
| if (deleteBtn) { | |
| deleteNode(deleteBtn.dataset.id); | |
| return; | |
| } | |
| }); | |
| // Перетаскивание | |
| setupDragAndDrop(); | |
| } | |
| // Настройка плавного перетаскивания | |
| function setupDragAndDrop() { | |
| document.addEventListener('mousedown', (e) => { | |
| const handle = e.target.closest('.drag-handle'); | |
| if (!handle) return; | |
| const nodeElement = handle.closest('.tree-node'); | |
| if (!nodeElement) return; | |
| draggedNode = findNodeById(treeData, nodeElement.dataset.id); | |
| draggedNodeParent = nodeElement.dataset.parentId === 'root' ? treeData : | |
| findNodeById(treeData, nodeElement.dataset.parentId).children; | |
| draggedNodeIndex = parseInt(nodeElement.dataset.index); | |
| // Создаем призрачный элемент для перетаскивания | |
| dragGhost = nodeElement.cloneNode(true); | |
| dragGhost.classList.add('drag-ghost'); | |
| dragGhost.style.width = nodeElement.offsetWidth + 'px'; | |
| dragGhost.style.left = nodeElement.getBoundingClientRect().left + 'px'; | |
| dragGhost.style.top = nodeElement.getBoundingClientRect().top + 'px'; | |
| document.body.appendChild(dragGhost); | |
| // Сохраняем начальную позицию | |
| lastDragPosition = { x: e.clientX, y: e.clientY }; | |
| isDragging = true; | |
| // Добавляем класс для визуального эффекта | |
| nodeElement.classList.add('dragging'); | |
| // Отключаем выделение текста при перетаскивании | |
| document.body.style.userSelect = 'none'; | |
| // Обработчики для плавного перетаскивания | |
| document.addEventListener('mousemove', handleDragMove); | |
| document.addEventListener('mouseup', handleDragEnd); | |
| e.preventDefault(); | |
| }); | |
| function handleDragMove(e) { | |
| if (!isDragging || !dragGhost) return; | |
| // Вычисляем смещение | |
| const dx = e.clientX - lastDragPosition.x; | |
| const dy = e.clientY - lastDragPosition.y; | |
| // Обновляем позицию призрака | |
| const ghostRect = dragGhost.getBoundingClientRect(); | |
| dragGhost.style.left = (ghostRect.left + dx) + 'px'; | |
| dragGhost.style.top = (ghostRect.top + dy) + 'px'; | |
| // Обновляем последнюю позицию | |
| lastDragPosition = { x: e.clientX, y: e.clientY }; | |
| // Определяем элемент под курсором | |
| const elements = document.elementsFromPoint(e.clientX, e.clientY); | |
| let targetElement = null; | |
| for (const el of elements) { | |
| if (el.classList.contains('tree-node') || el.classList.contains('nested') || el.id === 'treeRoot') { | |
| targetElement = el; | |
| break; | |
| } | |
| } | |
| // Обработка позиционирования плейсхолдера | |
| if (targetElement) { | |
| // Удаляем старый плейсхолдер | |
| removePlaceholder(); | |
| if (targetElement.classList.contains('tree-node')) { | |
| const rect = targetElement.getBoundingClientRect(); | |
| const midpoint = rect.top + rect.height / 2; | |
| // Определяем, куда вставлять - выше или ниже узла | |
| if (e.clientY < midpoint) { | |
| showPlaceholder(targetElement, 'before'); | |
| targetParentId = targetElement.dataset.parentId; | |
| targetIndex = parseInt(targetElement.dataset.index); | |
| } else { | |
| const hasChildren = targetElement.querySelector('.toggle-node') !== null; | |
| if (hasChildren) { | |
| const nested = targetElement.querySelector('.nested'); | |
| if (!nested.classList.contains('active')) { | |
| // Если узел свернут, показываем плейсхолдер внутри | |
| showPlaceholder(nested, 'first'); | |
| targetParentId = targetElement.dataset.id; | |
| targetIndex = 0; | |
| } else { | |
| // Если узел развернут, показываем плейсхолдер после | |
| showPlaceholder(targetElement, 'after'); | |
| targetParentId = targetElement.dataset.parentId; | |
| targetIndex = parseInt(targetElement.dataset.index) + 1; | |
| } | |
| } else { | |
| showPlaceholder(targetElement, 'after'); | |
| targetParentId = targetElement.dataset.parentId; | |
| targetIndex = parseInt(targetElement.dataset.index) + 1; | |
| } | |
| } | |
| } | |
| else if (targetElement.classList.contains('nested')) { | |
| const children = Array.from(targetElement.children).filter(el => el.classList.contains('tree-node')); | |
| if (children.length === 0) { | |
| showPlaceholder(targetElement, 'first'); | |
| targetParentId = targetElement.dataset.parentId; | |
| targetIndex = 0; | |
| } else { | |
| const lastChild = children[children.length - 1]; | |
| showPlaceholder(lastChild, 'after'); | |
| targetParentId = targetElement.dataset.parentId; | |
| targetIndex = children.length; | |
| } | |
| } | |
| else if (targetElement.id === 'treeRoot') { | |
| const children = Array.from(targetElement.children).filter(el => el.classList.contains('tree-node')); | |
| if (children.length === 0) { | |
| showPlaceholder(targetElement, 'first'); | |
| targetParentId = 'root'; | |
| targetIndex = 0; | |
| } else { | |
| const lastChild = children[children.length - 1]; | |
| showPlaceholder(lastChild, 'after'); | |
| targetParentId = 'root'; | |
| targetIndex = children.length; | |
| } | |
| } | |
| } | |
| } | |
| function handleDragEnd(e) { | |
| if (!isDragging) return; | |
| // Удаляем призрака | |
| if (dragGhost) { | |
| dragGhost.remove(); | |
| dragGhost = null; | |
| } | |
| // Удаляем плейсхолдер | |
| removePlaceholder(); | |
| // Восстанавливаем выделение текста | |
| document.body.style.userSelect = ''; | |
| // Удаляем обработчики | |
| document.removeEventListener('mousemove', handleDragMove); | |
| document.removeEventListener('mouseup', handleDragEnd); | |
| // Если есть перетаскиваемый узел и целевая позиция | |
| if (draggedNode && targetParentId !== null && targetIndex !== null) { | |
| // Определяем новый родительский массив | |
| let newParentArray; | |
| if (targetParentId === 'root') { | |
| newParentArray = treeData; | |
| } else { | |
| const parentNode = findNodeById(treeData, targetParentId); | |
| if (parentNode) { | |
| newParentArray = parentNode.children; | |
| } else { | |
| newParentArray = treeData; | |
| } | |
| } | |
| // Проверяем, что перемещение допустимо (не в самого себя или своих потомков) | |
| if (!isDescendant(draggedNode, targetParentId === 'root' ? null : findNodeById(treeData, targetParentId))) { | |
| // Удаляем узел из старого места | |
| const oldIndex = draggedNodeParent.indexOf(draggedNode); | |
| if (oldIndex !== -1) { | |
| draggedNodeParent.splice(oldIndex, 1); | |
| } | |
| // Вставляем узел в новое место | |
| if (targetIndex > newParentArray.length) { | |
| targetIndex = newParentArray.length; | |
| } | |
| newParentArray.splice(targetIndex, 0, draggedNode); | |
| // Перерисовываем дерево | |
| renderTree(); | |
| // Автоматически раскрываем родительский узел, если нужно | |
| if (targetParentId !== 'root') { | |
| const parentElement = document.querySelector(`.tree-node[data-id="${targetParentId}"]`); | |
| if (parentElement) { | |
| const nested = parentElement.querySelector('.nested'); | |
| if (nested && !nested.classList.contains('active')) { | |
| toggleNode(targetParentId); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Сбрасываем состояние перетаскивания | |
| resetDragState(); | |
| // Удаляем класс dragging со всех элементов | |
| document.querySelectorAll('.tree-node.dragging').forEach(el => { | |
| el.classList.remove('dragging'); | |
| }); | |
| isDragging = false; | |
| targetParentId = null; | |
| targetIndex = null; | |
| } | |
| } | |
| // Показать плейсхолдер для перетаскивания | |
| function showPlaceholder(element, position) { | |
| removePlaceholder(); | |
| placeholder = document.createElement('div'); | |
| placeholder.className = 'tree-node-placeholder'; | |
| placeholder.dataset.position = position; | |
| if (position === 'before') { | |
| element.parentNode.insertBefore(placeholder, element); | |
| } | |
| else if (position === 'after') { | |
| element.parentNode.insertBefore(placeholder, element.nextSibling); | |
| } | |
| else if (position === 'first') { | |
| element.insertBefore(placeholder, element.firstChild); | |
| } | |
| else if (position === 'last') { | |
| element.appendChild(placeholder); | |
| } | |
| } | |
| // Удалить плейсхолдер | |
| function removePlaceholder() { | |
| if (placeholder) { | |
| placeholder.remove(); | |
| placeholder = null; | |
| } | |
| } | |
| // Сбросить состояние перетаскивания | |
| function resetDragState() { | |
| draggedNode = null; | |
| draggedNodeParent = null; | |
| draggedNodeIndex = null; | |
| } | |
| // Проверить, является ли узел потомком другого узла | |
| function isDescendant(node, parentNode) { | |
| if (!parentNode) return false; | |
| if (node.id === parentNode.id) return true; | |
| // Проверяем всех детей родительского узла | |
| for (const child of parentNode.children) { | |
| if (isDescendant(node, child)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| // Найти узел по ID | |
| function findNodeById(data, id) { | |
| for (let i = 0; i < data.length; i++) { | |
| if (data[i].id == id) { | |
| return data[i]; | |
| } | |
| if (data[i].children && data[i].children.length > 0) { | |
| const found = findNodeById(data[i].children, id); | |
| if (found) return found; | |
| } | |
| } | |
| return null; | |
| } | |
| // Найти родительский узел | |
| function findParentNode(data, id, parent = null) { | |
| for (let i = 0; i < data.length; i++) { | |
| if (data[i].id == id) { | |
| return parent; | |
| } | |
| if (data[i].children && data[i].children.length > 0) { | |
| const found = findParentNode(data[i].children, id, data[i]); | |
| if (found) return found; | |
| } | |
| } | |
| return null; | |
| } | |
| // Открыть модальное окно для добавления/редактирования узла | |
| function openNodeModal(nodeId, parentId, editMode) { | |
| currentNodeId = nodeId; | |
| currentParentId = parentId; | |
| isEditing = editMode; | |
| if (editMode) { | |
| modalTitle.textContent = 'Редактировать узел'; | |
| const node = findNodeById(treeData, nodeId); | |
| nodeNameInput.value = node.name; | |
| } else { | |
| modalTitle.textContent = parentId ? 'Добавить дочерний узел' : 'Добавить корневой узел'; | |
| nodeNameInput.value = ''; | |
| } | |
| nodeModal.classList.remove('hidden'); | |
| nodeNameInput.focus(); | |
| } | |
| // Закрыть модальное окно | |
| function closeNodeModal() { | |
| nodeModal.classList.add('hidden'); | |
| } | |
| // Сохранить узел | |
| function saveNode() { | |
| const name = nodeNameInput.value.trim(); | |
| if (!name) { | |
| alert('Пожалуйста, введите название узла'); | |
| return; | |
| } | |
| if (isEditing) { | |
| // Редактирование существующего узла | |
| const node = findNodeById(treeData, currentNodeId); | |
| if (node) { | |
| node.name = name; | |
| } | |
| } else { | |
| // Добавление нового узла | |
| const newNode = { | |
| id: nextId++, | |
| name: name, | |
| children: [] | |
| }; | |
| if (currentParentId) { | |
| // Добавляем как дочерний узел | |
| const parentNode = findNodeById(treeData, currentParentId); | |
| if (parentNode) { | |
| parentNode.children.push(newNode); | |
| } | |
| } else { | |
| // Добавляем как корневой узел | |
| treeData.push(newNode); | |
| } | |
| } | |
| // Перерисовываем дерево | |
| renderTree(); | |
| closeNodeModal(); | |
| } | |
| // Удалить узел | |
| function deleteNode(nodeId) { | |
| if (!confirm('Вы уверены, что хотите удалить этот узел и все его дочерние элементы?')) { | |
| return; | |
| } | |
| const parent = findParentNode(treeData, nodeId); | |
| if (parent) { | |
| // Удаляем из дочерних элементов родителя | |
| parent.children = parent.children.filter(node => node.id != nodeId); | |
| } else { | |
| // Удаляем из корневых элементов | |
| treeData = treeData.filter(node => node.id != nodeId); | |
| } | |
| // Перерисовываем дерево | |
| renderTree(); | |
| } | |
| // Переключить видимость дочерних узлов | |
| function toggleNode(nodeId) { | |
| const nodeElement = document.querySelector(`.tree-node[data-id="${nodeId}"]`); | |
| if (!nodeElement) return; | |
| const nestedContainer = nodeElement.querySelector('.nested'); | |
| if (!nestedContainer) return; | |
| const toggleBtn = nodeElement.querySelector('.toggle-node'); | |
| if (nestedContainer.classList.contains('active')) { | |
| // Скрываем | |
| nestedContainer.classList.remove('active'); | |
| toggleBtn.innerHTML = '<i class="fas fa-caret-right"></i>'; | |
| } else { | |
| // Показываем | |
| nestedContainer.classList.add('active'); | |
| toggleBtn.innerHTML = '<i class="fas fa-caret-down"></i>'; | |
| } | |
| } | |
| // Развернуть все узлы | |
| function expandAllNodes() { | |
| document.querySelectorAll('.nested').forEach(nested => { | |
| nested.classList.add('active'); | |
| }); | |
| document.querySelectorAll('.toggle-node').forEach(btn => { | |
| btn.innerHTML = '<i class="fas fa-caret-down"></i>'; | |
| }); | |
| } | |
| // Свернуть все узлы | |
| function collapseAllNodes() { | |
| document.querySelectorAll('.nested').forEach(nested => { | |
| nested.classList.remove('active'); | |
| }); | |
| document.querySelectorAll('.toggle-node').forEach(btn => { | |
| btn.innerHTML = '<i class="fas fa-caret-right"></i>'; | |
| }); | |
| } | |
| </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=SimpleCodeTM/tree-moved" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |