diff --git a/public/app.js b/public/app.js index 451b27d..2fdb91f 100644 --- a/public/app.js +++ b/public/app.js @@ -17,6 +17,7 @@ const codeBtn = document.getElementById("codeBtn"); const linkBtn = document.getElementById("linkBtn"); const checkboxBtn = document.getElementById("checkboxBtn"); const imageBtn = document.getElementById("imageBtn"); +const previewBtn = document.getElementById("previewBtn"); // Кнопка настроек const settingsBtn = document.getElementById("settings-btn"); @@ -27,6 +28,10 @@ const imagePreviewContainer = document.getElementById("imagePreviewContainer"); const imagePreviewList = document.getElementById("imagePreviewList"); const clearImagesBtn = document.getElementById("clearImagesBtn"); +// Элементы для предпросмотра заметки +const notePreviewContainer = document.getElementById("notePreviewContainer"); +const notePreviewContent = document.getElementById("notePreviewContent"); + // Модальное окно для просмотра изображений const imageModal = document.getElementById("imageModal"); const modalImage = document.getElementById("modalImage"); @@ -35,6 +40,9 @@ const modalClose = document.querySelector(".image-modal-close"); // Массив для хранения выбранных изображений let selectedImages = []; +// Флаг режима предпросмотра +let isPreviewMode = false; + // Глобальные переменные для заметок и фильтрации let allNotes = []; let selectedDateFilter = null; @@ -771,6 +779,52 @@ imageBtn.addEventListener("touchend", function (event) { imageInput.click(); }); +// Обработчик для кнопки предпросмотра +previewBtn.addEventListener("click", function () { + togglePreview(); +}); + +// Функция переключения режима предпросмотра +function togglePreview() { + isPreviewMode = !isPreviewMode; + + if (isPreviewMode) { + // Показываем предпросмотр + noteInput.style.display = "none"; + notePreviewContainer.style.display = "block"; + + // Получаем содержимое и рендерим его + const content = noteInput.value; + if (content.trim()) { + // Парсим markdown и делаем теги кликабельными + const htmlContent = marked.parse(content); + const contentWithTags = makeTagsClickable(htmlContent); + notePreviewContent.innerHTML = contentWithTags; + + // Инициализируем lazy loading для изображений в превью + setTimeout(() => { + initLazyLoading(); + }, 0); + } else { + notePreviewContent.innerHTML = + '

Нет содержимого для предпросмотра

'; + } + + // Меняем иконку кнопки + previewBtn.innerHTML = + ''; + previewBtn.title = "Закрыть предпросмотр"; + } else { + // Возвращаемся к редактированию + noteInput.style.display = "block"; + notePreviewContainer.style.display = "none"; + + // Меняем иконку обратно + previewBtn.innerHTML = ''; + previewBtn.title = "Предпросмотр"; + } +} + // Обработчик выбора файлов imageInput.addEventListener("change", function (event) { const files = Array.from(event.target.files); @@ -1278,14 +1332,24 @@ async function renderNotes(notes) {
${dateDisplay}${pinIndicator}
-
- +
+
-
+
-
Ред.
-
Удал.
+
Ред.
+
Удал.
{ btn.addEventListener("click", async function (event) { const noteId = event.target.closest("#archiveBtn").dataset.id; - if (confirm("Архивировать эту заметку? Её можно будет восстановить из настроек.")) { + if ( + confirm( + "Архивировать эту заметку? Её можно будет восстановить из настроек." + ) + ) { try { const response = await fetch(`/api/notes/${noteId}/archive`, { method: "PUT", @@ -1476,6 +1544,7 @@ function addNoteEventListeners() { tag: "- [ ] ", }, { id: "editImageBtn", icon: "mdi:image-plus", tag: "image" }, + { id: "editPreviewBtn", icon: "mdi:eye", tag: "preview" }, ]; markdownButtons.forEach((button) => { @@ -1741,6 +1810,13 @@ function addNoteEventListeners() { const saveEditNote = async function () { if (textarea.value.trim() !== "" || editSelectedImages.length > 0) { try { + // Сбрасываем режим предпросмотра перед сохранением + if (isEditPreviewMode) { + isEditPreviewMode = false; + textarea.style.display = "block"; + editPreviewContainer.style.display = "none"; + } + const response = await fetch(`/api/notes/${noteId}`, { method: "PUT", headers: { @@ -1780,6 +1856,13 @@ function addNoteEventListeners() { if (!ok) return; } + // Сбрасываем режим предпросмотра + if (isEditPreviewMode) { + isEditPreviewMode = false; + textarea.style.display = "block"; + editPreviewContainer.style.display = "none"; + } + // Очистить выбранные новые изображения и превью editSelectedImages.length = 0; if (editImagePreviewContainer) { @@ -1829,10 +1912,29 @@ function addNoteEventListeners() { editImageInput.value = ""; }); + // Создаем контейнер для предпросмотра в режиме редактирования + const editPreviewContainer = document.createElement("div"); + editPreviewContainer.classList.add("note-preview-container"); + editPreviewContainer.style.display = "none"; + + const editPreviewHeader = document.createElement("div"); + editPreviewHeader.classList.add("note-preview-header"); + editPreviewHeader.innerHTML = "Предпросмотр:"; + + const editPreviewContent = document.createElement("div"); + editPreviewContent.classList.add("note-preview-content"); + + editPreviewContainer.appendChild(editPreviewHeader); + editPreviewContainer.appendChild(editPreviewContent); + + // Флаг для режима предпросмотра редактирования + let isEditPreviewMode = false; + // Очищаем текущий контент и вставляем markdown кнопки, textarea, элементы для изображений и контейнер с кнопкой сохранить noteContent.innerHTML = ""; noteContent.appendChild(markdownButtonsContainer); noteContent.appendChild(textarea); + noteContent.appendChild(editPreviewContainer); noteContent.appendChild(editImageInput); noteContent.appendChild(editImagePreviewContainer); noteContent.appendChild(saveButtonContainer); @@ -1853,6 +1955,46 @@ function addNoteEventListeners() { } else if (button.tag === "color") { // Для кнопки цвета открываем диалог выбора цвета insertColorTagForEdit(textarea); + } else if (button.tag === "preview") { + // Для кнопки предпросмотра переключаем режим + isEditPreviewMode = !isEditPreviewMode; + + if (isEditPreviewMode) { + // Показываем предпросмотр + textarea.style.display = "none"; + editPreviewContainer.style.display = "block"; + + // Получаем содержимое и рендерим его + const content = textarea.value; + if (content.trim()) { + // Парсим markdown и делаем теги кликабельными + const htmlContent = marked.parse(content); + const contentWithTags = makeTagsClickable(htmlContent); + editPreviewContent.innerHTML = contentWithTags; + + // Инициализируем lazy loading для изображений в превью + setTimeout(() => { + initLazyLoading(); + }, 0); + } else { + editPreviewContent.innerHTML = + '

Нет содержимого для предпросмотра

'; + } + + // Меняем иконку кнопки + btn.innerHTML = + ''; + btn.title = "Закрыть предпросмотр"; + } else { + // Возвращаемся к редактированию + textarea.style.display = "block"; + editPreviewContainer.style.display = "none"; + + // Меняем иконку обратно + btn.innerHTML = + ''; + btn.title = "Предпросмотр"; + } } else { insertMarkdownForEdit(textarea, button.tag); } @@ -2163,6 +2305,17 @@ async function saveNote() { selectedImages = []; updateImagePreview(); imageInput.value = ""; + + // Сбрасываем режим предпросмотра, если он был активен + if (isPreviewMode) { + isPreviewMode = false; + noteInput.style.display = "block"; + notePreviewContainer.style.display = "none"; + previewBtn.innerHTML = + ''; + previewBtn.title = "Предпросмотр"; + } + await loadNotes(true); // Показываем уведомление об успешном сохранении @@ -2407,8 +2560,12 @@ async function loadUserInfo() { if (response.ok) { const user = await response.json(); const userAvatar = document.getElementById("user-avatar"); - const userAvatarContainer = document.getElementById("user-avatar-container"); - const userAvatarPlaceholder = document.getElementById("user-avatar-placeholder"); + const userAvatarContainer = document.getElementById( + "user-avatar-container" + ); + const userAvatarPlaceholder = document.getElementById( + "user-avatar-placeholder" + ); // Показываем аватарку или плейсхолдер if (user.avatar) { diff --git a/public/notes.html b/public/notes.html index 3b55d3e..26cea2b 100644 --- a/public/notes.html +++ b/public/notes.html @@ -222,41 +222,36 @@ style="display: none" >
-
- - - -
- -
-
+
+ +
+
@@ -266,7 +261,11 @@ -
@@ -318,6 +324,9 @@ > +
+ + + @@ -126,54 +126,6 @@
-
- -
-
-
-
-
-
-
-
- -
- diff --git a/public/profile.js b/public/profile.js index 33edbc5..c3880f9 100644 --- a/public/profile.js +++ b/public/profile.js @@ -11,7 +11,6 @@ const newPasswordInput = document.getElementById("newPassword"); const confirmPasswordInput = document.getElementById("confirmPassword"); const changePasswordBtn = document.getElementById("changePasswordBtn"); const messageContainer = document.getElementById("messageContainer"); -const accentColorInput = document.getElementById("accentColor"); // Lazy loading для изображений function initLazyLoading() { @@ -78,12 +77,8 @@ async function loadProfile() { // Заполняем поля usernameInput.value = user.username || ""; emailInput.value = user.email || ""; - accentColorInput.value = user.accent_color || "#007bff"; - // Обновляем выбранный цвет в цветовых опциях - updateColorPickerSelection(user.accent_color || "#007bff"); - - // Применяем цветовой акцент пользователя + // Применяем цветовой акцент пользователя (для отображения) const accentColor = user.accent_color || "#007bff"; document.documentElement.style.setProperty("--accent-color", accentColor); @@ -104,17 +99,6 @@ async function loadProfile() { } } -// Функция для обновления выбора цвета в цветовой палитре -function updateColorPickerSelection(selectedColor) { - const colorOptions = document.querySelectorAll(".color-option"); - colorOptions.forEach((option) => { - option.classList.remove("selected"); - if (option.dataset.color === selectedColor) { - option.classList.add("selected"); - } - }); -} - // Обработчик загрузки аватарки avatarInput.addEventListener("change", async function (event) { const file = event.target.files[0]; @@ -200,7 +184,6 @@ deleteAvatarBtn.addEventListener("click", async function () { updateProfileBtn.addEventListener("click", async function () { const username = usernameInput.value.trim(); const email = emailInput.value.trim(); - const accentColor = accentColorInput.value; // Валидация if (!username) { @@ -227,7 +210,6 @@ updateProfileBtn.addEventListener("click", async function () { body: JSON.stringify({ username, email: email || null, - accent_color: accentColor, }), }); @@ -238,9 +220,6 @@ updateProfileBtn.addEventListener("click", async function () { const result = await response.json(); - // Применяем новый цветовой акцент - document.documentElement.style.setProperty("--accent-color", accentColor); - showMessage(result.message || "Профиль успешно обновлен", "success"); } catch (error) { console.error("Ошибка обновления профиля:", error); @@ -361,23 +340,6 @@ function setupLogoutHandler() { }); } -// Обработчики для цветовой палитры -function setupColorPicker() { - const colorOptions = document.querySelectorAll(".color-option"); - colorOptions.forEach((option) => { - option.addEventListener("click", function () { - const selectedColor = this.dataset.color; - accentColorInput.value = selectedColor; - updateColorPickerSelection(selectedColor); - }); - }); - - // Обработчик для input color - accentColorInput.addEventListener("input", function () { - updateColorPickerSelection(this.value); - }); -} - // Загружаем профиль при загрузке страницы document.addEventListener("DOMContentLoaded", function () { // Проверяем аутентификацию при загрузке страницы @@ -387,9 +349,6 @@ document.addEventListener("DOMContentLoaded", function () { // Инициализируем lazy loading для изображений initLazyLoading(); - // Настраиваем цветовую палитру - setupColorPicker(); - // Добавляем обработчик для кнопки выхода setupLogoutHandler(); }); diff --git a/public/settings.html b/public/settings.html index b7e0db2..cb5a784 100644 --- a/public/settings.html +++ b/public/settings.html @@ -54,22 +54,23 @@
- Настройки + Настройки
- + +
+ -
+

Архивные заметки

Архивированные заметки можно восстановить или удалить окончательно @@ -93,7 +151,7 @@

История действий

- +
- -
+
+ @@ -153,5 +219,3 @@ - - diff --git a/public/settings.js b/public/settings.js index f5e40e0..e951d5a 100644 --- a/public/settings.js +++ b/public/settings.js @@ -3,6 +3,12 @@ let logsOffset = 0; const logsLimit = 50; let hasMoreLogs = true; +// DOM элементы для внешнего вида +const settingsAccentColorInput = document.getElementById( + "settings-accentColor" +); +const updateAppearanceBtn = document.getElementById("updateAppearanceBtn"); + // Проверка аутентификации async function checkAuthentication() { try { @@ -27,19 +33,30 @@ async function checkAuthentication() { } } -// Загрузка информации о пользователе для применения accent color +// Загрузка информации о пользователе для применения accent color и заполнения формы async function loadUserInfo() { try { const response = await fetch("/api/user"); if (response.ok) { const user = await response.json(); const accentColor = user.accent_color || "#007bff"; + + // Применяем цветовой акцент if ( getComputedStyle(document.documentElement) .getPropertyValue("--accent-color") .trim() !== accentColor ) { - document.documentElement.style.setProperty("--accent-color", accentColor); + document.documentElement.style.setProperty( + "--accent-color", + accentColor + ); + } + + // Заполняем поле цветового акцента в настройках + if (settingsAccentColorInput) { + settingsAccentColorInput.value = accentColor; + updateColorPickerSelection(accentColor); } } } catch (error) { @@ -47,6 +64,33 @@ async function loadUserInfo() { } } +// Функция для обновления выбора цвета в цветовой палитре +function updateColorPickerSelection(selectedColor) { + const colorOptions = document.querySelectorAll( + "#appearance-tab .color-option" + ); + colorOptions.forEach((option) => { + option.classList.remove("selected"); + if (option.dataset.color === selectedColor) { + option.classList.add("selected"); + } + }); +} + +// Функция для показа сообщений +function showSettingsMessage(message, type = "success") { + const container = document.getElementById("settings-message-container"); + if (container) { + container.textContent = message; + container.className = `message-container ${type}`; + container.style.display = "block"; + + setTimeout(() => { + container.style.display = "none"; + }, 5000); + } +} + // Переключение табов function initTabs() { const tabs = document.querySelectorAll(".settings-tab"); @@ -69,6 +113,8 @@ function initTabs() { loadArchivedNotes(); } else if (tabName === "logs") { loadLogs(true); + } else if (tabName === "appearance") { + // Данные внешнего вида уже загружены в loadUserInfo() } }); }); @@ -77,7 +123,8 @@ function initTabs() { // Загрузка архивных заметок async function loadArchivedNotes() { const container = document.getElementById("archived-notes-container"); - container.innerHTML = '

Загрузка...

'; + container.innerHTML = + '

Загрузка...

'; try { const response = await fetch("/api/notes/archived"); @@ -112,7 +159,8 @@ async function loadArchivedNotes() { // Преобразуем markdown в HTML для предпросмотра const htmlContent = marked.parse(note.content); - const preview = htmlContent.substring(0, 200) + (htmlContent.length > 200 ? "..." : ""); + const preview = + htmlContent.substring(0, 200) + (htmlContent.length > 200 ? "..." : ""); // Изображения let imagesHtml = ""; @@ -165,9 +213,7 @@ function addArchivedNotesEventListeners() { } // Удаляем элемент из списка - document - .querySelector(`[data-note-id="${noteId}"]`) - ?.remove(); + document.querySelector(`[data-note-id="${noteId}"]`)?.remove(); // Проверяем, остались ли заметки const container = document.getElementById("archived-notes-container"); @@ -190,9 +236,7 @@ function addArchivedNotesEventListeners() { btn.addEventListener("click", async (e) => { const noteId = e.target.closest("button").dataset.id; if ( - confirm( - "Удалить эту заметку НАВСЕГДА? Это действие нельзя отменить!" - ) + confirm("Удалить эту заметку НАВСЕГДА? Это действие нельзя отменить!") ) { try { const response = await fetch(`/api/notes/archived/${noteId}`, { @@ -204,9 +248,7 @@ function addArchivedNotesEventListeners() { } // Удаляем элемент из списка - document - .querySelector(`[data-note-id="${noteId}"]`) - ?.remove(); + document.querySelector(`[data-note-id="${noteId}"]`)?.remove(); // Проверяем, остались ли заметки const container = document.getElementById("archived-notes-container"); @@ -237,7 +279,8 @@ async function loadLogs(reset = false) { const filterValue = document.getElementById("logTypeFilter").value; if (reset) { - tbody.innerHTML = 'Загрузка...'; + tbody.innerHTML = + 'Загрузка...'; } try { @@ -270,7 +313,7 @@ async function loadLogs(reset = false) { logs.forEach((log) => { const row = document.createElement("tr"); - + // Форматируем дату const created = new Date(log.created_at.replace(" ", "T") + "Z"); const dateStr = new Intl.DateTimeFormat("ru-RU", { @@ -301,7 +344,9 @@ async function loadLogs(reset = false) { row.innerHTML = ` ${dateStr} - ${actionText} + ${actionText} ${log.details || "-"} ${log.ip_address || "-"} `; @@ -337,9 +382,6 @@ document.addEventListener("DOMContentLoaded", async function () { // Инициализируем табы initTabs(); - // Загружаем архивные заметки по умолчанию - loadArchivedNotes(); - // Обработчик фильтра логов document.getElementById("logTypeFilter").addEventListener("change", () => { loadLogs(true); @@ -354,6 +396,67 @@ document.addEventListener("DOMContentLoaded", async function () { document.getElementById("loadMoreLogsBtn").addEventListener("click", () => { loadLogs(false); }); + + // Обработчик кнопки сохранения внешнего вида + if (updateAppearanceBtn) { + updateAppearanceBtn.addEventListener("click", async function () { + const accentColor = settingsAccentColorInput.value; + + try { + const response = await fetch("/api/user/profile", { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + accent_color: accentColor, + }), + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error || "Ошибка обновления цветового акцента"); + } + + const result = await response.json(); + + // Применяем новый цветовой акцент + document.documentElement.style.setProperty( + "--accent-color", + accentColor + ); + + showSettingsMessage( + result.message || "Цветовой акцент успешно обновлен", + "success" + ); + } catch (error) { + console.error("Ошибка обновления цветового акцента:", error); + showSettingsMessage(error.message, "error"); + } + }); + } + + // Обработчики для цветовой палитры + function setupAppearanceColorPicker() { + const colorOptions = document.querySelectorAll( + "#appearance-tab .color-option" + ); + colorOptions.forEach((option) => { + option.addEventListener("click", function () { + const selectedColor = this.dataset.color; + settingsAccentColorInput.value = selectedColor; + updateColorPickerSelection(selectedColor); + }); + }); + + // Обработчик для input color + if (settingsAccentColorInput) { + settingsAccentColorInput.addEventListener("input", function () { + updateColorPickerSelection(this.value); + }); + } + } + + setupAppearanceColorPicker(); }); - - diff --git a/public/style.css b/public/style.css index 63f2ff7..751ceed 100644 --- a/public/style.css +++ b/public/style.css @@ -47,11 +47,7 @@ header .iconify { margin-right: 8px; } -/* Стили для иконок в кнопках */ -.logout-btn .iconify { - font-size: 14px; - margin-right: 6px; -} +/* Стили для иконок в кнопках удалены, так как теперь кнопки имеют свои стили */ /* Стили для иконок в секциях */ .search-title .iconify, @@ -82,10 +78,7 @@ header .iconify[data-icon="mdi:account"], color: #9c27b0; } -/* Иконка выхода - красный */ -.logout-btn .iconify[data-icon="mdi:logout"] { - color: #f44336; -} +/* Цвет иконки выхода задается в стилях .logout-btn .iconify */ /* Иконка входа - синий */ header .iconify[data-icon="mdi:login"] { @@ -279,12 +272,19 @@ header { font-size: 24px; } +.user-avatar-placeholder-mini .iconify { + margin: 0; + padding: 0; + line-height: 1; + vertical-align: baseline; +} + /* Иконка настроек */ .settings-icon-btn { width: 40px; height: 40px; border-radius: 50%; - border: 2px solid #ddd; + border: none; background: white; cursor: pointer; display: flex; @@ -296,7 +296,6 @@ header { .settings-icon-btn:hover { background: var(--accent-color, #007bff); - border-color: var(--accent-color, #007bff); color: white; transform: rotate(45deg); } @@ -305,6 +304,10 @@ header { font-size: 20px; color: #666; transition: color 0.3s ease; + margin: 0; + padding: 0; + line-height: 1; + vertical-align: baseline; } .settings-icon-btn:hover .iconify { @@ -312,18 +315,34 @@ header { } .logout-btn { - padding: 8px 16px; + width: 40px; + height: 40px; + border-radius: 50%; + border: none; + background: white; cursor: pointer; - border: 1px solid #ddd; - background-color: #f8f9fa; - border-radius: 5px; - font-size: 14px; - color: #dc3545; + display: flex; + align-items: center; + justify-content: center; transition: all 0.3s ease; + flex-shrink: 0; } .logout-btn:hover { background-color: #dc3545; +} + +.logout-btn .iconify { + font-size: 20px; + color: #dc3545; + transition: color 0.3s ease; + margin: 0; + padding: 0; + line-height: 1; + vertical-align: baseline; +} + +.logout-btn:hover .iconify { color: white; } @@ -929,26 +948,42 @@ textarea:focus { } .message-container { - margin-top: 20px; - margin-bottom: 80px; /* Отступ снизу, чтобы контент не обрезался футером */ - padding: 10px; - border-radius: 5px; + position: fixed; + top: 20px; + left: 50%; + transform: translateX(-50%); + padding: 12px 20px; + border-radius: 8px; text-align: center; display: none; + z-index: 10000; + min-width: 300px; + max-width: 500px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + font-weight: 500; +} + +/* Адаптивность для мобильных устройств */ +@media (max-width: 768px) { + .message-container { + min-width: 90vw; + max-width: 90vw; + left: 5vw; + transform: none; + padding: 10px 15px; + } } .message-container.success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; - display: block; } .message-container.error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; - display: block; } .back-btn { @@ -1508,20 +1543,21 @@ textarea:focus { /* Адаптируем заголовок заметок */ .notes-header { - flex-direction: column; - align-items: flex-start; + flex-direction: row; + align-items: center; gap: 10px; width: 100%; } .notes-header-left { - width: 100%; + flex: 1; + min-width: 0; } .user-info { - width: 100%; - justify-content: space-between; - flex-wrap: wrap; + flex-shrink: 0; + justify-content: flex-end; + flex-wrap: nowrap; gap: 10px; } @@ -1585,6 +1621,85 @@ textarea:focus { color: #495057; } +/* Стили для контейнера предпросмотра заметки */ +.note-preview-container { + margin: 15px 0; + padding: 15px; + background: #f8f9fa; + border: 2px solid #007bff; + border-radius: 8px; + min-height: 200px; + max-height: 600px; + overflow-y: auto; +} + +.note-preview-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; + padding-bottom: 10px; + border-bottom: 2px solid #dee2e6; + font-weight: 600; + color: #007bff; + font-size: 16px; +} + +.note-preview-content { + background: white; + padding: 15px; + border-radius: 6px; + min-height: 150px; +} + +.note-preview-content h1, +.note-preview-content h2, +.note-preview-content h3, +.note-preview-content h4, +.note-preview-content h5, +.note-preview-content h6 { + margin-top: 0.5em; + margin-bottom: 0.5em; +} + +.note-preview-content p { + margin: 0.5em 0; +} + +.note-preview-content ul, +.note-preview-content ol { + margin: 0.5em 0; + padding-left: 2em; +} + +.note-preview-content code { + background: #f8f9fa; + padding: 2px 6px; + border-radius: 4px; + font-family: "Courier New", monospace; +} + +.note-preview-content pre { + background: #f8f9fa; + padding: 10px; + border-radius: 6px; + overflow-x: auto; +} + +.note-preview-content blockquote { + border-left: 4px solid #007bff; + padding-left: 15px; + margin: 10px 0; + color: #495057; +} + +.note-preview-content img { + max-width: 100%; + height: auto; + border-radius: 6px; + margin: 10px 0; +} + .clear-images-btn { background: #dc3545; color: white;