Files
tourrism_site/public/style-editor-advanced.html
Andrey K. Choi 13c752b93a feat: Оптимизация навигации AdminJS в логические группы
- Объединены ресурсы в 5 логических групп: Контент сайта, Бронирования, Отзывы и рейтинги, Персонал и гиды, Администрирование
- Удалены дублирующие настройки navigation для чистой группировки
- Добавлены CSS стили для визуального отображения иерархии с отступами
- Добавлены эмодзи-иконки для каждого типа ресурсов через CSS
- Улучшена навигация с правильной вложенностью элементов
2025-11-30 21:57:58 +09:00

1013 lines
38 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Редактор стилей сайта</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
height: 100vh;
overflow: hidden;
}
.editor-container {
display: flex;
height: 100vh;
}
/* Боковая панель */
.sidebar {
width: 350px;
background: white;
border-right: 1px solid #e0e0e0;
overflow-y: auto;
box-shadow: 2px 0 10px rgba(0,0,0,0.1);
}
.sidebar-header {
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.sidebar-header h1 {
font-size: 18px;
font-weight: 600;
}
.sidebar-header p {
opacity: 0.9;
font-size: 14px;
margin-top: 5px;
}
/* Группы настроек */
.settings-group {
border-bottom: 1px solid #f0f0f0;
}
.group-header {
padding: 15px 20px;
background: #fafafa;
border-bottom: 1px solid #e0e0e0;
font-weight: 600;
font-size: 14px;
color: #333;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
}
.group-header:hover {
background: #f0f0f0;
}
.group-header .toggle {
transition: transform 0.2s;
}
.group-header.collapsed .toggle {
transform: rotate(-90deg);
}
.group-content {
padding: 15px 20px;
}
.group-content.collapsed {
display: none;
}
/* Элементы формы */
.form-row {
margin-bottom: 15px;
}
.form-label {
display: block;
font-size: 12px;
font-weight: 500;
color: #555;
margin-bottom: 5px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.form-input {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
transition: all 0.2s;
}
.form-input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
/* Цветовые контролы */
.color-control {
display: flex;
align-items: center;
gap: 10px;
}
.color-preview {
width: 30px;
height: 30px;
border-radius: 6px;
border: 1px solid #ddd;
cursor: pointer;
position: relative;
overflow: hidden;
}
.color-input {
position: absolute;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
}
.color-text {
flex: 1;
font-family: monospace;
font-size: 12px;
}
/* Файловые контролы */
.file-control {
position: relative;
}
.file-preview {
width: 100%;
height: 80px;
border: 2px dashed #ddd;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
color: #888;
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
background-size: cover;
background-position: center;
}
.file-preview:hover {
border-color: #667eea;
color: #667eea;
}
.file-preview.has-image {
border-style: solid;
color: white;
text-shadow: 0 1px 2px rgba(0,0,0,0.5);
}
.file-input {
position: absolute;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
}
/* Селекты */
.select-control {
position: relative;
}
.form-select {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 6px;
background: white;
cursor: pointer;
}
/* Превью область */
.preview-area {
flex: 1;
display: flex;
flex-direction: column;
}
.preview-header {
padding: 15px 20px;
background: white;
border-bottom: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
align-items: center;
}
.preview-title {
font-size: 16px;
font-weight: 600;
color: #333;
}
.action-buttons {
display: flex;
gap: 10px;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5a6fd8;
}
.btn-secondary {
background: #f8f9fa;
color: #6c757d;
border: 1px solid #dee2e6;
}
.btn-secondary:hover {
background: #e9ecef;
}
.preview-content {
flex: 1;
position: relative;
overflow: hidden;
}
.preview-iframe {
width: 100%;
height: 100%;
border: none;
background: white;
}
/* Уведомления */
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 12px 20px;
border-radius: 6px;
color: white;
font-weight: 500;
z-index: 1000;
transform: translateX(100%);
transition: transform 0.3s;
}
.notification.show {
transform: translateX(0);
}
.notification.success {
background: #28a745;
}
.notification.error {
background: #dc3545;
}
/* Резайзер */
.resizer {
width: 5px;
background: transparent;
cursor: col-resize;
position: relative;
}
.resizer:hover {
background: #667eea;
}
/* Мобильная адаптация */
@media (max-width: 768px) {
.sidebar {
width: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 100;
transform: translateX(-100%);
transition: transform 0.3s;
}
.sidebar.open {
transform: translateX(0);
}
.mobile-toggle {
display: block;
position: fixed;
top: 20px;
left: 20px;
z-index: 101;
background: #667eea;
color: white;
border: none;
padding: 10px;
border-radius: 6px;
}
}
.mobile-toggle {
display: none;
}
/* Загрузка */
.loading {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: #888;
}
.spinner {
width: 20px;
height: 20px;
border: 2px solid #f3f3f3;
border-top: 2px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<button class="mobile-toggle" onclick="toggleSidebar()"></button>
<div class="editor-container">
<!-- Боковая панель с настройками -->
<div class="sidebar" id="sidebar">
<div class="sidebar-header">
<h1>🎨 Редактор стилей</h1>
<p>Настройки дизайна сайта</p>
</div>
<!-- Цвета -->
<div class="settings-group">
<div class="group-header" onclick="toggleGroup('colors')">
<span>🎨 Цветовая схема</span>
<span class="toggle"></span>
</div>
<div class="group-content" id="colors-content">
<div class="form-row">
<label class="form-label">Основной цвет</label>
<div class="color-control">
<div class="color-preview" style="background-color: #ff6b6b;">
<input type="color" class="color-input" value="#ff6b6b" onchange="updateColor('primary', this.value)">
</div>
<input type="text" class="color-text form-input" value="#ff6b6b" oninput="updateColorText('primary', this.value)">
</div>
</div>
<div class="form-row">
<label class="form-label">Вторичный цвет</label>
<div class="color-control">
<div class="color-preview" style="background-color: #38C172;">
<input type="color" class="color-input" value="#38C172" onchange="updateColor('secondary', this.value)">
</div>
<input type="text" class="color-text form-input" value="#38C172" oninput="updateColorText('secondary', this.value)">
</div>
</div>
<div class="form-row">
<label class="form-label">Цвет фона</label>
<div class="color-control">
<div class="color-preview" style="background-color: #f8f9fa;">
<input type="color" class="color-input" value="#f8f9fa" onchange="updateColor('background', this.value)">
</div>
<input type="text" class="color-text form-input" value="#f8f9fa" oninput="updateColorText('background', this.value)">
</div>
</div>
<div class="form-row">
<label class="form-label">Цвет текста</label>
<div class="color-control">
<div class="color-preview" style="background-color: #333333;">
<input type="color" class="color-input" value="#333333" onchange="updateColor('text', this.value)">
</div>
<input type="text" class="color-text form-input" value="#333333" oninput="updateColorText('text', this.value)">
</div>
</div>
</div>
</div>
<!-- Типографика -->
<div class="settings-group">
<div class="group-header" onclick="toggleGroup('typography')">
<span>📝 Типографика</span>
<span class="toggle"></span>
</div>
<div class="group-content" id="typography-content">
<div class="form-row">
<label class="form-label">Основной шрифт</label>
<select class="form-select" onchange="updateFont('primary', this.value)">
<option value="'Inter', sans-serif">Inter</option>
<option value="'Roboto', sans-serif">Roboto</option>
<option value="'Open Sans', sans-serif">Open Sans</option>
<option value="'Montserrat', sans-serif">Montserrat</option>
<option value="'Poppins', sans-serif">Poppins</option>
</select>
</div>
<div class="form-row">
<label class="form-label">Размер базового шрифта</label>
<input type="range" class="form-input" min="14" max="20" value="16"
oninput="updateFontSize('base', this.value); this.nextElementSibling.textContent = this.value + 'px'">
<span>16px</span>
</div>
<div class="form-row">
<label class="form-label">Высота строки</label>
<input type="range" class="form-input" min="1.2" max="2.0" step="0.1" value="1.6"
oninput="updateLineHeight(this.value); this.nextElementSibling.textContent = this.value">
<span>1.6</span>
</div>
</div>
</div>
<!-- Изображения -->
<div class="settings-group">
<div class="group-header" onclick="toggleGroup('images')">
<span>🖼️ Изображения</span>
<span class="toggle"></span>
</div>
<div class="group-content" id="images-content">
<div class="form-row">
<label class="form-label">Логотип</label>
<div class="file-control">
<div class="file-preview" onclick="document.getElementById('logo-input').click()">
<span>Выберите логотип</span>
</div>
<input type="file" id="logo-input" class="file-input" accept="image/*" onchange="uploadImage('logo', this)">
</div>
</div>
<div class="form-row">
<label class="form-label">Фоновое изображение героя</label>
<div class="file-control">
<div class="file-preview" onclick="document.getElementById('hero-bg-input').click()">
<span>Выберите фон</span>
</div>
<input type="file" id="hero-bg-input" class="file-input" accept="image/*" onchange="uploadImage('hero-bg', this)">
</div>
</div>
<div class="form-row">
<label class="form-label">Фавикон</label>
<div class="file-control">
<div class="file-preview" onclick="document.getElementById('favicon-input').click()">
<span>Выберите фавикон</span>
</div>
<input type="file" id="favicon-input" class="file-input" accept="image/x-icon,image/png" onchange="uploadImage('favicon', this)">
</div>
</div>
</div>
</div>
<!-- Макет -->
<div class="settings-group">
<div class="group-header" onclick="toggleGroup('layout')">
<span>📐 Макет</span>
<span class="toggle"></span>
</div>
<div class="group-content" id="layout-content">
<div class="form-row">
<label class="form-label">Максимальная ширина контента</label>
<input type="range" class="form-input" min="1000" max="1400" value="1200"
oninput="updateMaxWidth(this.value); this.nextElementSibling.textContent = this.value + 'px'">
<span>1200px</span>
</div>
<div class="form-row">
<label class="form-label">Отступы секций</label>
<input type="range" class="form-input" min="20" max="100" value="60"
oninput="updateSectionPadding(this.value); this.nextElementSibling.textContent = this.value + 'px'">
<span>60px</span>
</div>
<div class="form-row">
<label class="form-label">Скругление углов</label>
<input type="range" class="form-input" min="0" max="20" value="8"
oninput="updateBorderRadius(this.value); this.nextElementSibling.textContent = this.value + 'px'">
<span>8px</span>
</div>
</div>
</div>
<!-- Анимации -->
<div class="settings-group">
<div class="group-header" onclick="toggleGroup('animations')">
<span>✨ Анимации</span>
<span class="toggle"></span>
</div>
<div class="group-content" id="animations-content">
<div class="form-row">
<label class="form-label">Скорость переходов</label>
<select class="form-select" onchange="updateTransitionSpeed(this.value)">
<option value="0.2s">Быстро (0.2s)</option>
<option value="0.3s" selected>Нормально (0.3s)</option>
<option value="0.5s">Медленно (0.5s)</option>
</select>
</div>
<div class="form-row">
<label class="form-label">Эффект при наведении</label>
<select class="form-select" onchange="updateHoverEffect(this.value)">
<option value="scale">Масштабирование</option>
<option value="lift" selected>Поднятие</option>
<option value="fade">Изменение прозрачности</option>
<option value="none">Без эффекта</option>
</select>
</div>
</div>
</div>
</div>
<!-- Разделитель -->
<div class="resizer" onmousedown="startResize(event)"></div>
<!-- Область превью -->
<div class="preview-area">
<div class="preview-header">
<h2 class="preview-title">Превью сайта</h2>
<div class="action-buttons">
<button class="btn btn-secondary" onclick="resetStyles()">Сбросить</button>
<button class="btn btn-secondary" onclick="exportStyles()">Экспорт</button>
<button class="btn btn-primary" onclick="saveStyles()">Сохранить</button>
</div>
</div>
<div class="preview-content">
<iframe id="preview-iframe" class="preview-iframe" src="http://localhost:3000"></iframe>
</div>
</div>
</div>
<!-- Уведомления -->
<div id="notification" class="notification"></div>
<script>
let currentStyles = {};
let isResizing = false;
// Инициализация
document.addEventListener('DOMContentLoaded', function() {
loadCurrentStyles();
initializeIframe();
});
// Переключение групп настроек
function toggleGroup(groupName) {
const content = document.getElementById(groupName + '-content');
const header = content.previousElementSibling;
if (content.classList.contains('collapsed')) {
content.classList.remove('collapsed');
header.classList.remove('collapsed');
} else {
content.classList.add('collapsed');
header.classList.add('collapsed');
}
}
// Мобильное меню
function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
sidebar.classList.toggle('open');
}
// Обновление цветов
function updateColor(type, color) {
currentStyles[type + '-color'] = color;
// Обновляем превью цвета
const preview = document.querySelector(`input[onchange*="${type}"]`).parentElement;
preview.style.backgroundColor = color;
// Обновляем текстовое поле
const textInput = preview.nextElementSibling;
textInput.value = color;
applyStyleChanges();
}
function updateColorText(type, color) {
if (isValidColor(color)) {
updateColor(type, color);
}
}
function isValidColor(color) {
const s = new Option().style;
s.color = color;
return s.color !== '';
}
// Обновление шрифтов
function updateFont(type, font) {
currentStyles[type + '-font'] = font;
applyStyleChanges();
}
function updateFontSize(type, size) {
currentStyles[type + '-font-size'] = size + 'px';
applyStyleChanges();
}
function updateLineHeight(height) {
currentStyles['line-height'] = height;
applyStyleChanges();
}
// Загрузка изображений
async function uploadImage(type, input) {
const file = input.files[0];
if (!file) return;
const formData = new FormData();
formData.append('image', file);
try {
showNotification('Загрузка изображения...', 'info');
const response = await fetch('/api/images/upload', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success) {
currentStyles[type + '-image'] = result.url;
// Обновляем превью
const preview = input.parentElement.querySelector('.file-preview');
preview.style.backgroundImage = `url(${result.url})`;
preview.classList.add('has-image');
preview.innerHTML = '<span>Изображение загружено</span>';
applyStyleChanges();
showNotification('Изображение успешно загружено!', 'success');
} else {
throw new Error(result.error || 'Ошибка загрузки');
}
} catch (error) {
console.error('Ошибка загрузки изображения:', error);
showNotification('Ошибка загрузки изображения', 'error');
}
}
// Обновление макета
function updateMaxWidth(width) {
currentStyles['max-width'] = width + 'px';
applyStyleChanges();
}
function updateSectionPadding(padding) {
currentStyles['section-padding'] = padding + 'px';
applyStyleChanges();
}
function updateBorderRadius(radius) {
currentStyles['border-radius'] = radius + 'px';
applyStyleChanges();
}
// Обновление анимаций
function updateTransitionSpeed(speed) {
currentStyles['transition-speed'] = speed;
applyStyleChanges();
}
function updateHoverEffect(effect) {
currentStyles['hover-effect'] = effect;
applyStyleChanges();
}
// Применение изменений стилей
function applyStyleChanges() {
const iframe = document.getElementById('preview-iframe');
if (!iframe.contentDocument) return;
let customCSS = generateCustomCSS();
// Удаляем старые стили
const oldStyle = iframe.contentDocument.querySelector('#custom-styles');
if (oldStyle) oldStyle.remove();
// Добавляем новые стили
const style = iframe.contentDocument.createElement('style');
style.id = 'custom-styles';
style.textContent = customCSS;
iframe.contentDocument.head.appendChild(style);
}
// Генерация CSS
function generateCustomCSS() {
let css = ':root {\n';
// Цвета
if (currentStyles['primary-color']) {
css += ` --primary-color: ${currentStyles['primary-color']};\n`;
css += ` --primary-rgb: ${hexToRgb(currentStyles['primary-color'])};\n`;
}
if (currentStyles['secondary-color']) {
css += ` --secondary-color: ${currentStyles['secondary-color']};\n`;
css += ` --secondary-rgb: ${hexToRgb(currentStyles['secondary-color'])};\n`;
}
if (currentStyles['background-color']) {
css += ` --background-color: ${currentStyles['background-color']};\n`;
}
if (currentStyles['text-color']) {
css += ` --text-color: ${currentStyles['text-color']};\n`;
}
css += '}\n\n';
// Основные стили
css += 'body {\n';
if (currentStyles['primary-font']) {
css += ` font-family: ${currentStyles['primary-font']} !important;\n`;
}
if (currentStyles['base-font-size']) {
css += ` font-size: ${currentStyles['base-font-size']} !important;\n`;
}
if (currentStyles['line-height']) {
css += ` line-height: ${currentStyles['line-height']} !important;\n`;
}
if (currentStyles['text-color']) {
css += ` color: ${currentStyles['text-color']} !important;\n`;
}
css += '}\n\n';
// Контейнеры
if (currentStyles['max-width']) {
css += `.container, .main-content {\n max-width: ${currentStyles['max-width']} !important;\n margin: 0 auto;\n}\n\n`;
}
// Секции
if (currentStyles['section-padding']) {
css += `.section, section {\n padding: ${currentStyles['section-padding']} 0 !important;\n}\n\n`;
}
// Цветовые схемы для компонентов
if (currentStyles['primary-color']) {
css += `.btn-primary, .bg-primary, .text-primary {\n background-color: ${currentStyles['primary-color']} !important;\n border-color: ${currentStyles['primary-color']} !important;\n}\n\n`;
css += `.btn-primary:hover {\n background-color: ${darkenColor(currentStyles['primary-color'], 10)} !important;\n border-color: ${darkenColor(currentStyles['primary-color'], 10)} !important;\n}\n\n`;
css += `.navbar-brand, .nav-link:hover {\n color: ${currentStyles['primary-color']} !important;\n}\n\n`;
}
if (currentStyles['secondary-color']) {
css += `.btn-secondary, .bg-secondary {\n background-color: ${currentStyles['secondary-color']} !important;\n border-color: ${currentStyles['secondary-color']} !important;\n}\n\n`;
css += `.btn-secondary:hover {\n background-color: ${darkenColor(currentStyles['secondary-color'], 10)} !important;\n border-color: ${darkenColor(currentStyles['secondary-color'], 10)} !important;\n}\n\n`;
}
// Фон страницы
if (currentStyles['background-color']) {
css += `body, .bg-light {\n background-color: ${currentStyles['background-color']} !important;\n}\n\n`;
}
// Скругление углов
if (currentStyles['border-radius']) {
css += `.card, .btn, .form-control, .rounded, .modal-content {\n border-radius: ${currentStyles['border-radius']} !important;\n}\n\n`;
css += `.card-img-top {\n border-top-left-radius: ${currentStyles['border-radius']} !important;\n border-top-right-radius: ${currentStyles['border-radius']} !important;\n}\n\n`;
}
// Переходы
if (currentStyles['transition-speed']) {
css += `*, *::before, *::after {\n transition: all ${currentStyles['transition-speed']} ease !important;\n}\n\n`;
}
// Эффекты при наведении
if (currentStyles['hover-effect']) {
switch(currentStyles['hover-effect']) {
case 'scale':
css += `.card:hover, .btn:hover {\n transform: scale(1.05) !important;\n}\n\n`;
break;
case 'lift':
css += `.card:hover {\n transform: translateY(-10px) !important;\n box-shadow: 0 20px 40px rgba(0,0,0,0.15) !important;\n}\n\n`;
css += `.btn:hover {\n transform: translateY(-2px) !important;\n box-shadow: 0 8px 20px rgba(0,0,0,0.15) !important;\n}\n\n`;
break;
case 'fade':
css += `.card:hover, .btn:hover {\n opacity: 0.85 !important;\n}\n\n`;
break;
}
}
// Фоновые изображения
if (currentStyles['hero-bg-image']) {
css += `.hero, .jumbotron, .bg-hero {\n background-image: url('${currentStyles['hero-bg-image']}') !important;\n background-size: cover;\n background-position: center;\n background-attachment: fixed;\n}\n\n`;
}
// Логотип
if (currentStyles['logo-image']) {
css += `.navbar-brand img, .logo {\n content: url('${currentStyles['logo-image']}') !important;\n max-height: 50px;\n width: auto;\n}\n\n`;
}
return css;
}
// Вспомогательные функции для работы с цветами
function hexToRgb(hex) {
const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);
return result ?
\`\${parseInt(result[1], 16)}, \${parseInt(result[2], 16)}, \${parseInt(result[3], 16)}\` :
'0, 0, 0';
}
function darkenColor(hex, amount) {
const num = parseInt(hex.replace('#', ''), 16);
const r = Math.max(0, (num >> 16) - amount);
const g = Math.max(0, (num >> 8 & 0x00FF) - amount);
const b = Math.max(0, (num & 0x0000FF) - amount);
return '#' + (r << 16 | g << 8 | b).toString(16).padStart(6, '0');
}
// Инициализация iframe
function initializeIframe() {
const iframe = document.getElementById('preview-iframe');
iframe.onload = function() {
// Применяем текущие стили после загрузки
setTimeout(() => {
applyStyleChanges();
}, 100);
};
}
// Загрузка текущих стилей
async function loadCurrentStyles() {
try {
const response = await fetch('/api/settings/styles');
const data = await response.json();
currentStyles = data.styles || {};
updateUIFromStyles();
} catch (error) {
console.error('Ошибка загрузки стилей:', error);
}
}
// Обновление интерфейса из стилей
function updateUIFromStyles() {
// Обновляем все контролы на основе загруженных стилей
Object.keys(currentStyles).forEach(key => {
const value = currentStyles[key];
// Находим соответствующий контрол и обновляем его
const control = document.querySelector(`[onchange*="${key}"], [oninput*="${key}"]`);
if (control) {
control.value = value;
// Особая обработка для цветовых контролов
if (key.includes('color')) {
const preview = control.parentElement;
if (preview.classList.contains('color-preview')) {
preview.style.backgroundColor = value;
}
}
}
});
}
// Сохранение стилей
async function saveStyles() {
try {
showNotification('Сохранение стилей...', 'info');
const response = await fetch('/api/settings/styles', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
styles: currentStyles,
css: generateCustomCSS()
})
});
const result = await response.json();
if (result.success) {
showNotification('Стили успешно сохранены!', 'success');
} else {
throw new Error(result.error || 'Ошибка сохранения');
}
} catch (error) {
console.error('Ошибка сохранения стилей:', error);
showNotification('Ошибка сохранения стилей', 'error');
}
}
// Сброс стилей
function resetStyles() {
if (confirm('Вы уверены, что хотите сбросить все настройки?')) {
currentStyles = {};
updateUIFromStyles();
applyStyleChanges();
showNotification('Стили сброшены', 'success');
}
}
// Экспорт стилей
function exportStyles() {
const css = generateCustomCSS();
const blob = new Blob([css], { type: 'text/css' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'custom-styles.css';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showNotification('CSS файл экспортирован', 'success');
}
// Уведомления
function showNotification(message, type = 'info') {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.className = `notification ${type} show`;
setTimeout(() => {
notification.classList.remove('show');
}, 3000);
}
// Изменение размера панели
function startResize(e) {
isResizing = true;
document.addEventListener('mousemove', resize);
document.addEventListener('mouseup', stopResize);
}
function resize(e) {
if (!isResizing) return;
const sidebar = document.getElementById('sidebar');
const newWidth = Math.min(Math.max(e.clientX, 250), window.innerWidth * 0.7);
sidebar.style.width = newWidth + 'px';
}
function stopResize() {
isResizing = false;
document.removeEventListener('mousemove', resize);
document.removeEventListener('mouseup', stopResize);
}
// Горячие клавиши
document.addEventListener('keydown', function(e) {
if (e.ctrlKey || e.metaKey) {
switch(e.key) {
case 's':
e.preventDefault();
saveStyles();
break;
case 'r':
e.preventDefault();
resetStyles();
break;
}
}
});
</script>
</body>
</html>