Отзывы и предложения к софту от AleXStam
Поговорим о...
<!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: Arial, sans-serif;
background-color: #fff;
padding: 10px;
font-size: 11px;
}

.container {
max-width: 100%;
margin: 0 auto;
}

h1 {
text-align: center;
color: #333;
font-size: 16px;
margin-bottom: 15px;
font-weight: bold;
}

.search-container {
margin: 15px 0;
text-align: center;
}

.search-input {
width: 300px;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}

.search-input:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}

.controls-row {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
margin: 12px 0;
padding: 8px;
background: #f0f0f0;
border-radius: 4px;
border: 1px solid #ddd;
}

.stats {
font-size: 12px;
}

.group-select {
padding: 6px 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 12px;
background: white;
}

.show-groups-toggle {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
order: 2; /* Перемещаем вправо */
}

.toggle-checkbox {
width: 16px;
height: 16px;
}

.comparison-container {
display: flex;
flex-wrap: wrap;
gap: 15px;
align-items: flex-start;
}

.group-section {
flex: 1;
min-width: 350px;
margin-bottom: 15px;
}

.group-header {
background: #f8f9fa;
padding: 6px 10px;
border-radius: 4px;
margin-bottom: 8px;
font-weight: bold;
color: #495057;
font-size: 13px;
}

.icon-column {
border: 1px solid #e0e0e0;
border-radius: 5px;
padding: 8px;
background: #fafafa;
min-height: 100px;
}

.column-header {
display: grid;
grid-template-columns: 32px 20px 32px 1fr;
gap: 8px;
align-items: center;
padding: 5px 3px;
margin-bottom: 5px;
border-bottom: 1px solid #ddd;
font-weight: bold;
text-align: center;
}

.header-label {
font-size: 11px;
color: #333;
}

.icon-row {
display: grid;
grid-template-columns: 32px 20px 32px 1fr;
gap: 8px;
align-items: center;
padding: 6px 3px;
border-bottom: 1px solid #f0f0f0;
}

.icon-row:last-child {
border-bottom: none;
}

.icon-image {
width: 32px;
height: 32px;
object-fit: contain;
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
image-rendering: pixelated;
}

.bmp-processed {
/* Стиль для обработанных BMP с прозрачностью */
filter: none;
}

.arrow {
text-align: center;
color: #999;
font-size: 14px;
font-weight: bold;
}

.icon-info {
padding-left: 8px;
padding-right: 5px;
min-width: 0;
}

.icon-name {
color: #333;
line-height: 1.3;
word-wrap: break-word;
overflow-wrap: break-word;
hyphens: auto;
text-align: left;
}

.loading {
text-align: center;
padding: 20px;
color: #666;
width: 100%;
}

.no-results {
text-align: center;
padding: 40px;
color: #666;
width: 100%;
font-size: 14px;
}
</style>
</head>
<body>
<div class="container">
<h1>Сравнение иконок ИСИХОГИ старой и новой версий</h1>

<div class="search-container">
<input type="text"
id="searchInput"
class="search-input"
placeholder="Поиск по названию иконки...">
</div>

<div class="controls-row">
<div class="stats">
Всего иконок для сравнения: <span id="totalCount">0</span> |
Отображено: <span id="shownCount">0</span>
</div>
<select id="groupSelect" class="group-select" style="display: none;">
<option value="">Все группы</option>
</select>
<div class="show-groups-toggle">
<input type="checkbox" id="showGroupsToggle" class="toggle-checkbox" checked>
<label for="showGroupsToggle">Показывать группы</label>
</div>
</div>

<div class="comparison-container" id="iconsContainer">
<div class="loading">Загрузка иконок...</div>
</div>
</div>

<script>
let allIcons = [];
let filteredIcons = [];
let availableGroups = [];
let showGroups = true;

// Улучшенная функция для обработки BMP с розовым фоном
function processBMPWithPinkBackground(img) {
return new Promise((resolve) => {
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;

ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;

// Удаляем только чистый розовый (255,0,255) - без допусков
for (let i = 0; i < data.length; i += 4) {
if (data[i] === 255 && data[i + 1] === 0 && data[i + 2] === 255) {
data[i + 3] = 0; // Устанавливаем полную прозрачность
}
}

ctx.putImageData(imageData, 0, 0);

const processedImg = new Image();
processedImg.onload = () => resolve({
element: processedImg,
processed: true
});
processedImg.src = canvas.toDataURL('image/png');
} catch (error) {
console.warn('Ошибка обработки BMP:', error);
resolve({
element: img,
processed: false
});
}
});
}

// Функция для загрузки изображения с обработкой
function loadImageWithBMPProcessing(src, alt) {
return new Promise((resolve) => {
const img = new Image();

img.onload = async function() {
try {
// Для BMP файлов используем специальную обработку
if (src.toLowerCase().endsWith('.bmp')) {
const result = await processBMPWithPinkBackground(img);
result.element.alt = alt;
resolve(result);
} else {
// Для других форматов используем как есть
img.alt = alt;
resolve({ element: img, processed: false });
}
} catch (error) {
console.warn('Ошибка обработки изображения:', error, src);
img.alt = alt;
resolve({ element: img, processed: false });
}
};

img.onerror = function() {
console.warn('Ошибка загрузки изображения:', src);
// Заглушка для отсутствующего изображения
const placeholder = new Image();
placeholder.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSIjRjVGNUY1Ii8+CjxwYXRoIGQ9Ik0xNiAxOEMxNi41NTIzIDE4IDE3IDE3LjU1MjMgMTcgMTdWMTFDMTcgMTAuNDQ3NyAxNi41NTIzIDEwIDE2IDEwQzE1LjQ0NzcgMTAgMTUgMTAuNDQ3NyAxNSAxMVYxN0MxNSAxNy41NTIzIDE1LjQ0NzcgMTggMTYgMThaIiBmaWxsPSIjOTk5OTk5Ii8+CjxwYXRoIGQ9Ik0xNiAyMkMxNi41NTIzIDIyIDE3IDIxLjU1MjMgMTcgMjFDMTcgMjAuNDQ3NyAxNi41NTIzIDIwIDE2IDIwQzE1LjQ0NzcgMjAgMTUgMjAuNDQ3NyAxNSAyMUMxNSAyMS41NTIzIDE1LjQ0NzcgMjIgMTYgMjJaIiBmaWxsPSIjOTk5OTk5Ii8+Cjwvc3ZnPgo=';
placeholder.alt = alt;
resolve({ element: placeholder, processed: false });
};

// Пробуем загрузить изображение
img.src = src;
});
}

// Функция для группировки иконок
function groupIcons(icons) {
const groups = {};
const noGroup = [];

icons.forEach(icon => {
if (icon.group && icon.group.trim() !== '') {
if (!groups[icon.group]) {
groups[icon.group] = [];
}
groups[icon.group].push(icon);
} else {
noGroup.push(icon);
}
});

return { groups, noGroup };
}

// Функция для создания секции группы (улучшенная - без разбивки на колонки внутри группы)
function createGroupSection(groupName, icons) {
const section = document.createElement('div');
section.className = 'group-section';

const header = document.createElement('div');
header.className = 'group-header';
header.textContent = groupName;
section.appendChild(header);

// Создаем одну колонку для всей группы
const column = createIconColumn(icons);
section.appendChild(column);

return section;
}

// Функция для создания колонки с иконками
function createIconColumn(icons) {
const column = document.createElement('div');
column.className = 'icon-column';

// Заголовок колонки
const header = document.createElement('div');
header.className = 'column-header';

const beforeLabel = document.createElement('div');
beforeLabel.className = 'header-label';
beforeLabel.textContent = 'ДО';

const arrowSpace = document.createElement('div');
arrowSpace.className = 'arrow';
arrowSpace.textContent = '→';

const afterLabel = document.createElement('div');
afterLabel.className = 'header-label';
afterLabel.textContent = 'ПОСЛЕ';

const nameLabel = document.createElement('div');
nameLabel.className = 'header-label';
nameLabel.textContent = 'Название';
nameLabel.style.textAlign = 'center';

header.appendChild(beforeLabel);
header.appendChild(arrowSpace);
header.appendChild(afterLabel);
header.appendChild(nameLabel);

column.appendChild(header);

// Добавляем иконки в колонку
icons.forEach(icon => {
column.appendChild(createIconRow(icon));
});

return column;
}

// Функция для создания строки с иконками
function createIconRow(icon) {
const row = document.createElement('div');
row.className = 'icon-row';

const beforeContainer = document.createElement('div');
const afterContainer = document.createElement('div');

// Загружаем и обрабатываем изображения
Promise.all([
loadImageWithBMPProcessing(icon.before, `${icon.name} (до)`),
loadImageWithBMPProcessing(icon.after, `${icon.name} (после)`)
]).then(([beforeResult, afterResult]) => {
const beforeImg = beforeResult.element;
const afterImg = afterResult.element;

beforeImg.className = 'icon-image' + (beforeResult.processed ? ' bmp-processed' : '');
afterImg.className = 'icon-image' + (afterResult.processed ? ' bmp-processed' : '');

beforeContainer.appendChild(beforeImg);
afterContainer.appendChild(afterImg);
});

const arrow = document.createElement('div');
arrow.className = 'arrow';
arrow.textContent = '→';

const infoDiv = document.createElement('div');
infoDiv.className = 'icon-info';

const name = document.createElement('div');
name.className = 'icon-name';
name.textContent = icon.name;
name.title = icon.name;

infoDiv.appendChild(name);

row.appendChild(beforeContainer);
row.appendChild(arrow);
row.appendChild(afterContainer);
row.appendChild(infoDiv);

return row;
}

// Функция для отображения иконок с группировкой (улучшенная)
function renderIconsWithGroups() {
const container = document.getElementById('iconsContainer');
container.innerHTML = '';

if (filteredIcons.length === 0) {
container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>';
updateStats();
return;
}

// Группируем иконки
const { groups, noGroup } = groupIcons(filteredIcons);

// Отображаем группы (не сортируем по алфавиту, сохраняем порядок из данных)
Object.keys(groups).forEach(groupName => {
const section = createGroupSection(groupName, groups[groupName]);
container.appendChild(section);
});

// Затем иконки без группы
if (noGroup.length > 0) {
const noGroupSection = document.createElement('div');
noGroupSection.className = 'group-section';

const header = document.createElement('div');
header.className = 'group-header';
header.textContent = 'Прочие иконки';
noGroupSection.appendChild(header);

const column = createIconColumn(noGroup);
noGroupSection.appendChild(column);

container.appendChild(noGroupSection);
}

updateStats();
}

// Функция для отображения иконок без группировки (улучшенная сортировка)
function renderIconsWithoutGroups() {
const container = document.getElementById('iconsContainer');
container.innerHTML = '';

if (filteredIcons.length === 0) {
container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>';
updateStats();
return;
}

// Сортируем: сначала по группам, потом по алфавиту внутри групп
const sortedIcons = [...filteredIcons].sort((a, b) => {
// Сначала сравниваем группы
const groupA = a.group || 'zzz'; // Без группы в конец
const groupB = b.group || 'zzz';

if (groupA !== groupB) {
return groupA.localeCompare(groupB);
}

// Если группы одинаковые, сравниваем по имени
return a.name.localeCompare(b.name);
});

// Разбиваем на колонки по 20 иконок в каждой
const itemsPerColumn = 20;
const columnCount = Math.ceil(sortedIcons.length / itemsPerColumn);

for (let i = 0; i < columnCount; i++) {
const startIndex = i * itemsPerColumn;
const endIndex = startIndex + itemsPerColumn;
const columnIcons = sortedIcons.slice(startIndex, endIndex);
const column = createIconColumn(columnIcons);
container.appendChild(column);
}

updateStats();
}

// Функция для фильтрации иконок по поисковому запросу и группе
function filterIcons(searchTerm, selectedGroup = '') {
let filtered = [...allIcons];

// Фильтрация по поисковому запросу
if (searchTerm) {
const term = searchTerm.toLowerCase();
filtered = filtered.filter(icon =>
icon.name.toLowerCase().includes(term) ||
(icon.group && icon.group.toLowerCase().includes(term))
);
}

// Фильтрация по группе
if (selectedGroup) {
filtered = filtered.filter(icon => icon.group === selectedGroup);
}

filteredIcons = filtered;
renderFilteredIcons();
}

// Функция для отображения отфильтрованных иконок
function renderFilteredIcons() {
if (showGroups) {
renderIconsWithGroups();
} else {
renderIconsWithoutGroups();
}
}

// Обновление статистики
function updateStats() {
document.getElementById('totalCount').textContent = allIcons.length;
document.getElementById('shownCount').textContent = filteredIcons.length;
}

// Инициализация комбобокса групп
function initGroupSelect(groups) {
const groupSelect = document.getElementById('groupSelect');
groupSelect.innerHTML = '<option value="">Все группы</option>';

groups.forEach(group => {
const option = document.createElement('option');
option.value = group;
option.textContent = group;
groupSelect.appendChild(option);
});

groupSelect.style.display = groups.length > 0 ? 'block' : 'none';

groupSelect.addEventListener('change', function() {
const searchTerm = document.getElementById('searchInput').value;
filterIcons(searchTerm, this.value);
});
}

// Функция для отображения всех иконок
function renderIcons(images, groups) {
allIcons = images;
filteredIcons = [...allIcons];
availableGroups = groups;

initGroupSelect(groups);
renderFilteredIcons();
}

// Загружаем данные из внешнего файла
async function loadIcons() {
try {
const script = document.createElement('script');
script.src = 'image_list.js';
document.head.appendChild(script);

script.onload = function() {
try {
const data = loadBeforeAfterImages();

if (data && data.images) {
const processedImages = data.images.map(icon => {
const displayName = icon.name.replace(/^32x32_/, '');
return {
...icon,
name: displayName
};
});

renderIcons(processedImages, data.availableGroups || []);
} else {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки данных</div>';
}
} catch (error) {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка обработки данных</div>';
}
};

script.onerror = function() {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки файла image_list.js</div>';
};

} catch (error) {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки иконок</div>';
}
}

// Обработчик поиска
function setupSearch() {
const searchInput = document.getElementById('searchInput');
let searchTimeout;

searchInput.addEventListener('input', function(e) {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
const selectedGroup = document.getElementById('groupSelect').value;
filterIcons(e.target.value, selectedGroup);
}, 300);
});

searchInput.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
this.value = '';
const selectedGroup = document.getElementById('groupSelect').value;
filterIcons('', selectedGroup);
}
});
}

// Обработчик переключения отображения групп
function setupGroupsToggle() {
const toggle = document.getElementById('showGroupsToggle');

toggle.addEventListener('change', function() {
showGroups = this.checked;
renderFilteredIcons();
});
}

// Загружаем иконки при загрузке страницы
window.addEventListener('DOMContentLoaded', function() {
loadIcons();
setupSearch();
setupGroupsToggle();
});
</script>
</body>
</html>
<!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: Arial, sans-serif;
background-color: #fff;
padding: 10px;
font-size: 11px;
}

.container {
max-width: 100%;
margin: 0 auto;
}

h1 {
text-align: center;
color: #333;
font-size: 16px;
margin-bottom: 15px;
font-weight: bold;
}

.search-container {
margin: 15px 0;
text-align: center;
}

.search-input {
width: 300px;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}

.search-input:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}

.controls-row {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
margin: 12px 0;
padding: 8px;
background: #f0f0f0;
border-radius: 4px;
border: 1px solid #ddd;
}

.stats {
font-size: 12px;
}

.group-select {
padding: 6px 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 12px;
background: white;
}

.show-groups-toggle {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
order: 2;
}

.toggle-checkbox {
width: 16px;
height: 16px;
}

.comparison-container {
display: flex;
flex-wrap: wrap;
gap: 15px;
align-items: flex-start;
}

.group-section {
flex: 1;
min-width: 350px;
max-width: 500px;
margin-bottom: 15px;
}

.group-header {
background: #f8f9fa;
padding: 6px 10px;
border-radius: 4px;
margin-bottom: 8px;
font-weight: bold;
color: #495057;
font-size: 13px;
}

.icon-column {
border: 1px solid #e0e0e0;
border-radius: 5px;
padding: 8px;
background: #fafafa;
}

.column-header {
display: grid;
grid-template-columns: 32px 20px 32px 1fr;
gap: 8px;
align-items: center;
padding: 5px 3px;
margin-bottom: 5px;
border-bottom: 1px solid #ddd;
font-weight: bold;
text-align: center;
}

.header-label {
font-size: 11px;
color: #333;
}

.icon-row {
display: grid;
grid-template-columns: 32px 20px 32px 1fr;
gap: 8px;
align-items: center;
padding: 6px 3px;
border-bottom: 1px solid #f0f0f0;
}

.icon-row:last-child {
border-bottom: none;
}

.icon-image {
width: 32px;
height: 32px;
object-fit: contain;
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
image-rendering: pixelated;
}

.bmp-processed {
filter: none;
}

.arrow {
text-align: center;
color: #999;
font-size: 14px;
font-weight: bold;
}

.icon-info {
padding-left: 8px;
padding-right: 5px;
min-width: 0;
}

.icon-name {
color: #333;
line-height: 1.3;
word-wrap: break-word;
overflow-wrap: break-word;
hyphens: auto;
text-align: left;
}

.loading {
text-align: center;
padding: 20px;
color: #666;
width: 100%;
}

.no-results {
text-align: center;
padding: 40px;
color: #666;
width: 100%;
font-size: 14px;
}
</style>
</head>
<body>
<div class="container">
<h1>Сравнение иконок ИСИХОГИ старой и новой версий</h1>

<div class="search-container">
<input type="text"
id="searchInput"
class="search-input"
placeholder="Поиск по названию иконки...">
</div>

<div class="controls-row">
<div class="stats">
Всего иконок для сравнения: <span id="totalCount">0</span> |
Отображено: <span id="shownCount">0</span>
</div>
<select id="groupSelect" class="group-select" style="display: none;">
<option value="">Все группы</option>
</select>
<div class="show-groups-toggle">
<input type="checkbox" id="showGroupsToggle" class="toggle-checkbox" checked>
<label for="showGroupsToggle">Показывать группы</label>
</div>
</div>

<div class="comparison-container" id="iconsContainer">
<div class="loading">Загрузка иконок...</div>
</div>
</div>

<script>
let allIcons = [];
let filteredIcons = [];
let availableGroups = [];
let showGroups = true;

// Функция для расчета высоты группы (в строках)
function calculateGroupHeight(iconsCount) {
// Высота заголовка + высота строк иконок
const headerHeight = 40; // заголовок группы
const rowHeight = 40; // высота одной строки с иконкой
const padding = 16; // отступы
return headerHeight + (iconsCount * rowHeight) + padding;
}

// Алгоритм упорядочивания по высоте (как в Pinterest)
function arrangeGroups(groups) {
const groupEntries = Object.entries(groups);
if (groupEntries.length === 0) return [];

// Сортируем группы по количеству иконок (от больших к маленьким)
// Это помогает уменьшить пустоты
groupEntries.sort((a, b) => b[1].length - a[1].length);

const columns = [];
const result = [];

for (const [groupName, icons] of groupEntries) {
const groupHeight = calculateGroupHeight(icons.length);

if (columns.length === 0) {
// Первая группа
columns.push({ height: groupHeight, groups: [{ name: groupName, icons, height: groupHeight }] });
result.push({ name: groupName, icons, column: 0 });
} else {
// Ищем колонку с минимальной высотой
let minHeight = Infinity;
let minColumnIndex = 0;

for (let i = 0; i < columns.length; i++) {
if (columns[i].height < minHeight) {
minHeight = columns[i].height;
minColumnIndex = i;
}
}

// Добавляем группу в колонку с минимальной высотой
columns[minColumnIndex].height += groupHeight + 15; // + gap
columns[minColumnIndex].groups.push({ name: groupName, icons, height: groupHeight });
result.push({ name: groupName, icons, column: minColumnIndex });
}
}

return result;
}

// Функция для отображения иконок с группировкой (улучшенная компоновка)
function renderIconsWithGroups() {
const container = document.getElementById('iconsContainer');
container.innerHTML = '';

if (filteredIcons.length === 0) {
container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>';
updateStats();
return;
}

// Группируем иконки
const { groups, noGroup } = groupIcons(filteredIcons);

// Оптимально располагаем группы
const arrangedGroups = arrangeGroups(groups);

// Создаем колонки для отображения
const columnCount = Math.max(1, Math.floor(container.clientWidth / 400)); // Примерно по 400px на колонку
const columns = Array.from({ length: columnCount }, () => {
const col = document.createElement('div');
col.className = 'group-column';
col.style.flex = '1';
col.style.minWidth = '350px';
col.style.display = 'flex';
col.style.flexDirection = 'column';
col.style.gap = '15px';
return col;
});

// Распределяем группы по колонкам
arrangedGroups.forEach(group => {
const columnIndex = group.column % columnCount;
const section = createGroupSection(group.name, group.icons);
columns[columnIndex].appendChild(section);
});

// Добавляем иконки без группы в первую колонку
if (noGroup.length > 0) {
const noGroupSection = document.createElement('div');
noGroupSection.className = 'group-section';

const header = document.createElement('div');
header.className = 'group-header';
header.textContent = 'Прочие иконки';
noGroupSection.appendChild(header);

const column = createIconColumn(noGroup);
noGroupSection.appendChild(column);

// Добавляем в колонку с наименьшим количеством контента
let minHeightColumn = 0;
let minHeight = Infinity;

columns.forEach((col, index) => {
const height = col.scrollHeight;
if (height < minHeight) {
minHeight = height;
minHeightColumn = index;
}
});

columns[minHeightColumn].appendChild(noGroupSection);
}

// Очищаем контейнер и добавляем колонки
container.innerHTML = '';
columns.forEach(col => {
if (col.children.length > 0) {
container.appendChild(col);
}
});

updateStats();
}

// Функция для создания секции группы
function createGroupSection(groupName, icons) {
const section = document.createElement('div');
section.className = 'group-section';

const header = document.createElement('div');
header.className = 'group-header';
header.textContent = groupName;
section.appendChild(header);

const column = createIconColumn(icons);
section.appendChild(column);

return section;
}

// Остальные функции без изменений
function processBMPWithPinkBackground(img) {
return new Promise((resolve) => {
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;

ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;

for (let i = 0; i < data.length; i += 4) {
if (data[i] === 255 && data[i + 1] === 0 && data[i + 2] === 255) {
data[i + 3] = 0;
}
}

ctx.putImageData(imageData, 0, 0);

const processedImg = new Image();
processedImg.onload = () => resolve({
element: processedImg,
processed: true
});
processedImg.src = canvas.toDataURL('image/png');
} catch (error) {
console.warn('Ошибка обработки BMP:', error);
resolve({
element: img,
processed: false
});
}
});
}

function loadImageWithBMPProcessing(src, alt) {
return new Promise((resolve) => {
const img = new Image();

img.onload = async function() {
try {
if (src.toLowerCase().endsWith('.bmp')) {
const result = await processBMPWithPinkBackground(img);
result.element.alt = alt;
resolve(result);
} else {
img.alt = alt;
resolve({ element: img, processed: false });
}
} catch (error) {
console.warn('Ошибка обработки изображения:', error, src);
img.alt = alt;
resolve({ element: img, processed: false });
}
};

img.onerror = function() {
console.warn('Ошибка загрузки изображения:', src);
const placeholder = new Image();
placeholder.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSIjRjVGNUY1Ii8+CjxwYXRoIGQ9Ik0xNiAxOEMxNi41NTIzIDE4IDE3IDE3LjU1MjMgMTcgMTdWMTFDMTcgMTAuNDQ3NyAxNi41NTIzIDEwIDE2IDEwQzE1LjQ0NzcgMTAgMTUgMTAuNDQ3NyAxNSAxMVYxN0MxNSAxNy41NTIzIDE1LjQ0NzcgMTggMTYgMThaIiBmaWxsPSIjOTk5OTk5Ii8+CjxwYXRoIGQ9Ik0xNiAyMkMxNi41NTIzIDIyIDE3IDIxLjU1MjMgMTcgMjFDMTcgMjAuNDQ3NyAxNi41NTIzIDIwIDE2IDIwQzE1LjQ0NzcgMjAgMTUgMjAuNDQ3NyAxNSAyMUMxNSAyMS41NTIzIDE1LjQ0NzcgMjIgMTYgMjJaIiBmaWxsPSIjOTk5OTk5Ii8+Cjwvc3ZnPgo=';
placeholder.alt = alt;
resolve({ element: placeholder, processed: false });
};

img.src = src;
});
}

function groupIcons(icons) {
const groups = {};
const noGroup = [];

icons.forEach(icon => {
if (icon.group && icon.group.trim() !== '') {
if (!groups[icon.group]) {
groups[icon.group] = [];
}
groups[icon.group].push(icon);
} else {
noGroup.push(icon);
}
});

return { groups, noGroup };
}

function createIconColumn(icons) {
const column = document.createElement('div');
column.className = 'icon-column';

const header = document.createElement('div');
header.className = 'column-header';

const beforeLabel = document.createElement('div');
beforeLabel.className = 'header-label';
beforeLabel.textContent = 'ДО';

const arrowSpace = document.createElement('div');
arrowSpace.className = 'arrow';
arrowSpace.textContent = '→';

const afterLabel = document.createElement('div');
afterLabel.className = 'header-label';
afterLabel.textContent = 'ПОСЛЕ';

const nameLabel = document.createElement('div');
nameLabel.className = 'header-label';
nameLabel.textContent = 'Название';
nameLabel.style.textAlign = 'center';

header.appendChild(beforeLabel);
header.appendChild(arrowSpace);
header.appendChild(afterLabel);
header.appendChild(nameLabel);

column.appendChild(header);

icons.forEach(icon => {
column.appendChild(createIconRow(icon));
});

return column;
}

function createIconRow(icon) {
const row = document.createElement('div');
row.className = 'icon-row';

const beforeContainer = document.createElement('div');
const afterContainer = document.createElement('div');

Promise.all([
loadImageWithBMPProcessing(icon.before, `${icon.name} (до)`),
loadImageWithBMPProcessing(icon.after, `${icon.name} (после)`)
]).then(([beforeResult, afterResult]) => {
const beforeImg = beforeResult.element;
const afterImg = afterResult.element;

beforeImg.className = 'icon-image' + (beforeResult.processed ? ' bmp-processed' : '');
afterImg.className = 'icon-image' + (afterResult.processed ? ' bmp-processed' : '');

beforeContainer.appendChild(beforeImg);
afterContainer.appendChild(afterImg);
});

const arrow = document.createElement('div');
arrow.className = 'arrow';
arrow.textContent = '→';

const infoDiv = document.createElement('div');
infoDiv.className = 'icon-info';

const name = document.createElement('div');
name.className = 'icon-name';
name.textContent = icon.name;
name.title = icon.name;

infoDiv.appendChild(name);

row.appendChild(beforeContainer);
row.appendChild(arrow);
row.appendChild(afterContainer);
row.appendChild(infoDiv);

return row;
}

function renderIconsWithoutGroups() {
const container = document.getElementById('iconsContainer');
container.innerHTML = '';

if (filteredIcons.length === 0) {
container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>';
updateStats();
return;
}

const sortedIcons = [...filteredIcons].sort((a, b) => {
const groupA = a.group || 'zzz';
const groupB = b.group || 'zzz';

if (groupA !== groupB) {
return groupA.localeCompare(groupB);
}

return a.name.localeCompare(b.name);
});

const itemsPerColumn = 20;
const columnCount = Math.ceil(sortedIcons.length / itemsPerColumn);

for (let i = 0; i < columnCount; i++) {
const startIndex = i * itemsPerColumn;
const endIndex = startIndex + itemsPerColumn;
const columnIcons = sortedIcons.slice(startIndex, endIndex);
const column = createIconColumn(columnIcons);
container.appendChild(column);
}

updateStats();
}

function filterIcons(searchTerm, selectedGroup = '') {
let filtered = [...allIcons];

if (searchTerm) {
const term = searchTerm.toLowerCase();
filtered = filtered.filter(icon =>
icon.name.toLowerCase().includes(term) ||
(icon.group && icon.group.toLowerCase().includes(term))
);
}

if (selectedGroup) {
filtered = filtered.filter(icon => icon.group === selectedGroup);
}

filteredIcons = filtered;
renderFilteredIcons();
}

function renderFilteredIcons() {
if (showGroups) {
renderIconsWithGroups();
} else {
renderIconsWithoutGroups();
}
}

function updateStats() {
document.getElementById('totalCount').textContent = allIcons.length;
document.getElementById('shownCount').textContent = filteredIcons.length;
}

function initGroupSelect(groups) {
const groupSelect = document.getElementById('groupSelect');
groupSelect.innerHTML = '<option value="">Все группы</option>';

groups.forEach(group => {
const option = document.createElement('option');
option.value = group;
option.textContent = group;
groupSelect.appendChild(option);
});

groupSelect.style.display = groups.length > 0 ? 'block' : 'none';

groupSelect.addEventListener('change', function() {
const searchTerm = document.getElementById('searchInput').value;
filterIcons(searchTerm, this.value);
});
}

function renderIcons(images, groups) {
allIcons = images;
filteredIcons = [...allIcons];
availableGroups = groups;

initGroupSelect(groups);
renderFilteredIcons();
}

async function loadIcons() {
try {
const script = document.createElement('script');
script.src = 'image_list.js';
document.head.appendChild(script);

script.onload = function() {
try {
const data = loadBeforeAfterImages();

if (data && data.images) {
const processedImages = data.images.map(icon => {
const displayName = icon.name.replace(/^32x32_/, '');
return {
...icon,
name: displayName
};
});

renderIcons(processedImages, data.availableGroups || []);
} else {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки данных</div>';
}
} catch (error) {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка обработки данных</div>';
}
};

script.onerror = function() {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки файла image_list.js</div>';
};

} catch (error) {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки иконок</div>';
}
}

function setupSearch() {
const searchInput = document.getElementById('searchInput');
let searchTimeout;

searchInput.addEventListener('input', function(e) {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
const selectedGroup = document.getElementById('groupSelect').value;
filterIcons(e.target.value, selectedGroup);
}, 300);
});

searchInput.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
this.value = '';
const selectedGroup = document.getElementById('groupSelect').value;
filterIcons('', selectedGroup);
}
});
}

function setupGroupsToggle() {
const toggle = document.getElementById('showGroupsToggle');

toggle.addEventListener('change', function() {
showGroups = this.checked;
renderFilteredIcons();
});
}

window.addEventListener('DOMContentLoaded', function() {
loadIcons();
setupSearch();
setupGroupsToggle();
});
</script>
</body>
</html>
<!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: Arial, sans-serif;
background-color: #fff;
padding: 10px;
font-size: 11px;
}

.container {
max-width: 100%;
margin: 0 auto;
}

h1 {
text-align: center;
color: #333;
font-size: 16px;
margin-bottom: 15px;
font-weight: bold;
}

.search-container {
margin: 15px 0;
text-align: center;
}

.search-input {
width: 300px;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}

.search-input:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}

.controls-row {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
margin: 12px 0;
padding: 8px;
background: #f0f0f0;
border-radius: 4px;
border: 1px solid #ddd;
}

.stats {
font-size: 12px;
}

.group-select {
padding: 6px 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 12px;
background: white;
}

.show-groups-toggle {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
order: 2;
}

.toggle-checkbox {
width: 16px;
height: 16px;
}

.comparison-container {
display: flex;
flex-wrap: wrap;
gap: 15px;
align-items: flex-start;
}

.group-section {
flex: 0 1 calc(33.333% - 15px);
min-width: 350px;
margin-bottom: 15px;
}

.group-header {
background: #f8f9fa;
padding: 6px 10px;
border-radius: 4px;
margin-bottom: 8px;
font-weight: bold;
color: #495057;
font-size: 13px;
}

.icon-column {
border: 1px solid #e0e0e0;
border-radius: 5px;
padding: 8px;
background: #fafafa;
}

.column-header {
display: grid;
grid-template-columns: 32px 20px 32px 1fr;
gap: 8px;
align-items: center;
padding: 5px 3px;
margin-bottom: 5px;
border-bottom: 1px solid #ddd;
font-weight: bold;
text-align: center;
}

.header-label {
font-size: 11px;
color: #333;
}

.icon-row {
display: grid;
grid-template-columns: 32px 20px 32px 1fr;
gap: 8px;
align-items: center;
padding: 6px 3px;
border-bottom: 1px solid #f0f0f0;
}

.icon-row:last-child {
border-bottom: none;
}

.icon-image {
width: 32px;
height: 32px;
object-fit: contain;
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
image-rendering: pixelated;
}

.bmp-processed {
filter: none;
}

.arrow {
text-align: center;
color: #999;
font-size: 14px;
font-weight: bold;
}

.icon-info {
padding-left: 8px;
padding-right: 5px;
min-width: 0;
}

.icon-name {
color: #333;
line-height: 1.3;
word-wrap: break-word;
overflow-wrap: break-word;
hyphens: auto;
text-align: left;
}

.loading {
text-align: center;
padding: 20px;
color: #666;
width: 100%;
}

.no-results {
text-align: center;
padding: 40px;
color: #666;
width: 100%;
font-size: 14px;
}
</style>
</head>
<body>
<div class="container">
<h1>Сравнение иконок ИСИХОГИ старой и новой версий</h1>

<div class="search-container">
<input type="text"
id="searchInput"
class="search-input"
placeholder="Поиск по названию иконки...">
</div>

<div class="controls-row">
<div class="stats">
Всего иконок для сравнения: <span id="totalCount">0</span> |
Отображено: <span id="shownCount">0</span>
</div>
<select id="groupSelect" class="group-select" style="display: none;">
<option value="">Все группы</option>
</select>
<div class="show-groups-toggle">
<input type="checkbox" id="showGroupsToggle" class="toggle-checkbox" checked>
<label for="showGroupsToggle">Показывать группы</label>
</div>
</div>

<div class="comparison-container" id="iconsContainer">
<div class="loading">Загрузка иконок...</div>
</div>
</div>

<script>
let allIcons = [];
let filteredIcons = [];
let availableGroups = [];
let showGroups = true;

// Упрощенный алгоритм компоновки - просто располагаем группы плиткой
function arrangeGroups(groups) {
const groupEntries = Object.entries(groups);

// Сортируем группы по количеству иконок (от больших к маленьким)
// для лучшего визуального баланса
groupEntries.sort((a, b) => b[1].length - a[1].length);

return groupEntries.map(([name, icons]) => ({ name, icons }));
}

// Функция для отображения иконок с группировкой (простая плиточная компоновка)
function renderIconsWithGroups() {
const container = document.getElementById('iconsContainer');
container.innerHTML = '';

if (filteredIcons.length === 0) {
container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>';
updateStats();
return;
}

// Группируем иконки
const { groups, noGroup } = groupIcons(filteredIcons);

// Оптимально располагаем группы
const arrangedGroups = arrangeGroups(groups);

// Добавляем все группы как отдельные секции
arrangedGroups.forEach(group => {
const section = createGroupSection(group.name, group.icons);
container.appendChild(section);
});

// Добавляем иконки без группы
if (noGroup.length > 0) {
const noGroupSection = document.createElement('div');
noGroupSection.className = 'group-section';

const header = document.createElement('div');
header.className = 'group-header';
header.textContent = 'Прочие иконки';
noGroupSection.appendChild(header);

const column = createIconColumn(noGroup);
noGroupSection.appendChild(column);

container.appendChild(noGroupSection);
}

updateStats();
}

// Функция для создания секции группы
function createGroupSection(groupName, icons) {
const section = document.createElement('div');
section.className = 'group-section';

const header = document.createElement('div');
header.className = 'group-header';
header.textContent = groupName;
section.appendChild(header);

const column = createIconColumn(icons);
section.appendChild(column);

return section;
}

// Остальные функции без изменений
function processBMPWithPinkBackground(img) {
return new Promise((resolve) => {
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;

ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;

for (let i = 0; i < data.length; i += 4) {
if (data[i] === 255 && data[i + 1] === 0 && data[i + 2] === 255) {
data[i + 3] = 0;
}
}

ctx.putImageData(imageData, 0, 0);

const processedImg = new Image();
processedImg.onload = () => resolve({
element: processedImg,
processed: true
});
processedImg.src = canvas.toDataURL('image/png');
} catch (error) {
console.warn('Ошибка обработки BMP:', error);
resolve({
element: img,
processed: false
});
}
});
}

function loadImageWithBMPProcessing(src, alt) {
return new Promise((resolve) => {
const img = new Image();

img.onload = async function() {
try {
if (src.toLowerCase().endsWith('.bmp')) {
const result = await processBMPWithPinkBackground(img);
result.element.alt = alt;
resolve(result);
} else {
img.alt = alt;
resolve({ element: img, processed: false });
}
} catch (error) {
console.warn('Ошибка обработки изображения:', error, src);
img.alt = alt;
resolve({ element: img, processed: false });
}
};

img.onerror = function() {
console.warn('Ошибка загрузки изображения:', src);
const placeholder = new Image();
placeholder.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSIjRjVGNUY1Ii8+CjxwYXRoIGQ9Ik0xNiAxOEMxNi41NTIzIDE4IDE3IDE3LjU1MjMgMTcgMTdWMTFDMTcgMTAuNDQ3NyAxNi41NTIzIDEwIDE2IDEwQzE1LjQ0NzcgMTAgMTUgMTAuNDQ3NyAxNSAxMVYxN0MxNSAxNy41NTIzIDE1LjQ0NzcgMTggMTYgMThaIiBmaWxsPSIjOTk5OTk5Ii8+CjxwYXRoIGQ9Ik0xNiAyMkMxNi41NTIzIDIyIDE3IDIxLjU1MjMgMTcgMjFDMTcgMjAuNDQ3NyAxNi41NTIzIDIw IDE2IDIwQzE1LjQ0NzcgMjAgMTUgMjAuNDQ3NyAxNSAyMUMxNSAyMS41NTIzIDE1LjQ0NzcgMjIgMTYgMjJaIiBmaWxsPSIjOTk5OTk5Ii8+Cjwvc3ZnPgo=';
placeholder.alt = alt;
resolve({ element: placeholder, processed: false });
};

img.src = src;
});
}

function groupIcons(icons) {
const groups = {};
const noGroup = [];

icons.forEach(icon => {
if (icon.group && icon.group.trim() !== '') {
if (!groups[icon.group]) {
groups[icon.group] = [];
}
groups[icon.group].push(icon);
} else {
noGroup.push(icon);
}
});

return { groups, noGroup };
}

function createIconColumn(icons) {
const column = document.createElement('div');
column.className = 'icon-column';

const header = document.createElement('div');
header.className = 'column-header';

const beforeLabel = document.createElement('div');
beforeLabel.className = 'header-label';
beforeLabel.textContent = 'ДО';

const arrowSpace = document.createElement('div');
arrowSpace.className = 'arrow';
arrowSpace.textContent = '→';

const afterLabel = document.createElement('div');
afterLabel.className = 'header-label';
afterLabel.textContent = 'ПОСЛЕ';

const nameLabel = document.createElement('div');
nameLabel.className = 'header-label';
nameLabel.textContent = 'Название';
nameLabel.style.textAlign = 'center';

header.appendChild(beforeLabel);
header.appendChild(arrowSpace);
header.appendChild(afterLabel);
header.appendChild(nameLabel);

column.appendChild(header);

icons.forEach(icon => {
column.appendChild(createIconRow(icon));
});

return column;
}

function createIconRow(icon) {
const row = document.createElement('div');
row.className = 'icon-row';

const beforeContainer = document.createElement('div');
const afterContainer = document.createElement('div');

Promise.all([
loadImageWithBMPProcessing(icon.before, `${icon.name} (до)`),
loadImageWithBMPProcessing(icon.after, `${icon.name} (после)`)
]).then(([beforeResult, afterResult]) => {
const beforeImg = beforeResult.element;
const afterImg = afterResult.element;

beforeImg.className = 'icon-image' + (beforeResult.processed ? ' bmp-processed' : '');
afterImg.className = 'icon-image' + (afterResult.processed ? ' bmp-processed' : '');

beforeContainer.appendChild(beforeImg);
afterContainer.appendChild(afterImg);
});

const arrow = document.createElement('div');
arrow.className = 'arrow';
arrow.textContent = '→';

const infoDiv = document.createElement('div');
infoDiv.className = 'icon-info';

const name = document.createElement('div');
name.className = 'icon-name';
name.textContent = icon.name;
name.title = icon.name;

infoDiv.appendChild(name);

row.appendChild(beforeContainer);
row.appendChild(arrow);
row.appendChild(afterContainer);
row.appendChild(infoDiv);

return row;
}

function renderIconsWithoutGroups() {
const container = document.getElementById('iconsContainer');
container.innerHTML = '';

if (filteredIcons.length === 0) {
container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>';
updateStats();
return;
}

const sortedIcons = [...filteredIcons].sort((a, b) => {
const groupA = a.group || 'zzz';
const groupB = b.group || 'zzz';

if (groupA !== groupB) {
return groupA.localeCompare(groupB);
}

return a.name.localeCompare(b.name);
});

const itemsPerColumn = 20;
const columnCount = Math.ceil(sortedIcons.length / itemsPerColumn);

for (let i = 0; i < columnCount; i++) {
const startIndex = i * itemsPerColumn;
const endIndex = startIndex + itemsPerColumn;
const columnIcons = sortedIcons.slice(startIndex, endIndex);
const column = createIconColumn(columnIcons);
container.appendChild(column);
}

updateStats();
}

function filterIcons(searchTerm, selectedGroup = '') {
let filtered = [...allIcons];

if (searchTerm) {
const term = searchTerm.toLowerCase();
filtered = filtered.filter(icon =>
icon.name.toLowerCase().includes(term) ||
(icon.group && icon.group.toLowerCase().includes(term))
);
}

if (selectedGroup) {
filtered = filtered.filter(icon => icon.group === selectedGroup);
}

filteredIcons = filtered;
renderFilteredIcons();
}

function renderFilteredIcons() {
if (showGroups) {
renderIconsWithGroups();
} else {
renderIconsWithoutGroups();
}
}

function updateStats() {
document.getElementById('totalCount').textContent = allIcons.length;
document.getElementById('shownCount').textContent = filteredIcons.length;
}

function initGroupSelect(groups) {
const groupSelect = document.getElementById('groupSelect');
groupSelect.innerHTML = '<option value="">Все группы</option>';

groups.forEach(group => {
const option = document.createElement('option');
option.value = group;
option.textContent = group;
groupSelect.appendChild(option);
});

groupSelect.style.display = groups.length > 0 ? 'block' : 'none';

groupSelect.addEventListener('change', function() {
const searchTerm = document.getElementById('searchInput').value;
filterIcons(searchTerm, this.value);
});
}

function renderIcons(images, groups) {
allIcons = images;
filteredIcons = [...allIcons];
availableGroups = groups;

initGroupSelect(groups);
renderFilteredIcons();
}

async function loadIcons() {
try {
const script = document.createElement('script');
script.src = 'image_list.js';
document.head.appendChild(script);

script.onload = function() {
try {
const data = loadBeforeAfterImages();

if (data && data.images) {
const processedImages = data.images.map(icon => {
const displayName = icon.name.replace(/^32x32_/, '');
return {
...icon,
name: displayName
};
});

renderIcons(processedImages, data.availableGroups || []);
} else {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки данных</div>';
}
} catch (error) {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка обработки данных</div>';
}
};

script.onerror = function() {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки файла image_list.js</div>';
};

} catch (error) {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки иконок</div>';
}
}

function setupSearch() {
const searchInput = document.getElementById('searchInput');
let searchTimeout;

searchInput.addEventListener('input', function(e) {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
const selectedGroup = document.getElementById('groupSelect').value;
filterIcons(e.target.value, selectedGroup);
}, 300);
});

searchInput.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
this.value = '';
const selectedGroup = document.getElementById('groupSelect').value;
filterIcons('', selectedGroup);
}
});
}

function setupGroupsToggle() {
const toggle = document.getElementById('showGroupsToggle');

toggle.addEventListener('change', function() {
showGroups = this.checked;
renderFilteredIcons();
});
}

window.addEventListener('DOMContentLoaded', function() {
loadIcons();
setupSearch();
setupGroupsToggle();
});
</script>
</body>
</html>
<!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: Arial, sans-serif;
background-color: #fff;
padding: 10px;
font-size: 11px;
}

.container {
max-width: 100%;
margin: 0 auto;
}

h1 {
text-align: center;
color: #333;
font-size: 16px;
margin-bottom: 15px;
font-weight: bold;
}

.search-container {
margin: 15px 0;
text-align: center;
}

.search-input {
width: 300px;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}

.search-input:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}

.controls-row {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
margin: 12px 0;
padding: 8px;
background: #f0f0f0;
border-radius: 4px;
border: 1px solid #ddd;
}

.stats {
font-size: 12px;
}

.group-select {
padding: 6px 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 12px;
background: white;
}

.show-groups-toggle {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
order: 2;
}

.toggle-checkbox {
width: 16px;
height: 16px;
}

.comparison-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
gap: 15px;
align-items: start;
}

.group-section {
break-inside: avoid;
}

.group-header {
background: #f8f9fa;
padding: 6px 10px;
border-radius: 4px;
margin-bottom: 8px;
font-weight: bold;
color: #495057;
font-size: 13px;
position: sticky;
top: 0;
z-index: 1;
}

.icon-column {
border: 1px solid #e0e0e0;
border-radius: 5px;
padding: 8px;
background: #fafafa;
width: 100%;
min-width: 380px;
max-width: 380px;
}

.column-header {
display: grid;
grid-template-columns: 32px 20px 32px 1fr;
gap: 8px;
align-items: center;
padding: 5px 3px;
margin-bottom: 5px;
border-bottom: 1px solid #ddd;
font-weight: bold;
text-align: center;
}

.header-label {
font-size: 11px;
color: #333;
}

.icon-row {
display: grid;
grid-template-columns: 32px 20px 32px 1fr;
gap: 8px;
align-items: center;
padding: 6px 3px;
border-bottom: 1px solid #f0f0f0;
min-height: 40px;
}

.icon-row:last-child {
border-bottom: none;
}

.icon-image {
width: 32px;
height: 32px;
object-fit: contain;
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
image-rendering: pixelated;
}

.bmp-processed {
filter: none;
}

.arrow {
text-align: center;
color: #999;
font-size: 14px;
font-weight: bold;
}

.icon-info {
padding-left: 8px;
padding-right: 5px;
min-width: 0;
width: 100%;
}

.icon-name {
color: #333;
line-height: 1.3;
word-wrap: break-word;
overflow-wrap: break-word;
hyphens: auto;
text-align: left;
font-size: 11px;
max-width: 200px;
}

.loading {
text-align: center;
padding: 20px;
color: #666;
width: 100%;
grid-column: 1 / -1;
}

.no-results {
text-align: center;
padding: 40px;
color: #666;
width: 100%;
font-size: 14px;
grid-column: 1 / -1;
}

.columns-container {
display: flex;
gap: 15px;
width: 100%;
}

.masonry-column {
flex: 1;
min-width: 380px;
display: flex;
flex-direction: column;
gap: 15px;
}
</style>
</head>
<body>
<div class="container">
<h1>Сравнение иконок ИСИХОГИ старой и новой версий</h1>

<div class="search-container">
<input type="text"
id="searchInput"
class="search-input"
placeholder="Поиск по названию иконки...">
</div>

<div class="controls-row">
<div class="stats">
Всего иконок для сравнения: <span id="totalCount">0</span> |
Отображено: <span id="shownCount">0</span>
</div>
<select id="groupSelect" class="group-select" style="display: none;">
<option value="">Все группы</option>
</select>
<div class="show-groups-toggle">
<input type="checkbox" id="showGroupsToggle" class="toggle-checkbox" checked>
<label for="showGroupsToggle">Показывать группы</label>
</div>
</div>

<div class="comparison-container" id="iconsContainer">
<div class="loading">Загрузка иконок...</div>
</div>
</div>

<script>
let allIcons = [];
let filteredIcons = [];
let availableGroups = [];
let showGroups = true;

// Функция для разбивки больших групп на части
function splitLargeGroup(icons, maxItems = 15) {
if (icons.length <= maxItems) {
return [icons];
}

const chunks = [];
for (let i = 0; i < icons.length; i += maxItems) {
chunks.push(icons.slice(i, i + maxItems));
}
return chunks;
}

// Алгоритм masonry компоновки
function arrangeGroupsMasonry(groups) {
const groupEntries = Object.entries(groups);
const result = [];

// Сначала разбиваем большие группы
const allGroupChunks = [];

groupEntries.forEach(([groupName, icons]) => {
const chunks = splitLargeGroup(icons, 15);
chunks.forEach((chunk, index) => {
const chunkName = chunks.length > 1 ? `${groupName} (${index + 1}/${chunks.length})` : groupName;
allGroupChunks.push({
name: chunkName,
icons: chunk,
originalGroup: groupName,
height: calculateGroupHeight(chunk.length)
});
});
});

// Сортируем по высоте (от высоких к низким для лучшего заполнения)
allGroupChunks.sort((a, b) => b.height - a.height);

return allGroupChunks;
}

// Функция для расчета примерной высоты группы
function calculateGroupHeight(iconsCount) {
const headerHeight = 40;
const rowHeight = 32;
const padding = 16;
return headerHeight + (iconsCount * rowHeight) + padding;
}

// Функция для создания masonry колонок
function createMasonryLayout(groupsChunks) {
const container = document.getElementById('iconsContainer');
const containerWidth = container.clientWidth;
const columnWidth = 380 + 15; // ширина колонки + gap
const columnCount = Math.max(1, Math.floor(containerWidth / columnWidth));

// Создаем колонки
const columns = Array.from({ length: columnCount }, () => {
const col = document.createElement('div');
col.className = 'masonry-column';
return col;
});

// Массив для отслеживания высоты каждой колонки
const columnHeights = Array(columnCount).fill(0);

// Распределяем группы по колонкам (алгоритм "наименьшая высота")
groupsChunks.forEach(group => {
// Находим колонку с наименьшей высотой
let minHeight = Math.min(...columnHeights);
let columnIndex = columnHeights.indexOf(minHeight);

// Добавляем группу в эту колонку
const section = createGroupSection(group.name, group.icons);
columns[columnIndex].appendChild(section);

// Обновляем высоту колонки
columnHeights[columnIndex] += group.height;
});

return columns;
}

// Функция для отображения иконок с группировкой (masonry компоновка)
function renderIconsWithGroups() {
const container = document.getElementById('iconsContainer');
container.innerHTML = '';

if (filteredIcons.length === 0) {
container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>';
updateStats();
return;
}

// Группируем иконки
const { groups, noGroup } = groupIcons(filteredIcons);

// Создаем masonry компоновку для групп
const groupsChunks = arrangeGroupsMasonry(groups);

// Добавляем иконки без группы
if (noGroup.length > 0) {
const noGroupChunks = splitLargeGroup(noGroup, 15);
noGroupChunks.forEach((chunk, index) => {
const chunkName = noGroupChunks.length > 1 ? `Прочие иконки (${index + 1}/${noGroupChunks.length})` : 'Прочие иконки';
groupsChunks.push({
name: chunkName,
icons: chunk,
originalGroup: 'no_group',
height: calculateGroupHeight(chunk.length)
});
});
}

// Создаем masonry колонки
const columns = createMasonryLayout(groupsChunks);

// Очищаем контейнер и добавляем колонки
container.innerHTML = '';
const columnsContainer = document.createElement('div');
columnsContainer.className = 'columns-container';

columns.forEach(col => {
if (col.children.length > 0) {
columnsContainer.appendChild(col);
}
});

container.appendChild(columnsContainer);
updateStats();
}

// Функция для создания секции группы
function createGroupSection(groupName, icons) {
const section = document.createElement('div');
section.className = 'group-section';

const header = document.createElement('div');
header.className = 'group-header';
header.textContent = groupName;
section.appendChild(header);

const column = createIconColumn(icons);
section.appendChild(column);

return section;
}

// Остальные функции без изменений
function processBMPWithPinkBackground(img) {
return new Promise((resolve) => {
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;

ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;

for (let i = 0; i < data.length; i += 4) {
if (data[i] === 255 && data[i + 1] === 0 && data[i + 2] === 255) {
data[i + 3] = 0;
}
}

ctx.putImageData(imageData, 0, 0);

const processedImg = new Image();
processedImg.onload = () => resolve({
element: processedImg,
processed: true
});
processedImg.src = canvas.toDataURL('image/png');
} catch (error) {
console.warn('Ошибка обработки BMP:', error);
resolve({
element: img,
processed: false
});
}
});
}

function loadImageWithBMPProcessing(src, alt) {
return new Promise((resolve) => {
const img = new Image();

img.onload = async function() {
try {
if (src.toLowerCase().endsWith('.bmp')) {
const result = await processBMPWithPinkBackground(img);
result.element.alt = alt;
resolve(result);
} else {
img.alt = alt;
resolve({ element: img, processed: false });
}
} catch (error) {
console.warn('Ошибка обработки изображения:', error, src);
img.alt = alt;
resolve({ element: img, processed: false });
}
};

img.onerror = function() {
console.warn('Ошибка загрузки изображения:', src);
const placeholder = new Image();
placeholder.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSIjRjVGNUY1Ii8+CjxwYXRoIGQ9Ik0xNiAxOEMxNi41NTIzIDE4IDE3IDE3LjU1MjMgMTcgMTdWMTFDMTcgMTAuNDQ3NyAxNi41NTIzIDEwIDE2IDEwQzE1LjQ0NzcgMTAgMTUgMTAuNDQ3NyAxNSAxMVYxN0MxNSAxNy41NTIzIDE1LjQ0NzcgMTggMTYgMThaIiBmaWxsPSIjOTk5OTk5Ii8+CjxwYXRoIGQ9Ik0xNiAyMkMxNi41NTIzIDIyIDE3IDIxLjU1MjMgMTcgMjFDMTcgMjAuNDQ3NyAxNi41NTIzIDIwIDE2IDIwQzE1LjQ0NzcgMjAgMTUgMjAuNDQ3NyAxNSAyMUMxNSAyMS41NTIzIDE1LjQ0NzcgMjIgMTYgMjJaIiBmaWxsPSIjOTk5OTk5Ii8+Cjwvc3ZnPgo=';
placeholder.alt = alt;
resolve({ element: placeholder, processed: false });
};

img.src = src;
});
}

function groupIcons(icons) {
const groups = {};
const noGroup = [];

icons.forEach(icon => {
if (icon.group && icon.group.trim() !== '') {
if (!groups[icon.group]) {
groups[icon.group] = [];
}
groups[icon.group].push(icon);
} else {
noGroup.push(icon);
}
});

return { groups, noGroup };
}

function createIconColumn(icons) {
const column = document.createElement('div');
column.className = 'icon-column';

const header = document.createElement('div');
header.className = 'column-header';

const beforeLabel = document.createElement('div');
beforeLabel.className = 'header-label';
beforeLabel.textContent = 'ДО';

const arrowSpace = document.createElement('div');
arrowSpace.className = 'arrow';
arrowSpace.textContent = '→';

const afterLabel = document.createElement('div');
afterLabel.className = 'header-label';
afterLabel.textContent = 'ПОСЛЕ';

const nameLabel = document.createElement('div');
nameLabel.className = 'header-label';
nameLabel.textContent = 'Название';
nameLabel.style.textAlign = 'center';

header.appendChild(beforeLabel);
header.appendChild(arrowSpace);
header.appendChild(afterLabel);
header.appendChild(nameLabel);

column.appendChild(header);

icons.forEach(icon => {
column.appendChild(createIconRow(icon));
});

return column;
}

function createIconRow(icon) {
const row = document.createElement('div');
row.className = 'icon-row';

const beforeContainer = document.createElement('div');
const afterContainer = document.createElement('div');

Promise.all([
loadImageWithBMPProcessing(icon.before, `${icon.name} (до)`),
loadImageWithBMPProcessing(icon.after, `${icon.name} (после)`)
]).then(([beforeResult, afterResult]) => {
const beforeImg = beforeResult.element;
const afterImg = afterResult.element;

beforeImg.className = 'icon-image' + (beforeResult.processed ? ' bmp-processed' : '');
afterImg.className = 'icon-image' + (afterResult.processed ? ' bmp-processed' : '');

beforeContainer.appendChild(beforeImg);
afterContainer.appendChild(afterImg);
});

const arrow = document.createElement('div');
arrow.className = 'arrow';
arrow.textContent = '→';

const infoDiv = document.createElement('div');
infoDiv.className = 'icon-info';

const name = document.createElement('div');
name.className = 'icon-name';
name.textContent = icon.name;
name.title = icon.name;

infoDiv.appendChild(name);

row.appendChild(beforeContainer);
row.appendChild(arrow);
row.appendChild(afterContainer);
row.appendChild(infoDiv);

return row;
}

function renderIconsWithoutGroups() {
const container = document.getElementById('iconsContainer');
container.innerHTML = '';

if (filteredIcons.length === 0) {
container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>';
updateStats();
return;
}

const sortedIcons = [...filteredIcons].sort((a, b) => {
const groupA = a.group || 'zzz';
const groupB = b.group || 'zzz';

if (groupA !== groupB) {
return groupA.localeCompare(groupB);
}

return a.name.localeCompare(b.name);
});

// Для режима без групп используем простые колонки
const itemsPerColumn = 20;
const columnCount = Math.ceil(sortedIcons.length / itemsPerColumn);

for (let i = 0; i < columnCount; i++) {
const startIndex = i * itemsPerColumn;
const endIndex = startIndex + itemsPerColumn;
const columnIcons = sortedIcons.slice(startIndex, endIndex);
const column = createIconColumn(columnIcons);
container.appendChild(column);
}

updateStats();
}

function filterIcons(searchTerm, selectedGroup = '') {
let filtered = [...allIcons];

if (searchTerm) {
const term = searchTerm.toLowerCase();
filtered = filtered.filter(icon =>
icon.name.toLowerCase().includes(term) ||
(icon.group && icon.group.toLowerCase().includes(term))
);
}

if (selectedGroup) {
filtered = filtered.filter(icon => icon.group === selectedGroup);
}

filteredIcons = filtered;
renderFilteredIcons();
}

function renderFilteredIcons() {
if (showGroups) {
renderIconsWithGroups();
} else {
renderIconsWithoutGroups();
}
}

function updateStats() {
document.getElementById('totalCount').textContent = allIcons.length;
document.getElementById('shownCount').textContent = filteredIcons.length;
}

function initGroupSelect(groups) {
const groupSelect = document.getElementById('groupSelect');
groupSelect.innerHTML = '<option value="">Все группы</option>';

groups.forEach(group => {
const option = document.createElement('option');
option.value = group;
option.textContent = group;
groupSelect.appendChild(option);
});

groupSelect.style.display = groups.length > 0 ? 'block' : 'none';

groupSelect.addEventListener('change', function() {
const searchTerm = document.getElementById('searchInput').value;
filterIcons(searchTerm, this.value);
});
}

function renderIcons(images, groups) {
allIcons = images;
filteredIcons = [...allIcons];
availableGroups = groups;

initGroupSelect(groups);
renderFilteredIcons();
}

async function loadIcons() {
try {
const script = document.createElement('script');
script.src = 'image_list.js';
document.head.appendChild(script);

script.onload = function() {
try {
const data = loadBeforeAfterImages();

if (data && data.images) {
const processedImages = data.images.map(icon => {
const displayName = icon.name.replace(/^32x32_/, '');
return {
...icon,
name: displayName
};
});

renderIcons(processedImages, data.availableGroups || []);
} else {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки данных</div>';
}
} catch (error) {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка обработки данных</div>';
}
};

script.onerror = function() {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки файла image_list.js</div>';
};

} catch (error) {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки иконок</div>';
}
}

function setupSearch() {
const searchInput = document.getElementById('searchInput');
let searchTimeout;

searchInput.addEventListener('input', function(e) {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
const selectedGroup = document.getElementById('groupSelect').value;
filterIcons(e.target.value, selectedGroup);
}, 300);
});

searchInput.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
this.value = '';
const selectedGroup = document.getElementById('groupSelect').value;
filterIcons('', selectedGroup);
}
});
}

function setupGroupsToggle() {
const toggle = document.getElementById('showGroupsToggle');

toggle.addEventListener('change', function() {
showGroups = this.checked;
renderFilteredIcons();
});
}

window.addEventListener('DOMContentLoaded', function() {
loadIcons();
setupSearch();
setupGroupsToggle();
});
</script>
</body>
</html>
<!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: Arial, sans-serif;
background-color: #fff;
padding: 10px;
font-size: 11px;
}

.container {
max-width: 100%;
margin: 0 auto;
}

h1 {
text-align: center;
color: #333;
font-size: 16px;
margin-bottom: 15px;
font-weight: bold;
}

.search-container {
margin: 15px 0;
text-align: center;
}

.search-input {
width: 300px;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}

.search-input:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}

.controls-row {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
margin: 12px 0;
padding: 8px;
background: #f0f0f0;
border-radius: 4px;
border: 1px solid #ddd;
}

.stats {
font-size: 12px;
}

.group-select {
padding: 6px 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 12px;
background: white;
}

.show-groups-toggle {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
order: 2;
}

.toggle-checkbox {
width: 16px;
height: 16px;
}

.comparison-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
gap: 15px;
align-items: start;
}

.group-section {
break-inside: avoid;
}

.group-header {
background: #f8f9fa;
padding: 6px 10px;
border-radius: 4px;
margin-bottom: 8px;
font-weight: bold;
color: #495057;
font-size: 13px;
position: sticky;
top: 0;
z-index: 1;
}

.icon-column {
border: 1px solid #e0e0e0;
border-radius: 5px;
padding: 8px;
background: #fafafa;
width: 100%;
min-width: 380px;
max-width: 380px;
}

.column-header {
display: grid;
grid-template-columns: 32px 20px 32px 1fr;
gap: 8px;
align-items: center;
padding: 5px 3px;
margin-bottom: 5px;
border-bottom: 1px solid #ddd;
font-weight: bold;
text-align: center;
}

.header-label {
font-size: 11px;
color: #333;
}

.icon-row {
display: grid;
grid-template-columns: 32px 20px 32px 1fr;
gap: 8px;
align-items: center;
padding: 6px 3px;
border-bottom: 1px solid #f0f0f0;
min-height: 40px;
}

.icon-row:last-child {
border-bottom: none;
}

.icon-image {
width: 32px;
height: 32px;
object-fit: contain;
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
image-rendering: pixelated;
}

.bmp-processed {
filter: none;
}

.arrow {
text-align: center;
color: #999;
font-size: 14px;
font-weight: bold;
}

.icon-info {
padding-left: 8px;
padding-right: 5px;
min-width: 0;
width: 100%;
}

.icon-name {
color: #333;
line-height: 1.3;
word-wrap: break-word;
overflow-wrap: break-word;
hyphens: auto;
text-align: left;
font-size: 11px;
max-width: 200px;
}

.loading {
text-align: center;
padding: 20px;
color: #666;
width: 100%;
grid-column: 1 / -1;
}

.no-results {
text-align: center;
padding: 40px;
color: #666;
width: 100%;
font-size: 14px;
grid-column: 1 / -1;
}

.columns-container {
display: flex;
gap: 15px;
width: 100%;
}

.masonry-column {
flex: 1;
min-width: 380px;
display: flex;
flex-direction: column;
gap: 15px;
}
</style>
</head>
<body>
<div class="container">
<h1>Сравнение иконок ИСИХОГИ старой и новой версий</h1>

<div class="search-container">
<input type="text"
id="searchInput"
class="search-input"
placeholder="Поиск по названию иконки...">
</div>

<div class="controls-row">
<div class="stats">
Всего иконок для сравнения: <span id="totalCount">0</span> |
Отображено: <span id="shownCount">0</span>
</div>
<select id="groupSelect" class="group-select" style="display: none;">
<option value="">Все группы</option>
</select>
<div class="show-groups-toggle">
<input type="checkbox" id="showGroupsToggle" class="toggle-checkbox" checked>
<label for="showGroupsToggle">Показывать группы</label>
</div>
</div>

<div class="comparison-container" id="iconsContainer">
<div class="loading">Загрузка иконок...</div>
</div>
</div>

<script>
let allIcons = [];
let filteredIcons = [];
let availableGroups = [];
let showGroups = true;

// Функция для разбивки больших групп на части
function splitLargeGroup(icons, maxItems = 15) {
if (icons.length <= maxItems) {
return [icons];
}

const chunks = [];
for (let i = 0; i < icons.length; i += maxItems) {
chunks.push(icons.slice(i, i + maxItems));
}
return chunks;
}

// Алгоритм masonry компоновки
function arrangeGroupsMasonry(groups) {
const groupEntries = Object.entries(groups);
const result = [];

// Сначала разбиваем большие группы
const allGroupChunks = [];

groupEntries.forEach(([groupName, icons]) => {
const chunks = splitLargeGroup(icons, 15);
chunks.forEach((chunk, index) => {
const chunkName = chunks.length > 1 ? `${groupName} (${index + 1}/${chunks.length})` : groupName;
allGroupChunks.push({
name: chunkName,
icons: chunk,
originalGroup: groupName,
height: calculateGroupHeight(chunk.length)
});
});
});

// Сортируем по высоте (от высоких к низким для лучшего заполнения)
allGroupChunks.sort((a, b) => b.height - a.height);

return allGroupChunks;
}

// Функция для расчета примерной высоты группы
function calculateGroupHeight(iconsCount) {
const headerHeight = 40;
const rowHeight = 32;
const padding = 16;
return headerHeight + (iconsCount * rowHeight) + padding;
}

// Функция для создания masonry колонок
function createMasonryLayout(groupsChunks) {
const container = document.getElementById('iconsContainer');
const containerWidth = container.clientWidth;
const columnWidth = 380 + 15; // ширина колонки + gap
const columnCount = Math.max(1, Math.floor(containerWidth / columnWidth));

// Создаем колонки
const columns = Array.from({ length: columnCount }, () => {
const col = document.createElement('div');
col.className = 'masonry-column';
return col;
});

// Массив для отслеживания высоты каждой колонки
const columnHeights = Array(columnCount).fill(0);

// Распределяем группы по колонкам (алгоритм "наименьшая высота")
groupsChunks.forEach(group => {
// Находим колонку с наименьшей высотой
let minHeight = Math.min(...columnHeights);
let columnIndex = columnHeights.indexOf(minHeight);

// Добавляем группу в эту колонку
const section = createGroupSection(group.name, group.icons);
columns[columnIndex].appendChild(section);

// Обновляем высоту колонки
columnHeights[columnIndex] += group.height;
});

return columns;
}

// Функция для отображения иконок с группировкой (masonry компоновка)
function renderIconsWithGroups() {
const container = document.getElementById('iconsContainer');
container.innerHTML = '';

if (filteredIcons.length === 0) {
container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>';
updateStats();
return;
}

// Группируем иконки
const { groups, noGroup } = groupIcons(filteredIcons);

// Создаем masonry компоновку для групп
const groupsChunks = arrangeGroupsMasonry(groups);

// Добавляем иконки без группы
if (noGroup.length > 0) {
const noGroupChunks = splitLargeGroup(noGroup, 15);
noGroupChunks.forEach((chunk, index) => {
const chunkName = noGroupChunks.length > 1 ? `Прочие иконки (${index + 1}/${noGroupChunks.length})` : 'Прочие иконки';
groupsChunks.push({
name: chunkName,
icons: chunk,
originalGroup: 'no_group',
height: calculateGroupHeight(chunk.length)
});
});
}

// Создаем masonry колонки
const columns = createMasonryLayout(groupsChunks);

// Очищаем контейнер и добавляем колонки
container.innerHTML = '';
const columnsContainer = document.createElement('div');
columnsContainer.className = 'columns-container';

columns.forEach(col => {
if (col.children.length > 0) {
columnsContainer.appendChild(col);
}
});

container.appendChild(columnsContainer);
updateStats();
}

// Функция для создания секции группы
function createGroupSection(groupName, icons) {
const section = document.createElement('div');
section.className = 'group-section';

const header = document.createElement('div');
header.className = 'group-header';
header.textContent = groupName;
section.appendChild(header);

const column = createIconColumn(icons);
section.appendChild(column);

return section;
}

// Остальные функции без изменений
function processBMPWithPinkBackground(img) {
return new Promise((resolve) => {
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;

ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;

for (let i = 0; i < data.length; i += 4) {
if (data[i] === 255 && data[i + 1] === 0 && data[i + 2] === 255) {
data[i + 3] = 0;
}
}

ctx.putImageData(imageData, 0, 0);

const processedImg = new Image();
processedImg.onload = () => resolve({
element: processedImg,
processed: true
});
processedImg.src = canvas.toDataURL('image/png');
} catch (error) {
console.warn('Ошибка обработки BMP:', error);
resolve({
element: img,
processed: false
});
}
});
}

function loadImageWithBMPProcessing(src, alt) {
return new Promise((resolve) => {
const img = new Image();

img.onload = async function() {
try {
if (src.toLowerCase().endsWith('.bmp')) {
const result = await processBMPWithPinkBackground(img);
result.element.alt = alt;
resolve(result);
} else {
img.alt = alt;
resolve({ element: img, processed: false });
}
} catch (error) {
console.warn('Ошибка обработки изображения:', error, src);
img.alt = alt;
resolve({ element: img, processed: false });
}
};

img.onerror = function() {
console.warn('Ошибка загрузки изображения:', src);
const placeholder = new Image();
placeholder.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSIjRjVGNUY1Ii8+CjxwYXRoIGQ9Ik0xNiAxOEMxNi41NTIzIDE4IDE3IDE3LjU1MjMgMTcgMTdWMTFDMTcgMTAuNDQ3NyAxNi41NTIzIDEwIDE2IDEwQzE1LjQ0NzcgMTAgMTUgMTAuNDQ3NyAxNSAxMVYxN0MxNSAxNy41NTIzIDE1LjQ0NzcgMTggMTYgMThaIiBmaWxsPSIjOTk5OTk5Ii8+CjxwYXRoIGQ9Ik0xNiAyMkMxNi41NTIzIDIyIDE3IDIxLjU1MjMgMTcgMjFDMTcgMjAuNDQ3NyAxNi41NTIzIDIwIDE2IDIwQzE1LjQ0NzcgMjAgMTUgMjAuNDQ3NyAxNSAyMUMxNSAyMS41NTIzIDE1LjQ0NzcgMjIgMTYgMjJaIiBmaWxsPSIjOTk5OTk5Ii8+Cjwvc3ZnPgo=';
placeholder.alt = alt;
resolve({ element: placeholder, processed: false });
};

img.src = src;
});
}

function groupIcons(icons) {
const groups = {};
const noGroup = [];

icons.forEach(icon => {
if (icon.group && icon.group.trim() !== '') {
if (!groups[icon.group]) {
groups[icon.group] = [];
}
groups[icon.group].push(icon);
} else {
noGroup.push(icon);
}
});

return { groups, noGroup };
}

function createIconColumn(icons) {
const column = document.createElement('div');
column.className = 'icon-column';

const header = document.createElement('div');
header.className = 'column-header';

const beforeLabel = document.createElement('div');
beforeLabel.className = 'header-label';
beforeLabel.textContent = 'ДО';

const arrowSpace = document.createElement('div');
arrowSpace.className = 'arrow';
arrowSpace.textContent = '→';

const afterLabel = document.createElement('div');
afterLabel.className = 'header-label';
afterLabel.textContent = 'ПОСЛЕ';

const nameLabel = document.createElement('div');
nameLabel.className = 'header-label';
nameLabel.textContent = 'Название';
nameLabel.style.textAlign = 'center';

header.appendChild(beforeLabel);
header.appendChild(arrowSpace);
header.appendChild(afterLabel);
header.appendChild(nameLabel);

column.appendChild(header);

icons.forEach(icon => {
column.appendChild(createIconRow(icon));
});

return column;
}

function createIconRow(icon) {
const row = document.createElement('div');
row.className = 'icon-row';

const beforeContainer = document.createElement('div');
const afterContainer = document.createElement('div');

Promise.all([
loadImageWithBMPProcessing(icon.before, `${icon.name} (до)`),
loadImageWithBMPProcessing(icon.after, `${icon.name} (после)`)
]).then(([beforeResult, afterResult]) => {
const beforeImg = beforeResult.element;
const afterImg = afterResult.element;

beforeImg.className = 'icon-image' + (beforeResult.processed ? ' bmp-processed' : '');
afterImg.className = 'icon-image' + (afterResult.processed ? ' bmp-processed' : '');

beforeContainer.appendChild(beforeImg);
afterContainer.appendChild(afterImg);
});

const arrow = document.createElement('div');
arrow.className = 'arrow';
arrow.textContent = '→';

const infoDiv = document.createElement('div');
infoDiv.className = 'icon-info';

const name = document.createElement('div');
name.className = 'icon-name';
name.textContent = icon.name;
name.title = icon.name;

infoDiv.appendChild(name);

row.appendChild(beforeContainer);
row.appendChild(arrow);
row.appendChild(afterContainer);
row.appendChild(infoDiv);

return row;
}

function renderIconsWithoutGroups() {
const container = document.getElementById('iconsContainer');
container.innerHTML = '';

if (filteredIcons.length === 0) {
container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>';
updateStats();
return;
}

const sortedIcons = [...filteredIcons].sort((a, b) => {
const groupA = a.group || 'zzz';
const groupB = b.group || 'zzz';

if (groupA !== groupB) {
return groupA.localeCompare(groupB);
}

return a.name.localeCompare(b.name);
});

// Для режима без групп используем простые колонки
const itemsPerColumn = 20;
const columnCount = Math.ceil(sortedIcons.length / itemsPerColumn);

for (let i = 0; i < columnCount; i++) {
const startIndex = i * itemsPerColumn;
const endIndex = startIndex + itemsPerColumn;
const columnIcons = sortedIcons.slice(startIndex, endIndex);
const column = createIconColumn(columnIcons);
container.appendChild(column);
}

updateStats();
}

function filterIcons(searchTerm, selectedGroup = '') {
let filtered = [...allIcons];

if (searchTerm) {
const term = searchTerm.toLowerCase();
filtered = filtered.filter(icon =>
icon.name.toLowerCase().includes(term) ||
(icon.group && icon.group.toLowerCase().includes(term))
);
}

if (selectedGroup) {
filtered = filtered.filter(icon => icon.group === selectedGroup);
}

filteredIcons = filtered;
renderFilteredIcons();
}

function renderFilteredIcons() {
if (showGroups) {
renderIconsWithGroups();
} else {
renderIconsWithoutGroups();
}
}

function updateStats() {
document.getElementById('totalCount').textContent = allIcons.length;
document.getElementById('shownCount').textContent = filteredIcons.length;
}

function initGroupSelect(groups) {
const groupSelect = document.getElementById('groupSelect');
groupSelect.innerHTML = '<option value="">Все группы</option>';

groups.forEach(group => {
const option = document.createElement('option');
option.value = group;
option.textContent = group;
groupSelect.appendChild(option);
});

groupSelect.style.display = groups.length > 0 ? 'block' : 'none';

groupSelect.addEventListener('change', function() {
const searchTerm = document.getElementById('searchInput').value;
filterIcons(searchTerm, this.value);
});
}

function renderIcons(images, groups) {
allIcons = images;
filteredIcons = [...allIcons];
availableGroups = groups;

initGroupSelect(groups);
renderFilteredIcons();
}

async function loadIcons() {
try {
const script = document.createElement('script');
script.src = 'image_list.js';
document.head.appendChild(script);

script.onload = function() {
try {
const data = loadBeforeAfterImages();

if (data && data.images) {
const processedImages = data.images.map(icon => {
const displayName = icon.name.replace(/^32x32_/, '');
return {
...icon,
name: displayName
};
});

renderIcons(processedImages, data.availableGroups || []);
} else {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки данных</div>';
}
} catch (error) {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка обработки данных</div>';
}
};

script.onerror = function() {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки файла image_list.js</div>';
};

} catch (error) {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки иконок</div>';
}
}

function setupSearch() {
const searchInput = document.getElementById('searchInput');
let searchTimeout;

searchInput.addEventListener('input', function(e) {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
const selectedGroup = document.getElementById('groupSelect').value;
filterIcons(e.target.value, selectedGroup);
}, 300);
});

searchInput.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
this.value = '';
const selectedGroup = document.getElementById('groupSelect').value;
filterIcons('', selectedGroup);
}
});
}

function setupGroupsToggle() {
const toggle = document.getElementById('showGroupsToggle');

toggle.addEventListener('change', function() {
showGroups = this.checked;
renderFilteredIcons();
});
}

window.addEventListener('DOMContentLoaded', function() {
loadIcons();
setupSearch();
setupGroupsToggle();
});
</script>
</body>
</html>
<!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: Arial, sans-serif;
background-color: #fff;
padding: 10px;
font-size: 11px;
}

.container {
max-width: 1200px; /* Ограничение максимальной ширины */
margin: 0 auto;
}

h1 {
text-align: center;
color: #333;
font-size: 16px;
margin-bottom: 15px;
font-weight: bold;
}

.search-container {
margin: 15px 0;
text-align: center;
}

.search-input {
width: 300px;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}

.search-input:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}

.controls-row {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
margin: 12px 0;
padding: 8px;
background: #f0f0f0;
border-radius: 4px;
border: 1px solid #ddd;
}

.stats {
font-size: 12px;
}

.group-select {
padding: 6px 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 12px;
background: white;
}

.show-groups-toggle {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
order: 2;
}

.toggle-checkbox {
width: 16px;
height: 16px;
}

.comparison-container {
display: flex;
flex-wrap: wrap;
gap: 15px;
align-items: flex-start;
justify-content: center; /* Центрируем колонки */
}

.group-section {
break-inside: avoid;
}

.group-header {
background: #f8f9fa;
padding: 6px 10px;
border-radius: 4px;
margin-bottom: 8px;
font-weight: bold;
color: #495057;
font-size: 13px;
}

.icon-column {
border: 1px solid #e0e0e0;
border-radius: 5px;
padding: 8px;
background: #fafafa;
width: 380px; /* Фиксированная ширина */
flex-shrink: 0; /* Запрещаем сжатие */
}

.column-header {
display: grid;
grid-template-columns: 32px 20px 32px 1fr;
gap: 8px;
align-items: center;
padding: 5px 3px;
margin-bottom: 5px;
border-bottom: 1px solid #ddd;
font-weight: bold;
text-align: center;
}

.header-label {
font-size: 11px;
color: #333;
}

.icon-row {
display: grid;
grid-template-columns: 32px 20px 32px 1fr;
gap: 8px;
align-items: center;
padding: 6px 3px;
border-bottom: 1px solid #f0f0f0;
min-height: 40px;
}

.icon-row:last-child {
border-bottom: none;
}

.icon-image {
width: 32px;
height: 32px;
object-fit: contain;
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
image-rendering: pixelated;
}

.bmp-processed {
filter: none;
}

.arrow {
text-align: center;
color: #999;
font-size: 14px;
font-weight: bold;
}

.icon-info {
padding-left: 8px;
padding-right: 5px;
min-width: 0;
width: 100%;
}

.icon-name {
color: #333;
line-height: 1.3;
word-wrap: break-word;
overflow-wrap: break-word;
hyphens: auto;
text-align: left;
font-size: 11px;
max-width: 200px;
}

.loading {
text-align: center;
padding: 20px;
color: #666;
width: 100%;
}

.no-results {
text-align: center;
padding: 40px;
color: #666;
width: 100%;
font-size: 14px;
}

.columns-container {
display: flex;
flex-wrap: wrap;
gap: 15px;
width: 100%;
justify-content: center; /* Центрируем колонки */
}

.masonry-column {
display: flex;
flex-direction: column;
gap: 15px;
}

/* Адаптивность для мобильных устройств */
@media (max-width: 768px) {
.container {
max-width: 100%;
padding: 0 10px;
}

.icon-column {
width: 100%; /* На мобильных занимает всю ширину */
max-width: 380px; /* Но не больше фиксированной ширины */
}

.search-input {
width: 100%;
max-width: 300px;
}

.controls-row {
flex-wrap: wrap;
gap: 10px;
}
}
</style>
</head>
<body>
<div class="container">
<h1>Сравнение иконок ИСИХОГИ старой и новой версий</h1>

<div class="search-container">
<input type="text"
id="searchInput"
class="search-input"
placeholder="Поиск по названию иконки...">
</div>

<div class="controls-row">
<div class="stats">
Всего иконок для сравнения: <span id="totalCount">0</span> |
Отображено: <span id="shownCount">0</span>
</div>
<select id="groupSelect" class="group-select" style="display: none;">
<option value="">Все группы</option>
</select>
<div class="show-groups-toggle">
<input type="checkbox" id="showGroupsToggle" class="toggle-checkbox" checked>
<label for="showGroupsToggle">Показывать группы</label>
</div>
</div>

<div class="comparison-container" id="iconsContainer">
<div class="loading">Загрузка иконок...</div>
</div>
</div>

<script>
let allIcons = [];
let filteredIcons = [];
let availableGroups = [];
let showGroups = true;

// Функция для разбивки больших групп на части
function splitLargeGroup(icons, maxItems = 15) {
if (icons.length <= maxItems) {
return [icons];
}

const chunks = [];
for (let i = 0; i < icons.length; i += maxItems) {
chunks.push(icons.slice(i, i + maxItems));
}
return chunks;
}

// Функция для расчета примерной высоты группы
function calculateGroupHeight(iconsCount) {
const headerHeight = 40;
const rowHeight = 32;
const padding = 16;
return headerHeight + (iconsCount * rowHeight) + padding;
}

// Функция для создания адаптивной masonry компоновки
function createAdaptiveMasonryLayout(groupsChunks) {
const container = document.getElementById('iconsContainer');
const containerWidth = container.clientWidth;
const columnWidth = 380 + 15; // ширина колонки + gap

// Ограничиваем максимальное количество колонок исходя из максимальной ширины 1200px
const maxColumns = Math.floor(1200 / columnWidth);
const availableColumns = Math.min(maxColumns, Math.max(1, Math.floor(containerWidth / columnWidth)));

console.log(`Container width: ${containerWidth}, Columns: ${availableColumns}`);

// Создаем колонки
const columns = Array.from({ length: availableColumns }, () => {
const col = document.createElement('div');
col.className = 'masonry-column';
return col;
});

// Массив для отслеживания высоты каждой колонки
const columnHeights = Array(availableColumns).fill(0);

// Распределяем группы по колонкам (алгоритм "наименьшая высота")
groupsChunks.forEach(group => {
// Находим колонку с наименьшей высотой
let minHeight = Math.min(...columnHeights);
let columnIndex = columnHeights.indexOf(minHeight);

// Добавляем группу в эту колонку
const section = createGroupSection(group.name, group.icons);
columns[columnIndex].appendChild(section);

// Обновляем высоту колонки
columnHeights[columnIndex] += group.height;
});

return columns;
}

// Функция для отображения иконок с группировкой (masonry компоновка)
function renderIconsWithGroups() {
const container = document.getElementById('iconsContainer');
container.innerHTML = '';

if (filteredIcons.length === 0) {
container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>';
updateStats();
return;
}

// Группируем иконки
const { groups, noGroup } = groupIcons(filteredIcons);

// Собираем все группы и разбиваем большие
const allGroupChunks = [];

// Обрабатываем группы с иконками
Object.entries(groups).forEach(([groupName, icons]) => {
const chunks = splitLargeGroup(icons, 15);
chunks.forEach((chunk, index) => {
const chunkName = chunks.length > 1 ? `${groupName} (${index + 1}/${chunks.length})` : groupName;
allGroupChunks.push({
name: chunkName,
icons: chunk,
originalGroup: groupName,
height: calculateGroupHeight(chunk.length)
});
});
});

// Добавляем иконки без группы
if (noGroup.length > 0) {
const noGroupChunks = splitLargeGroup(noGroup, 15);
noGroupChunks.forEach((chunk, index) => {
const chunkName = noGroupChunks.length > 1 ? `Прочие иконки (${index + 1}/${noGroupChunks.length})` : 'Прочие иконки';
allGroupChunks.push({
name: chunkName,
icons: chunk,
originalGroup: 'no_group',
height: calculateGroupHeight(chunk.length)
});
});
}

// Сортируем по высоте (от высоких к низким для лучшего заполнения)
allGroupChunks.sort((a, b) => b.height - a.height);

// Создаем адаптивную masonry компоновку
const columns = createAdaptiveMasonryLayout(allGroupChunks);

// Очищаем контейнер и добавляем колонки
container.innerHTML = '';
const columnsContainer = document.createElement('div');
columnsContainer.className = 'columns-container';

columns.forEach(col => {
if (col.children.length > 0) {
columnsContainer.appendChild(col);
}
});

container.appendChild(columnsContainer);
updateStats();
}

// Функция для создания секции группы
function createGroupSection(groupName, icons) {
const section = document.createElement('div');
section.className = 'group-section';

const header = document.createElement('div');
header.className = 'group-header';
header.textContent = groupName;
section.appendChild(header);

const column = createIconColumn(icons);
section.appendChild(column);

return section;
}

// Обработчик изменения размера окна
function setupResizeHandler() {
let resizeTimeout;
window.addEventListener('resize', function() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
if (showGroups && filteredIcons.length > 0) {
renderIconsWithGroups();
}
}, 250);
});
}

// Остальные функции без изменений
function processBMPWithPinkBackground(img) {
return new Promise((resolve) => {
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;

ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;

for (let i = 0; i < data.length; i += 4) {
if (data[i] === 255 && data[i + 1] === 0 && data[i + 2] === 255) {
data[i + 3] = 0;
}
}

ctx.putImageData(imageData, 0, 0);

const processedImg = new Image();
processedImg.onload = () => resolve({
element: processedImg,
processed: true
});
processedImg.src = canvas.toDataURL('image/png');
} catch (error) {
console.warn('Ошибка обработки BMP:', error);
resolve({
element: img,
processed: false
});
}
});
}

function loadImageWithBMPProcessing(src, alt) {
return new Promise((resolve) => {
const img = new Image();

img.onload = async function() {
try {
if (src.toLowerCase().endsWith('.bmp')) {
const result = await processBMPWithPinkBackground(img);
result.element.alt = alt;
resolve(result);
} else {
img.alt = alt;
resolve({ element: img, processed: false });
}
} catch (error) {
console.warn('Ошибка обработки изображения:', error, src);
img.alt = alt;
resolve({ element: img, processed: false });
}
};

img.onerror = function() {
console.warn('Ошибка загрузки изображения:', src);
const placeholder = new Image();
placeholder.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSIjRjVGNUY1Ii8+CjxwYXRoIGQ9Ik0xNiAxOEMxNi41NTIzIDE4IDE3IDE3LjU1MjMgMTcgMTdWMTFDMTcgMTAuNDQ3NyAxNi41NTIzIDEwIDE2IDEwQzE1LjQ0NzcgMTAgMTUgMTAuNDQ3NyAxNSAxMVYxN0MxNSAxNy41NTIzIDE1LjQ0NzcgMTggMTYgMThaIiBmaWxsPSIjOTk5OTk5Ii8+CjxwYXRoIGQ9Ik0xNiAyMkMxNi41NTIzIDIyIDE3IDIxLjU1MjMgMTcgMjFDMTcgMjAuNDQ3NyAxNi41NTIzIDIwIDE2IDIwQzE1LjQ0NzcgMjAgMTUgMjAuNDQ3NyAxNSAyMUMxNSAyMS41NTIzIDE1LjQ0NzcgMjIgMTYgMjJaIiBmaWxsPSIjOTk5OTk5Ii8+Cjwvc3ZnPgo=';
placeholder.alt = alt;
resolve({ element: placeholder, processed: false });
};

img.src = src;
});
}

function groupIcons(icons) {
const groups = {};
const noGroup = [];

icons.forEach(icon => {
if (icon.group && icon.group.trim() !== '') {
if (!groups[icon.group]) {
groups[icon.group] = [];
}
groups[icon.group].push(icon);
} else {
noGroup.push(icon);
}
});

return { groups, noGroup };
}

function createIconColumn(icons) {
const column = document.createElement('div');
column.className = 'icon-column';

const header = document.createElement('div');
header.className = 'column-header';

const beforeLabel = document.createElement('div');
beforeLabel.className = 'header-label';
beforeLabel.textContent = 'ДО';

const arrowSpace = document.createElement('div');
arrowSpace.className = 'arrow';
arrowSpace.textContent = '→';

const afterLabel = document.createElement('div');
afterLabel.className = 'header-label';
afterLabel.textContent = 'ПОСЛЕ';

const nameLabel = document.createElement('div');
nameLabel.className = 'header-label';
nameLabel.textContent = 'Название';
nameLabel.style.textAlign = 'center';

header.appendChild(beforeLabel);
header.appendChild(arrowSpace);
header.appendChild(afterLabel);
header.appendChild(nameLabel);

column.appendChild(header);

icons.forEach(icon => {
column.appendChild(createIconRow(icon));
});

return column;
}

function createIconRow(icon) {
const row = document.createElement('div');
row.className = 'icon-row';

const beforeContainer = document.createElement('div');
const afterContainer = document.createElement('div');

Promise.all([
loadImageWithBMPProcessing(icon.before, `${icon.name} (до)`),
loadImageWithBMPProcessing(icon.after, `${icon.name} (после)`)
]).then(([beforeResult, afterResult]) => {
const beforeImg = beforeResult.element;
const afterImg = afterResult.element;

beforeImg.className = 'icon-image' + (beforeResult.processed ? ' bmp-processed' : '');
afterImg.className = 'icon-image' + (afterResult.processed ? ' bmp-processed' : '');

beforeContainer.appendChild(beforeImg);
afterContainer.appendChild(afterImg);
});

const arrow = document.createElement('div');
arrow.className = 'arrow';
arrow.textContent = '→';

const infoDiv = document.createElement('div');
infoDiv.className = 'icon-info';

const name = document.createElement('div');
name.className = 'icon-name';
name.textContent = icon.name;
name.title = icon.name;

infoDiv.appendChild(name);

row.appendChild(beforeContainer);
row.appendChild(arrow);
row.appendChild(afterContainer);
row.appendChild(infoDiv);

return row;
}

function renderIconsWithoutGroups() {
const container = document.getElementById('iconsContainer');
container.innerHTML = '';

if (filteredIcons.length === 0) {
container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>';
updateStats();
return;
}

const sortedIcons = [...filteredIcons].sort((a, b) => {
const groupA = a.group || 'zzz';
const groupB = b.group || 'zzz';

if (groupA !== groupB) {
return groupA.localeCompare(groupB);
}

return a.name.localeCompare(b.name);
});

// Для режима без групп используем простые колонки
const itemsPerColumn = 20;
const columnCount = Math.ceil(sortedIcons.length / itemsPerColumn);

for (let i = 0; i < columnCount; i++) {
const startIndex = i * itemsPerColumn;
const endIndex = startIndex + itemsPerColumn;
const columnIcons = sortedIcons.slice(startIndex, endIndex);
const column = createIconColumn(columnIcons);
container.appendChild(column);
}

updateStats();
}

function filterIcons(searchTerm, selectedGroup = '') {
let filtered = [...allIcons];

if (searchTerm) {
const term = searchTerm.toLowerCase();
filtered = filtered.filter(icon =>
icon.name.toLowerCase().includes(term) ||
(icon.group && icon.group.toLowerCase().includes(term))
);
}

if (selectedGroup) {
filtered = filtered.filter(icon => icon.group === selectedGroup);
}

filteredIcons = filtered;
renderFilteredIcons();
}

function renderFilteredIcons() {
if (showGroups) {
renderIconsWithGroups();
} else {
renderIconsWithoutGroups();
}
}

function updateStats() {
document.getElementById('totalCount').textContent = allIcons.length;
document.getElementById('shownCount').textContent = filteredIcons.length;
}

function initGroupSelect(groups) {
const groupSelect = document.getElementById('groupSelect');
groupSelect.innerHTML = '<option value="">Все группы</option>';

groups.forEach(group => {
const option = document.createElement('option');
option.value = group;
option.textContent = group;
groupSelect.appendChild(option);
});

groupSelect.style.display = groups.length > 0 ? 'block' : 'none';

groupSelect.addEventListener('change', function() {
const searchTerm = document.getElementById('searchInput').value;
filterIcons(searchTerm, this.value);
});
}

function renderIcons(images, groups) {
allIcons = images;
filteredIcons = [...allIcons];
availableGroups = groups;

initGroupSelect(groups);
renderFilteredIcons();
}

async function loadIcons() {
try {
const script = document.createElement('script');
script.src = 'image_list.js';
document.head.appendChild(script);

script.onload = function() {
try {
const data = loadBeforeAfterImages();

if (data && data.images) {
const processedImages = data.images.map(icon => {
const displayName = icon.name.replace(/^32x32_/, '');
return {
...icon,
name: displayName
};
});

renderIcons(processedImages, data.availableGroups || []);
} else {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки данных</div>';
}
} catch (error) {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка обработки данных</div>';
}
};

script.onerror = function() {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки файла image_list.js</div>';
};

} catch (error) {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки иконок</div>';
}
}

function setupSearch() {
const searchInput = document.getElementById('searchInput');
let searchTimeout;

searchInput.addEventListener('input', function(e) {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
const selectedGroup = document.getElementById('groupSelect').value;
filterIcons(e.target.value, selectedGroup);
}, 300);
});

searchInput.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
this.value = '';
const selectedGroup = document.getElementById('groupSelect').value;
filterIcons('', selectedGroup);
}
});
}

function setupGroupsToggle() {
const toggle = document.getElementById('showGroupsToggle');

toggle.addEventListener('change', function() {
showGroups = this.checked;
renderFilteredIcons();
});
}

window.addEventListener('DOMContentLoaded', function() {
loadIcons();
setupSearch();
setupGroupsToggle();
setupResizeHandler(); // Добавляем обработчик изменения размера
});
</script>
</body>
</html>
у меня есть las файл геофизических ислледований он имеет кодировку ANSI и неправильную информацию в заглоловке (~Well information и ~Version information) , которую необходимо переработать, а по результату сделать экспорт файла в кодировке уже UTF-8 и обязательно со спецификацией. Данную программу необходимо написать в HTML. А в заголовке написать "Конвертер ЛАС файлов для OIS TERRA"

вот исходная часть которую необходимо менять

~Version information
VERS. 2.20: CWLS LAS - VERSION 2.20
WRAP. NO: One line per depth step

~Well information
# MNEM.UNIT DATA TYPE INFORMATION
# ====.================================: ===================
STRT.M 2.20:Начальная глубина
STOP.M 51.00:Конечная глубина
STEP.M 0.09999999999999964:Шаг квантования по глубине
NULL. -999999.00:Null values
COMP. :БГРЭ
WELL. :/М-563
FLD :Хампинский-3/Бюгюехский-1
LOC . LOCATION:
CNTY. COUNTY:
STAT. STATE:
CTRY. COUNTRY:
SRVC :
DATE. LOG DATE:
METD. METHOD:
~Curve information
# MNEM.UNIT API CODE CURVE DESCRIPTION
# ====.================================:====================
DEPT.M :Глубина
K.имп/мин :
TH.имп/мин :
U.имп/мин :
ИК.мСим/м :
КМВ.*10^(-5)ед.СИ :
СГК.мкР/час :
СМ.нТл :
~Parameter information block
# MNEM.UNIT VALUE DESCRIPTION
# ====.================================:====================

~Other information
# ------------------- REMARKS AREA ------------------------
# ==========================================================

~Ascii Log Data
2.20 -9999.00 -9999.00 -9999.00 3.277 -9999.00 -9999.00 -9999.00

и там дальше остальные даные

Необходимо получить шапку в таком формате:

~Version information
VERS. 2.0: CWLS LAS - VERSION 2.20
WRAP. NO: One line per depth step

~Well information
# MNEM.UNIT DATA TYPE INFORMATION
# ====.================================: ===================
STRT.M 2.20: Начальная глубина
STOP.M 51.00: Конечная глубина
DRILL.M 51.00: Глубина по бурению, м
STEP.M 0.09999999999999964: Шаг квантования по глубине
NULL. -999999.00: Поле забоя
COMP. БГРЭ: Партия
WELL. /М-563: Скважина
OBT. Хампинский-3/Бюгюехский-1: Объект
SRVC. БГРЭ: Предприятие-исполнитель
~Curve information
# MNEM.UNIT API CODE CURVE DESCRIPTION
# ====.================================:====================
MD.м :Depth
K.имп/мин :
TH.имп/мин :
U.имп/мин :
ИК.мСим/м :
КМВ.*10^(-5)ед.СИ :
СГК.мкР/час :
СМ.нТл :
~Ascii Log Data
2.20 -9999.00 -9999.00 -9999.00 3.277 -9999.00 -9999.00 -9999.00

там учти часть убирается, что-то добавляется а какие-то данные прописываются из других, и учти что после ~Ascii Log Data там у разных файлов свои данные и их много, также и методы там могут быть разные, объекты, номера и т.д.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Конвертер ЛАС файлов для OIS TERRA</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
}
h1 {
color: #2c3e50;
text-align: center;
margin-bottom: 30px;
}
.container {
background-color: #f9f9f9;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.input-section, .output-section {
margin-bottom: 20px;
}
textarea {
width: 100%;
height: 200px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: monospace;
resize: vertical;
}
button {
background-color: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
margin-right: 10px;
}
button:hover {
background-color: #2980b9;
}
.file-input {
margin-bottom: 10px;
}
.specification {
margin-top: 20px;
padding: 15px;
background-color: #e8f4fc;
border-radius: 4px;
font-size: 14px;
}
.specification h3 {
margin-top: 0;
}
.status {
margin-top: 10px;
padding: 10px;
border-radius: 4px;
}
.success {
background-color: #d4edda;
color: #155724;
}
.error {
background-color: #f8d7da;
color: #721c24;
}
</style>
</head>
<body>
<h1>Конвертер ЛАС файлов для OIS TERRA</h1>

<div class="container">
<div class="input-section">
<h2>Входной LAS файл</h2>
<input type="file" id="fileInput" class="file-input" accept=".las">
<textarea id="inputText" placeholder="Загрузите LAS файл или вставьте его содержимое здесь..."></textarea>
<button id="convertBtn">Конвертировать</button>
<button id="downloadBtn" disabled>Скачать результат</button>
<div id="status" class="status"></div>
</div>

<div class="output-section">
<h2>Результат конвертации</h2>
<textarea id="outputText" placeholder="Здесь появится результат конвертации..." readonly></textarea>
</div>

<div class="specification">
<h3>Спецификация преобразования:</h3>
<ul>
<li>Изменение кодировки с ANSI на UTF-8</li>
<li>Обновление версии LAS с 2.20 на 2.0</li>
<li>Переработка секции ~Well information:
<ul>
<li>Добавление поля DRILL.M со значением STOP.M</li>
<li>Изменение описания NULL. на "Поле забоя"</li>
<li>Заполнение COMP. значением "БГРЭ"</li>
<li>Заполнение WELL. значением "/М-563"</li>
<li>Добавление поля OBT. со значением FLD</li>
<li>Заполнение SRVC. значением "БГРЭ"</li>
</ul>
</li>
<li>Переименование DEPT.M в MD.м в секции ~Curve information</li>
<li>Удаление секций ~Parameter information block и ~Other information</li>
<li>Сохранение всех данных из секции ~Ascii Log Data</li>
</ul>
</div>
</div>

<script>
document.getElementById('fileInput').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('inputText').value = e.target.result;
};
reader.readAsText(file, 'windows-1251'); // Чтение в кодировке ANSI
}
});

document.getElementById('convertBtn').addEventListener('click', function() {
const inputText = document.getElementById('inputText').value;
if (!inputText.trim()) {
showStatus('Пожалуйста, загрузите или вставьте LAS файл', 'error');
return;
}

try {
const convertedText = convertLasFile(inputText);
document.getElementById('outputText').value = convertedText;
document.getElementById('downloadBtn').disabled = false;
showStatus('Файл успешно сконвертирован!', 'success');
} catch (error) {
showStatus('Ошибка при конвертации: ' + error.message, 'error');
}
});

document.getElementById('downloadBtn').addEventListener('click', function() {
const outputText = document.getElementById('outputText').value;
if (!outputText.trim()) {
showStatus('Нет данных для скачивания', 'error');
return;
}

const blob = new Blob([outputText], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'converted_file.las';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
});

function convertLasFile(inputText) {
// Разделяем файл на секции
const sections = splitIntoSections(inputText);

// Обрабатываем каждую секцию
let result = '';

// Обработка секции ~Version information
if (sections.version) {
result += '~Version information\n';
const lines = sections.version.split('\n');
for (let line of lines) {
if (line.startsWith('VERS.')) {
// Заменяем версию на 2.0
result += line.replace('VERS. 2.20', 'VERS. 2.0') + '\n';
} else if (line.trim() !== '') {
result += line + '\n';
}
}
result += '\n';
}

// Обработка секции ~Well information
if (sections.well) {
result += '~Well information\n';
const lines = sections.well.split('\n');

// Извлекаем значения из исходного файла
const values = extractWellValues(lines);

// Формируем новую секцию
result += '# MNEM.UNIT DATA TYPE INFORMATION\n';
result += '# ====.================================: ===================\n';
result += `STRT.M ${values.STRT || '2.20'}: Начальная глубина\n`;
result += `STOP.M ${values.STOP || '51.00'}: Конечная глубина\n`;
result += `DRILL.M ${values.STOP || '51.00'}: Глубина по бурению, м\n`;
result += `STEP.M ${values.STEP || '0.09999999999999964'}: Шаг квантования по глубине\n`;
result += `NULL. ${values.NULL || '-999999.00'}: Поле забоя\n`;
result += `COMP. БГРЭ: Партия\n`;
result += `WELL. /М-563: Скважина\n`;
result += `OBT. ${values.FLD || 'Хампинский-3/Бюгюехский-1'}: Объект\n`;
result += `SRVC. БГРЭ: Предприятие-исполнитель\n`;
result += '\n';
}

// Обработка секции ~Curve information
if (sections.curve) {
result += '~Curve information\n';
const lines = sections.curve.split('\n');
for (let line of lines) {
if (line.startsWith('DEPT.M')) {
// Заменяем DEPT.M на MD.м
result += line.replace('DEPT.M', 'MD.м') + '\n';
} else if (line.trim() !== '') {
result += line + '\n';
}
}
result += '\n';
}

// Добавляем секцию ~Ascii Log Data
if (sections.ascii) {
result += '~Ascii Log Data\n';
result += sections.ascii + '\n';
}

return result;
}

function splitIntoSections(text) {
const sections = {};
const lines = text.split('\n');
let currentSection = '';
let sectionContent = '';

for (let line of lines) {
if (line.startsWith('~')) {
// Сохраняем предыдущую секцию
if (currentSection) {
sections[currentSection] = sectionContent.trim();
}

// Начинаем новую секцию
currentSection = line.substring(1).toLowerCase().split(' ')[0];
sectionContent = '';
} else if (currentSection) {
sectionContent += line + '\n';
}
}

// Сохраняем последнюю секцию
if (currentSection) {
sections[currentSection] = sectionContent.trim();
}

return sections;
}

function extractWellValues(lines) {
const values = {};

for (let line of lines) {
// Пропускаем комментарии и пустые строки
if (line.startsWith('#') || line.trim() === '') continue;

// Разбираем строку на части
const parts = line.split(/[: ]+/);
if (parts.length >= 2) {
const mnem = parts[0].split('.')[0]; // Получаем мнемонику
const value = parts[1]; // Получаем значение

if (mnem && value) {
values[mnem] = value;
}
}
}

return values;
}

function showStatus(message, type) {
const statusDiv = document.getElementById('status');
statusDiv.textContent = message;
statusDiv.className = 'status ' + type;
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Конвертер ЛАС файлов для OIS TERRA</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 900px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
background-color: #f5f5f5;
}
h1 {
color: #2c3e50;
text-align: center;
margin-bottom: 30px;
padding-bottom: 10px;
border-bottom: 2px solid #3498db;
}
.container {
background-color: white;
border-radius: 8px;
padding: 25px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.input-section, .output-section {
margin-bottom: 25px;
}
h2 {
color: #3498db;
margin-top: 0;
}
textarea {
width: 100%;
height: 200px;
padding: 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: monospace;
resize: vertical;
font-size: 14px;
}
.button-group {
margin: 15px 0;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
button {
background-color: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
button:hover {
background-color: #2980b9;
}
button:disabled {
background-color: #95a5a6;
cursor: not-allowed;
}
.file-input {
margin-bottom: 15px;
}
.specification {
margin-top: 25px;
padding: 20px;
background-color: #e8f4fc;
border-radius: 4px;
font-size: 14px;
border-left: 4px solid #3498db;
}
.specification h3 {
margin-top: 0;
color: #2c3e50;
}
.status {
margin-top: 15px;
padding: 12px;
border-radius: 4px;
font-weight: bold;
}
.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.info {
background-color: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
.section-title {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.section-title h2 {
margin: 0;
}
.icon {
margin-right: 10px;
font-size: 20px;
}
.encoding-info {
font-size: 12px;
color: #7f8c8d;
margin-top: 5px;
}
</style>
</head>
<body>
<h1>Конвертер ЛАС файлов для OIS TERRA</h1>

<div class="container">
<div class="input-section">
<div class="section-title">
<span class="icon">📥</span>
<h2>Входной LAS файл (ANSI)</h2>
</div>
<input type="file" id="fileInput" class="file-input" accept=".las">
<textarea id="inputText" placeholder="Загрузите LAS файл или вставьте его содержимое здесь..."></textarea>
<div class="encoding-info">Текущая кодировка: ANSI (Windows-1251)</div>

<div class="button-group">
<button id="convertBtn">Конвертировать</button>
<button id="downloadBtn" disabled>Скачать результат</button>
<button id="clearBtn">Очистить</button>
</div>

<div id="status" class="status"></div>
</div>

<div class="output-section">
<div class="section-title">
<span class="icon">📤</span>
<h2>Результат конвертации (UTF-8 с BOM)</h2>
</div>
<textarea id="outputText" placeholder="Здесь появится результат конвертации..." readonly></textarea>
<div class="encoding-info">Кодировка результата: UTF-8 с BOM, окончания строк: Windows (CR+LF)</div>
</div>

<div class="specification">
<h3>Спецификация преобразования:</h3>
<ul>
<li><strong>Изменение кодировки:</strong> с ANSI (Windows-1251) на UTF-8 с BOM</li>
<li><strong>Окончания строк:</strong> преобразование в Windows формат (CR+LF)</li>
<li><strong>Версия LAS:</strong> с 2.20 на 2.0</li>
<li><strong>Переработка секции ~Well information:</strong>
<ul>
<li>Добавление поля DRILL.M со значением STOP.M</li>
<li>Изменение описания NULL. на "Поле забоя"</li>
<li>Заполнение COMP. значением "БГРЭ"</li>
<li>Заполнение WELL. значением "/М-563"</li>
<li>Добавление поля OBT. со значением FLD</li>
<li>Заполнение SRVC. значением "БГРЭ"</li>
<li>Удаление полей: LOC, CNTY, STAT, CTRY, DATE, METD</li>
</ul>
</li>
<li><strong>Секция ~Curve information:</strong> переименование DEPT.M в MD.м</li>
<li><strong>Удаление секций:</strong> ~Parameter information block и ~Other information</li>
<li><strong>Сохранение данных:</strong> всех данных из секции ~Ascii Log Data</li>
</ul>
</div>
</div>

<script>
document.getElementById('fileInput').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('inputText').value = e.target.result;
showStatus('Файл загружен успешно', 'success');
};
reader.onerror = function() {
showStatus('Ошибка при чтении файла', 'error');
};
// Чтение в кодировке ANSI (Windows-1251)
reader.readAsText(file, 'windows-1251');
}
});

document.getElementById('convertBtn').addEventListener('click', function() {
const inputText = document.getElementById('inputText').value;
if (!inputText.trim()) {
showStatus('Пожалуйста, загрузите или вставьте LAS файл', 'error');
return;
}

try {
const convertedText = convertLasFile(inputText);
document.getElementById('outputText').value = convertedText;
document.getElementById('downloadBtn').disabled = false;
showStatus('Файл успешно сконвертирован! Кодировка: UTF-8 с BOM', 'success');
} catch (error) {
showStatus('Ошибка при конвертации: ' + error.message, 'error');
console.error(error);
}
});

document.getElementById('downloadBtn').addEventListener('click', function() {
const outputText = document.getElementById('outputText').value;
if (!outputText.trim()) {
showStatus('Нет данных для скачивания', 'error');
return;
}

// Добавляем BOM для UTF-8
const bom = '\uFEFF';
const textWithBom = bom + outputText;

// Создаем Blob с правильной кодировкой
const blob = new Blob([textWithBom], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'converted_file_utf8.las';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);

showStatus('Файл успешно скачан в кодировке UTF-8 с BOM', 'success');
});

document.getElementById('clearBtn').addEventListener('click', function() {
document.getElementById('inputText').value = '';
document.getElementById('outputText').value = '';
document.getElementById('fileInput').value = '';
document.getElementById('downloadBtn').disabled = true;
showStatus('Поля очищены', 'info');
});

function convertLasFile(inputText) {
// Разделяем файл на секции
const sections = splitIntoSections(inputText);

// Обрабатываем каждую секцию
let result = '';

// Обработка секции ~Version information
if (sections.version) {
result += '~Version information\r\n';
const lines = sections.version.split('\n');
for (let line of lines) {
if (line.startsWith('VERS.')) {
// Заменяем версию на 2.0
result += line.replace('VERS. 2.20', 'VERS. 2.0') + '\r\n';
} else if (line.trim() !== '') {
result += line + '\r\n';
}
}
result += '\r\n';
}

// Обработка секции ~Well information
if (sections.well) {
result += '~Well information\r\n';
const lines = sections.well.split('\n');

// Извлекаем значения из исходного файла
const values = extractWellValues(lines);

// Формируем новую секцию
result += '# MNEM.UNIT DATA TYPE INFORMATION\r\n';
result += '# ====.================================: ===================\r\n';
result += `STRT.M ${values.STRT || '2.20'}: Начальная глубина\r\n`;
result += `STOP.M ${values.STOP || '51.00'}: Конечная глубина\r\n`;
result += `DRILL.M ${values.STOP || '51.00'}: Глубина по бурению, м\r\n`;
result += `STEP.M ${values.STEP || '0.09999999999999964'}: Шаг квантования по глубине\r\n`;
result += `NULL. ${values.NULL || '-999999.00'}: Поле забоя\r\n`;
result += `COMP. БГРЭ: Партия\r\n`;
result += `WELL. /М-563: Скважина\r\n`;
result += `OBT. ${values.FLD || 'Хампинский-3/Бюгюехский-1'}: Объект\r\n`;
result += `SRVC. БГРЭ: Предприятие-исполнитель\r\n`;
result += '\r\n';
}

// Обработка секции ~Curve information
if (sections.curve) {
result += '~Curve information\r\n';
const lines = sections.curve.split('\n');
for (let line of lines) {
if (line.startsWith('DEPT.M')) {
// Заменяем DEPT.M на MD.м
result += line.replace('DEPT.M', 'MD.м') + '\r\n';
} else if (line.trim() !== '') {
result += line + '\r\n';
}
}
result += '\r\n';
}

// Добавляем секцию ~Ascii Log Data
if (sections.ascii) {
result += '~Ascii Log Data\r\n';
// Сохраняем данные как есть, но с правильными окончаниями строк
const asciiLines = sections.ascii.split('\n');
for (let line of asciiLines) {
if (line.trim() !== '') {
result += line + '\r\n';
}
}
}

return result;
}

function splitIntoSections(text) {
const sections = {};
const lines = text.split('\n');
let currentSection = '';
let sectionContent = '';

for (let line of lines) {
if (line.startsWith('~')) {
// Сохраняем предыдущую секцию
if (currentSection) {
sections[currentSection] = sectionContent.trim();
}

// Начинаем новую секцию
currentSection = line.substring(1).toLowerCase().split(' ')[0];
sectionContent = '';
} else if (currentSection) {
sectionContent += line + '\n';
}
}

// Сохраняем последнюю секцию
if (currentSection) {
sections[currentSection] = sectionContent.trim();
}

return sections;
}

function extractWellValues(lines) {
const values = {};

for (let line of lines) {
// Пропускаем комментарии и пустые строки
if (line.startsWith('#') || line.trim() === '') continue;

// Разбираем строку на части
const parts = line.split(/[: ]+/);
if (parts.length >= 2) {
const mnem = parts[0].split('.')[0]; // Получаем мнемонику
const value = parts[1]; // Получаем значение

if (mnem && value) {
values[mnem] = value;
}
}
}

return values;
}

function showStatus(message, type) {
const statusDiv = document.getElementById('status');
statusDiv.textContent = message;
statusDiv.className = 'status ' + type;

// Автоматически скрыть сообщение через 5 секунд
setTimeout(() => {
statusDiv.textContent = '';
statusDiv.className = 'status';
}, 5000);
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Конвертер ЛАС файлов для OIS TERRA</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 900px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
background-color: #f5f5f5;
}
h1 {
color: #2c3e50;
text-align: center;
margin-bottom: 30px;
padding-bottom: 10px;
border-bottom: 2px solid #3498db;
}
.container {
background-color: white;
border-radius: 8px;
padding: 25px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.input-section, .output-section {
margin-bottom: 25px;
}
h2 {
color: #3498db;
margin-top: 0;
}
textarea {
width: 100%;
height: 200px;
padding: 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: monospace;
resize: vertical;
font-size: 14px;
}
.button-group {
margin: 15px 0;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
button {
background-color: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
button:hover {
background-color: #2980b9;
}
button:disabled {
background-color: #95a5a6;
cursor: not-allowed;
}
.file-input {
margin-bottom: 15px;
}
.specification {
margin-top: 25px;
padding: 20px;
background-color: #e8f4fc;
border-radius: 4px;
font-size: 14px;
border-left: 4px solid #3498db;
}
.specification h3 {
margin-top: 0;
color: #2c3e50;
}
.status {
margin-top: 15px;
padding: 12px;
border-radius: 4px;
font-weight: bold;
}
.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.info {
background-color: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
.section-title {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.section-title h2 {
margin: 0;
}
.icon {
margin-right: 10px;
font-size: 20px;
}
.encoding-info {
font-size: 12px;
color: #7f8c8d;
margin-top: 5px;
}
</style>
</head>
<body>
<h1>Конвертер ЛАС файлов для OIS TERRA</h1>

<div class="container">
<div class="input-section">
<div class="section-title">
<span class="icon">📥</span>
<h2>Входной LAS файл (ANSI)</h2>
</div>
<input type="file" id="fileInput" class="file-input" accept=".las">
<textarea id="inputText" placeholder="Загрузите LAS файл или вставьте его содержимое здесь..."></textarea>
<div class="encoding-info">Текущая кодировка: ANSI (Windows-1251)</div>

<div class="button-group">
<button id="convertBtn">Конвертировать</button>
<button id="downloadBtn" disabled>Скачать результат</button>
<button id="clearBtn">Очистить</button>
</div>

<div id="status" class="status"></div>
</div>

<div class="output-section">
<div class="section-title">
<span class="icon">📤</span>
<h2>Результат конвертации (UTF-8 с BOM)</h2>
</div>
<textarea id="outputText" placeholder="Здесь появится результат конвертации..." readonly></textarea>
<div class="encoding-info">Кодировка результата: UTF-8 с BOM, окончания строк: Windows (CR+LF)</div>
</div>

<div class="specification">
<h3>Спецификация преобразования:</h3>
<ul>
<li><strong>Изменение кодировки:</strong> с ANSI (Windows-1251) на UTF-8 с BOM</li>
<li><strong>Окончания строк:</strong> преобразование в Windows формат (CR+LF)</li>
<li><strong>Версия LAS:</strong> с 2.20 на 2.0</li>
<li><strong>Переработка секции ~Well information:</strong>
<ul>
<li>Добавление поля DRILL.M со значением STOP.M</li>
<li>Изменение описания NULL. на "Поле забоя"</li>
<li>COMP. - данные берутся из исходного файла</li>
<li>WELL. - данные берутся из исходного файла</li>
<li>OBT. - данные берутся из поля FLD исходного файла</li>
<li>SRVC. - данные берутся из поля COMP исходного файла</li>
<li>Удаление полей: LOC, CNTY, STAT, CTRY, DATE, METD</li>
</ul>
</li>
<li><strong>Секция ~Curve information:</strong> переименование DEPT.M в MD.м</li>
<li><strong>Удаление секций:</strong> ~Parameter information block и ~Other information</li>
<li><strong>Сохранение данных:</strong> всех данных из секции ~Ascii Log Data</li>
</ul>
</div>
</div>

<script>
document.getElementById('fileInput').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('inputText').value = e.target.result;
showStatus('Файл загружен успешно', 'success');
};
reader.onerror = function() {
showStatus('Ошибка при чтении файла', 'error');
};
// Чтение в кодировке ANSI (Windows-1251)
reader.readAsText(file, 'windows-1251');
}
});

document.getElementById('convertBtn').addEventListener('click', function() {
const inputText = document.getElementById('inputText').value;
if (!inputText.trim()) {
showStatus('Пожалуйста, загрузите или вставьте LAS файл', 'error');
return;
}

try {
const convertedText = convertLasFile(inputText);
document.getElementById('outputText').value = convertedText;
document.getElementById('downloadBtn').disabled = false;
showStatus('Файл успешно сконвертирован! Кодировка: UTF-8 с BOM', 'success');
} catch (error) {
showStatus('Ошибка при конвертации: ' + error.message, 'error');
console.error(error);
}
});

document.getElementById('downloadBtn').addEventListener('click', function() {
const outputText = document.getElementById('outputText').value;
if (!outputText.trim()) {
showStatus('Нет данных для скачивания', 'error');
return;
}

// Добавляем BOM для UTF-8
const bom = '\uFEFF';
const textWithBom = bom + outputText;

// Создаем Blob с правильной кодировкой
const blob = new Blob([textWithBom], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'converted_file_utf8.las';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);

showStatus('Файл успешно скачан в кодировке UTF-8 с BOM', 'success');
});

document.getElementById('clearBtn').addEventListener('click', function() {
document.getElementById('inputText').value = '';
document.getElementById('outputText').value = '';
document.getElementById('fileInput').value = '';
document.getElementById('downloadBtn').disabled = true;
showStatus('Поля очищены', 'info');
});

function convertLasFile(inputText) {
// Разделяем файл на секции
const sections = splitIntoSections(inputText);

// Обрабатываем каждую секцию
let result = '';

// Обработка секции ~Version information
if (sections.version) {
result += '~Version information\r\n';
const lines = sections.version.split('\n');
for (let line of lines) {
if (line.startsWith('VERS.')) {
// Заменяем версию на 2.0 - исправленная строка
result += line.replace('VERS. 2.20', 'VERS. 2.0').replace('VERS. 2.20:', 'VERS. 2.0:') + '\r\n';
} else if (line.trim() !== '') {
result += line + '\r\n';
}
}
result += '\r\n';
}

// Обработка секции ~Well information
if (sections.well) {
result += '~Well information\r\n';
const lines = sections.well.split('\n');

// Извлекаем значения из исходного файла
const values = extractWellValues(lines);

// Формируем новую секцию
result += '# MNEM.UNIT DATA TYPE INFORMATION\r\n';
result += '# ====.================================: ===================\r\n';
result += `STRT.M ${values.STRT || '2.20'}: Начальная глубина\r\n`;
result += `STOP.M ${values.STOP || '51.00'}: Конечная глубина\r\n`;
result += `DRILL.M ${values.STOP || '51.00'}: Глубина по бурению, м\r\n`;
result += `STEP.M ${values.STEP || '0.09999999999999964'}: Шаг квантования по глубине\r\n`;
result += `NULL. ${values.NULL || '-999999.00'}: Поле забоя\r\n`;
result += `COMP. ${values.COMP || 'БГРЭ'}: Партия\r\n`;
result += `WELL. ${values.WELL || '/М-563'}: Скважина\r\n`;
result += `OBT. ${values.FLD || 'Хампинский-3/Бюгюехский-1'}: Объект\r\n`;
result += `SRVC. ${values.COMP || 'БГРЭ'}: Предприятие-исполнитель\r\n`;
result += '\r\n';
}

// Обработка секции ~Curve information
if (sections.curve) {
result += '~Curve information\r\n';
const lines = sections.curve.split('\n');
for (let line of lines) {
if (line.startsWith('DEPT.M')) {
// Заменяем DEPT.M на MD.м
result += line.replace('DEPT.M', 'MD.м') + '\r\n';
} else if (line.trim() !== '') {
result += line + '\r\n';
}
}
result += '\r\n';
}

// Добавляем секцию ~Ascii Log Data
if (sections.ascii) {
result += '~Ascii Log Data\r\n';
// Сохраняем данные как есть, но с правильными окончаниями строк
const asciiLines = sections.ascii.split('\n');
for (let line of asciiLines) {
if (line.trim() !== '') {
result += line + '\r\n';
}
}
}

return result;
}

function splitIntoSections(text) {
const sections = {};
const lines = text.split('\n');
let currentSection = '';
let sectionContent = '';

for (let line of lines) {
if (line.startsWith('~')) {
// Сохраняем предыдущую секцию
if (currentSection) {
sections[currentSection] = sectionContent.trim();
}

// Начинаем новую секцию
currentSection = line.substring(1).toLowerCase().split(' ')[0];
sectionContent = '';
} else if (currentSection) {
sectionContent += line + '\n';
}
}

// Сохраняем последнюю секцию
if (currentSection) {
sections[currentSection] = sectionContent.trim();
}

return sections;
}

function extractWellValues(lines) {
const values = {};

for (let line of lines) {
// Пропускаем комментарии и пустые строки
if (line.startsWith('#') || line.trim() === '') continue;

// Разбираем строку на части - исправленный парсинг
const colonIndex = line.indexOf(':');
if (colonIndex !== -1) {
const beforeColon = line.substring(0, colonIndex).trim();
const afterColon = line.substring(colonIndex + 1).trim();

// Разделяем часть до двоеточия на мнемонику и значение
const parts = beforeColon.split(/\s+/);
if (parts.length >= 2) {
const mnem = parts[0].split('.')[0]; // Получаем мнемонику
const value = parts[1]; // Получаем значение

if (mnem) {
// Сохраняем значение из поля (часть до двоеточия)
values[mnem] = value;
// Также сохраняем описание (часть после двоеточия) если нужно
values[mnem + '_DESC'] = afterColon;
}
} else if (parts.length === 1) {
// Случай когда есть только мнемоника без значения (например "COMP. :БГРЭ")
const mnem = parts[0].split('.')[0];
if (mnem) {
// Значение берем из части после двоеточия
values[mnem] = afterColon;
values[mnem + '_DESC'] = afterColon;
}
}
}
}

return values;
}

function showStatus(message, type) {
const statusDiv = document.getElementById('status');
statusDiv.textContent = message;
statusDiv.className = 'status ' + type;

// Автоматически скрыть сообщение через 5 секунд
setTimeout(() => {
statusDiv.textContent = '';
statusDiv.className = 'status';
}, 5000);
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Конвертер ЛАС файлов для OIS TERRA</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 900px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
background-color: #f5f5f5;
}
h1 {
color: #2c3e50;
text-align: center;
margin-bottom: 30px;
padding-bottom: 10px;
border-bottom: 2px solid #3498db;
}
.container {
background-color: white;
border-radius: 8px;
padding: 25px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.input-section, .output-section {
margin-bottom: 25px;
}
h2 {
color: #3498db;
margin-top: 0;
}
textarea {
width: 100%;
height: 200px;
padding: 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: monospace;
resize: vertical;
font-size: 14px;
}
.button-group {
margin: 15px 0;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
button {
background-color: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
button:hover {
background-color: #2980b9;
}
button:disabled {
background-color: #95a5a6;
cursor: not-allowed;
}
.file-input {
margin-bottom: 15px;
}
.specification {
margin-top: 25px;
padding: 20px;
background-color: #e8f4fc;
border-radius: 4px;
font-size: 14px;
border-left: 4px solid #3498db;
}
.specification h3 {
margin-top: 0;
color: #2c3e50;
}
.status {
margin-top: 15px;
padding: 12px;
border-radius: 4px;
font-weight: bold;
}
.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.info {
background-color: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
.section-title {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.section-title h2 {
margin: 0;
}
.icon {
margin-right: 10px;
font-size: 20px;
}
.encoding-info {
font-size: 12px;
color: #7f8c8d;
margin-top: 5px;
}
</style>
</head>
<body>
<h1>Конвертер ЛАС файлов для OIS TERRA</h1>

<div class="container">
<div class="input-section">
<div class="section-title">
<span class="icon">📥</span>
<h2>Входной LAS файл (ANSI)</h2>
</div>
<input type="file" id="fileInput" class="file-input" accept=".las">
<textarea id="inputText" placeholder="Загрузите LAS файл или вставьте его содержимое здесь..."></textarea>
<div class="encoding-info">Текущая кодировка: ANSI (Windows-1251)</div>

<div class="button-group">
<button id="convertBtn">Конвертировать</button>
<button id="downloadBtn" disabled>Скачать результат</button>
<button id="clearBtn">Очистить</button>
</div>

<div id="status" class="status"></div>
</div>

<div class="output-section">
<div class="section-title">
<span class="icon">📤</span>
<h2>Результат конвертации (UTF-8 с BOM)</h2>
</div>
<textarea id="outputText" placeholder="Здесь появится результат конвертации..." readonly></textarea>
<div class="encoding-info">Кодировка результата: UTF-8 с BOM, окончания строк: Windows (CR+LF)</div>
</div>

<div class="specification">
<h3>Спецификация преобразования:</h3>
<ul>
<li><strong>Изменение кодировки:</strong> с ANSI (Windows-1251) на UTF-8 с BOM</li>
<li><strong>Окончания строк:</strong> преобразование в Windows формат (CR+LF)</li>
<li><strong>Версия LAS:</strong> с 2.20 на 2.0</li>
<li><strong>Переработка секции ~Well information:</strong>
<ul>
<li>Добавление поля DRILL.M со значением STOP.M</li>
<li>Изменение описания NULL. на "Поле забоя"</li>
<li>COMP. - данные берутся из исходного файла</li>
<li>WELL. - данные берутся из исходного файла</li>
<li>OBT. - данные берутся из поля FLD исходного файла</li>
<li>SRVC. - данные берутся из поля COMP исходного файла</li>
<li>Удаление полей: LOC, CNTY, STAT, CTRY, DATE, METD</li>
</ul>
</li>
<li><strong>Секция ~Curve information:</strong> переименование DEPT.M в MD.м</li>
<li><strong>Удаление секций:</strong> ~Parameter information block и ~Other information</li>
<li><strong>Сохранение данных:</strong> всех данных из секции ~Ascii Log Data</li>
</ul>
</div>
</div>

<script>
document.getElementById('fileInput').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('inputText').value = e.target.result;
showStatus('Файл загружен успешно', 'success');
};
reader.onerror = function() {
showStatus('Ошибка при чтении файла', 'error');
};
// Чтение в кодировке ANSI (Windows-1251)
reader.readAsText(file, 'windows-1251');
}
});

document.getElementById('convertBtn').addEventListener('click', function() {
const inputText = document.getElementById('inputText').value;
if (!inputText.trim()) {
showStatus('Пожалуйста, загрузите или вставьте LAS файл', 'error');
return;
}

try {
const convertedText = convertLasFile(inputText);
document.getElementById('outputText').value = convertedText;
document.getElementById('downloadBtn').disabled = false;
showStatus('Файл успешно сконвертирован! Кодировка: UTF-8 с BOM', 'success');
} catch (error) {
showStatus('Ошибка при конвертации: ' + error.message, 'error');
console.error(error);
}
});

document.getElementById('downloadBtn').addEventListener('click', function() {
const outputText = document.getElementById('outputText').value;
if (!outputText.trim()) {
showStatus('Нет данных для скачивания', 'error');
return;
}

// Добавляем BOM для UTF-8
const bom = '\uFEFF';
const textWithBom = bom + outputText;

// Создаем Blob с правильной кодировкой
const blob = new Blob([textWithBom], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'converted_file_utf8.las';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);

showStatus('Файл успешно скачан в кодировке UTF-8 с BOM', 'success');
});

document.getElementById('clearBtn').addEventListener('click', function() {
document.getElementById('inputText').value = '';
document.getElementById('outputText').value = '';
document.getElementById('fileInput').value = '';
document.getElementById('downloadBtn').disabled = true;
showStatus('Поля очищены', 'info');
});

function convertLasFile(inputText) {
// Разделяем файл на секции
const sections = splitIntoSections(inputText);

// Обрабатываем каждую секцию
let result = '';

// Обработка секции ~Version information
if (sections.version) {
result += '~Version information\r\n';
const lines = sections.version.split('\n');
for (let line of lines) {
if (line.startsWith('VERS.')) {
// Заменяем версию на 2.0 - улучшенная замена
let newLine = line;
// Заменяем все возможные варианты записи версии 2.20
newLine = newLine.replace(/VERS\.\s*2\.20/, 'VERS. 2.0');
newLine = newLine.replace(/VERS\.\s*2\.20:/, 'VERS. 2.0:');
newLine = newLine.replace(/2\.20\s*:/, '2.0:');
newLine = newLine.replace(/2\.20\s*CWLS/, '2.0: CWLS');
result += newLine + '\r\n';
} else if (line.trim() !== '') {
result += line + '\r\n';
}
}
result += '\r\n';
}

// Обработка секции ~Well information
if (sections.well) {
result += '~Well information\r\n';
const lines = sections.well.split('\n');

// Извлекаем значения из исходного файла
const values = extractWellValues(lines);

// Формируем новую секцию
result += '# MNEM.UNIT DATA TYPE INFORMATION\r\n';
result += '# ====.================================: ===================\r\n';
result += `STRT.M ${values.STRT || '2.20'}: Начальная глубина\r\n`;
result += `STOP.M ${values.STOP || '51.00'}: Конечная глубина\r\n`;
result += `DRILL.M ${values.STOP || '51.00'}: Глубина по бурению, м\r\n`;
result += `STEP.M ${values.STEP || '0.09999999999999964'}: Шаг квантования по глубине\r\n`;
result += `NULL. ${values.NULL || '-999999.00'}: Поле забоя\r\n`;
result += `COMP. ${values.COMP || 'БГРЭ'}: Партия\r\n`;
result += `WELL. ${values.WELL || '/М-563'}: Скважина\r\n`;
result += `OBT. ${values.FLD || 'Хампинский-3/Бюгюехский-1'}: Объект\r\n`;
result += `SRVC. ${values.COMP || 'БГРЭ'}: Предприятие-исполнитель\r\n`;
result += '\r\n';
}

// Обработка секции ~Curve information
if (sections.curve) {
result += '~Curve information\r\n';
const lines = sections.curve.split('\n');
for (let line of lines) {
if (line.startsWith('DEPT.M')) {
// Заменяем DEPT.M на MD.м
result += line.replace('DEPT.M', 'MD.м') + '\r\n';
} else if (line.trim() !== '') {
result += line + '\r\n';
}
}
result += '\r\n';
}

// Добавляем секцию ~Ascii Log Data
if (sections.ascii) {
result += '~Ascii Log Data\r\n';
// Сохраняем данные как есть, но с правильными окончаниями строк
const asciiLines = sections.ascii.split('\n');
for (let line of asciiLines) {
if (line.trim() !== '') {
result += line + '\r\n';
}
}
}

return result;
}

function splitIntoSections(text) {
const sections = {};
const lines = text.split('\n');
let currentSection = '';
let sectionContent = '';

for (let line of lines) {
if (line.startsWith('~')) {
// Сохраняем предыдущую секцию
if (currentSection) {
sections[currentSection] = sectionContent.trim();
}

// Начинаем новую секцию
currentSection = line.substring(1).toLowerCase().split(' ')[0];
sectionContent = '';
} else if (currentSection) {
sectionContent += line + '\n';
}
}

// Сохраняем последнюю секцию
if (currentSection) {
sections[currentSection] = sectionContent.trim();
}

return sections;
}

function extractWellValues(lines) {
const values = {};

for (let line of lines) {
// Пропускаем комментарии и пустые строки
if (line.startsWith('#') || line.trim() === '') continue;

// Разбираем строку на части - исправленный парсинг
const colonIndex = line.indexOf(':');
if (colonIndex !== -1) {
const beforeColon = line.substring(0, colonIndex).trim();
const afterColon = line.substring(colonIndex + 1).trim();

// Разделяем часть до двоеточия на мнемонику и значение
const parts = beforeColon.split(/\s+/);
if (parts.length >= 2) {
const mnem = parts[0].split('.')[0]; // Получаем мнемонику
const value = parts[1]; // Получаем значение

if (mnem) {
// Сохраняем значение из поля (часть до двоеточия)
values[mnem] = value;
// Также сохраняем описание (часть после двоеточия) если нужно
values[mnem + '_DESC'] = afterColon;
}
} else if (parts.length === 1) {
// Случай когда есть только мнемоника без значения (например "COMP. :БГРЭ")
const mnem = parts[0].split('.')[0];
if (mnem) {
// Значение берем из части после двоеточия
values[mnem] = afterColon;
values[mnem + '_DESC'] = afterColon;
}
}
}
}

return values;
}

function showStatus(message, type) {
const statusDiv = document.getElementById('status');
statusDiv.textContent = message;
statusDiv.className = 'status ' + type;

// Автоматически скрыть сообщение через 5 секунд
setTimeout(() => {
statusDiv.textContent = '';
statusDiv.className = 'status';
}, 5000);
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Конвертер ЛАС файлов для OIS TERRA</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 900px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
background-color: #f5f5f5;
}
h1 {
color: #2c3e50;
text-align: center;
margin-bottom: 30px;
padding-bottom: 10px;
border-bottom: 2px solid #3498db;
}
.container {
background-color: white;
border-radius: 8px;
padding: 25px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.input-section, .output-section {
margin-bottom: 25px;
}
h2 {
color: #3498db;
margin-top: 0;
}
textarea {
width: 100%;
height: 200px;
padding: 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: monospace;
resize: vertical;
font-size: 14px;
}
.button-group {
margin: 15px 0;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
button {
background-color: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
button:hover {
background-color: #2980b9;
}
button:disabled {
background-color: #95a5a6;
cursor: not-allowed;
}
.file-input {
margin-bottom: 15px;
}
.specification {
margin-top: 25px;
padding: 20px;
background-color: #e8f4fc;
border-radius: 4px;
font-size: 14px;
border-left: 4px solid #3498db;
}
.specification h3 {
margin-top: 0;
color: #2c3e50;
}
.status {
margin-top: 15px;
padding: 12px;
border-radius: 4px;
font-weight: bold;
}
.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.info {
background-color: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
.section-title {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.section-title h2 {
margin: 0;
}
.icon {
margin-right: 10px;
font-size: 20px;
}
.encoding-info {
font-size: 12px;
color: #7f8c8d;
margin-top: 5px;
}
.file-info {
background-color: #f8f9fa;
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
font-size: 14px;
}
</style>
</head>
<body>
<h1>Конвертер ЛАС файлов для OIS TERRA</h1>

<div class="container">
<div class="input-section">
<div class="section-title">
<span class="icon">📥</span>
<h2>Входной LAS файл (ANSI)</h2>
</div>
<input type="file" id="fileInput" class="file-input" accept=".las">
<div id="fileInfo" class="file-info" style="display: none;">
<strong>Загружен файл:</strong> <span id="fileName"></span>
</div>
<textarea id="inputText" placeholder="Загрузите LAS файл или вставьте его содержимое здесь..."></textarea>
<div class="encoding-info">Текущая кодировка: ANSI (Windows-1251)</div>

<div class="button-group">
<button id="convertBtn">Конвертировать</button>
<button id="downloadBtn" disabled>Скачать результат</button>
<button id="clearBtn">Очистить</button>
</div>

<div id="status" class="status"></div>
</div>

<div class="output-section">
<div class="section-title">
<span class="icon">📤</span>
<h2>Результат конвертации (UTF-8 с BOM)</h2>
</div>
<textarea id="outputText" placeholder="Здесь появится результат конвертации..." readonly></textarea>
<div class="encoding-info">Кодировка результата: UTF-8 с BOM, окончания строк: Windows (CR+LF)</div>
</div>

<div class="specification">
<h3>Спецификация преобразования:</h3>
<ul>
<li><strong>Изменение кодировки:</strong> с ANSI (Windows-1251) на UTF-8 с BOM</li>
<li><strong>Окончания строк:</strong> преобразование в Windows формат (CR+LF)</li>
<li><strong>Версия LAS:</strong> с 2.20 на 2.0</li>
<li><strong>Переработка секции ~Well information:</strong>
<ul>
<li>Добавление поля DRILL.M со значением STOP.M</li>
<li>Изменение описания NULL. на "Поле забоя"</li>
<li>COMP. - данные берутся из исходного файла</li>
<li>WELL. - данные берутся из исходного файла</li>
<li>OBT. - данные берутся из поля FLD исходного файла</li>
<li>SRVC. - данные берутся из поля COMP исходного файла</li>
<li>Удаление полей: LOC, CNTY, STAT, CTRY, DATE, METD</li>
</ul>
</li>
<li><strong>Секция ~Curve information:</strong> переименование DEPT.M в MD.м</li>
<li><strong>Удаление секций:</strong> ~Parameter information block и ~Other information</li>
<li><strong>Сохранение данных:</strong> всех данных из секции ~Ascii Log Data</li>
</ul>
</div>
</div>

<script>
let originalFileName = '';

document.getElementById('fileInput').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
originalFileName = file.name;

// Показываем информацию о файле
document.getElementById('fileName').textContent = originalFileName;
document.getElementById('fileInfo').style.display = 'block';

const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('inputText').value = e.target.result;
showStatus('Файл загружен успешно', 'success');
};
reader.onerror = function() {
showStatus('Ошибка при чтении файла', 'error');
};
// Чтение в кодировке ANSI (Windows-1251)
reader.readAsText(file, 'windows-1251');
}
});

document.getElementById('convertBtn').addEventListener('click', function() {
const inputText = document.getElementById('inputText').value;
if (!inputText.trim()) {
showStatus('Пожалуйста, загрузите или вставьте LAS файл', 'error');
return;
}

try {
const convertedText = convertLasFile(inputText);
document.getElementById('outputText').value = convertedText;
document.getElementById('downloadBtn').disabled = false;
showStatus('Файл успешно сконвертирован! Кодировка: UTF-8 с BOM', 'success');
} catch (error) {
showStatus('Ошибка при конвертации: ' + error.message, 'error');
console.error(error);
}
});

document.getElementById('downloadBtn').addEventListener('click', function() {
const outputText = document.getElementById('outputText').value;
if (!outputText.trim()) {
showStatus('Нет данных для скачивания', 'error');
return;
}

// Добавляем BOM для UTF-8
const bom = '\uFEFF';
const textWithBom = bom + outputText;

// Формируем имя файла
let downloadFileName;
if (originalFileName) {
// Убираем расширение .las если есть и добавляем приставку
const baseName = originalFileName.replace(/\.las$/i, '');
downloadFileName = `converted_${baseName}.las`;
} else {
downloadFileName = 'converted_file_utf8.las';
}

// Создаем Blob с правильной кодировкой
const blob = new Blob([textWithBom], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = downloadFileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);

showStatus(`Файл успешно скачан: ${downloadFileName}`, 'success');
});

document.getElementById('clearBtn').addEventListener('click', function() {
document.getElementById('inputText').value = '';
document.getElementById('outputText').value = '';
document.getElementById('fileInput').value = '';
document.getElementById('fileInfo').style.display = 'none';
document.getElementById('downloadBtn').disabled = true;
originalFileName = '';
showStatus('Поля очищены', 'info');
});

function convertLasFile(inputText) {
// Разделяем файл на секции
const sections = splitIntoSections(inputText);

// Обрабатываем каждую секцию
let result = '';

// Обработка секции ~Version information
if (sections.version) {
result += '~Version information\r\n';
const lines = sections.version.split('\n');
for (let line of lines) {
if (line.startsWith('VERS.')) {
// Заменяем версию на 2.0 - улучшенная замена
let newLine = line;
// Заменяем все возможные варианты записи версии 2.20
newLine = newLine.replace(/VERS\.\s*2\.20/, 'VERS. 2.0');
newLine = newLine.replace(/VERS\.\s*2\.20:/, 'VERS. 2.0:');
newLine = newLine.replace(/2\.20\s*:/, '2.0:');
newLine = newLine.replace(/2\.20\s*CWLS/, '2.0: CWLS');
result += newLine + '\r\n';
} else if (line.trim() !== '') {
result += line + '\r\n';
}
}
result += '\r\n';
}

// Обработка секции ~Well information
if (sections.well) {
result += '~Well information\r\n';
const lines = sections.well.split('\n');

// Извлекаем значения из исходного файла
const values = extractWellValues(lines);

// Формируем новую секцию
result += '# MNEM.UNIT DATA TYPE INFORMATION\r\n';
result += '# ====.================================: ===================\r\n';
result += `STRT.M ${values.STRT || '2.20'}: Начальная глубина\r\n`;
result += `STOP.M ${values.STOP || '51.00'}: Конечная глубина\r\n`;
result += `DRILL.M ${values.STOP || '51.00'}: Глубина по бурению, м\r\n`;
result += `STEP.M ${values.STEP || '0.09999999999999964'}: Шаг квантования по глубине\r\n`;
result += `NULL. ${values.NULL || '-999999.00'}: Поле забоя\r\n`;
result += `COMP. ${values.COMP || 'БГРЭ'}: Партия\r\n`;
result += `WELL. ${values.WELL || '/М-563'}: Скважина\r\n`;
result += `OBT. ${values.FLD || 'Хампинский-3/Бюгюехский-1'}: Объект\r\n`;
result += `SRVC. ${values.COMP || 'БГРЭ'}: Предприятие-исполнитель\r\n`;
result += '\r\n';
}

// Обработка секции ~Curve information
if (sections.curve) {
result += '~Curve information\r\n';
const lines = sections.curve.split('\n');
for (let line of lines) {
if (line.startsWith('DEPT.M')) {
// Заменяем DEPT.M на MD.м
result += line.replace('DEPT.M', 'MD.м') + '\r\n';
} else if (line.trim() !== '') {
result += line + '\r\n';
}
}
result += '\r\n';
}

// Добавляем секцию ~Ascii Log Data
if (sections.ascii) {
result += '~Ascii Log Data\r\n';
// Сохраняем данные как есть, но с правильными окончаниями строк
const asciiLines = sections.ascii.split('\n');
for (let line of asciiLines) {
if (line.trim() !== '') {
result += line + '\r\n';
}
}
}

return result;
}

function splitIntoSections(text) {
const sections = {};
const lines = text.split('\n');
let currentSection = '';
let sectionContent = '';

for (let line of lines) {
if (line.startsWith('~')) {
// Сохраняем предыдущую секцию
if (currentSection) {
sections[currentSection] = sectionContent.trim();
}

// Начинаем новую секцию
currentSection = line.substring(1).toLowerCase().split(' ')[0];
sectionContent = '';
} else if (currentSection) {
sectionContent += line + '\n';
}
}

// Сохраняем последнюю секцию
if (currentSection) {
sections[currentSection] = sectionContent.trim();
}

return sections;
}

function extractWellValues(lines) {
const values = {};

for (let line of lines) {
// Пропускаем комментарии и пустые строки
if (line.startsWith('#') || line.trim() === '') continue;

// Разбираем строку на части - исправленный парсинг
const colonIndex = line.indexOf(':');
if (colonIndex !== -1) {
const beforeColon = line.substring(0, colonIndex).trim();
const afterColon = line.substring(colonIndex + 1).trim();

// Разделяем часть до двоеточия на мнемонику и значение
const parts = beforeColon.split(/\s+/);
if (parts.length >= 2) {
const mnem = parts[0].split('.')[0]; // Получаем мнемонику
const value = parts[1]; // Получаем значение

if (mnem) {
// Сохраняем значение из поля (часть до двоеточия)
values[mnem] = value;
// Также сохраняем описание (часть после двоеточия) если нужно
values[mnem + '_DESC'] = afterColon;
}
} else if (parts.length === 1) {
// Случай когда есть только мнемоника без значения (например "COMP. :БГРЭ")
const mnem = parts[0].split('.')[0];
if (mnem) {
// Значение берем из части после двоеточия
values[mnem] = afterColon;
values[mnem + '_DESC'] = afterColon;
}
}
}
}

return values;
}

function showStatus(message, type) {
const statusDiv = document.getElementById('status');
statusDiv.textContent = message;
statusDiv.className = 'status ' + type;

// Автоматически скрыть сообщение через 5 секунд
setTimeout(() => {
statusDiv.textContent = '';
statusDiv.className = 'status';
}, 5000);
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Просмотр SVG иконок</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
font-family: Arial, sans-serif;
background-color: #f0f2f5;
padding: 20px;
}

.container {
max-width: 1600px;
margin: 0 auto;
}

h1 {
text-align: center;
margin-bottom: 20px;
color: #333;
font-size: 24px;
}

.controls {
display: flex;
gap: 10px;
margin-bottom: 15px;
flex-wrap: wrap;
align-items: center;
background: white;
padding: 15px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}

#folderInput {
padding: 8px;
border: 2px dashed #007bff;
border-radius: 4px;
background: white;
cursor: pointer;
font-size: 14px;
}

#loadIcons {
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}

#loadIcons:hover {
background: #0056b3;
}

#searchInput {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
flex: 1;
min-width: 200px;
font-size: 14px;
}

.stats {
margin-bottom: 15px;
padding: 10px 15px;
background: white;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
font-size: 14px;
}

/* Компактная сетка иконок */
.icons-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
gap: 8px;
margin-bottom: 20px;
}

.icon-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 8px;
border: 1px solid #e1e5e9;
border-radius: 6px;
background: white;
transition: all 0.2s ease;
cursor: pointer;
position: relative;
}

.icon-item:hover {
border-color: #007bff;
background: #f8fbff;
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0,123,255,0.2);
}

.icon-item.copied::after {
content: '✓ Скопировано!';
position: absolute;
top: -10px;
background: #28a745;
color: white;
padding: 2px 6px;
border-radius: 4px;
font-size: 10px;
z-index: 10;
}

.icon {
width: 32px;
height: 32px;
margin-bottom: 5px;
object-fit: contain;
}

.icon-name {
font-size: 10px;
color: #666;
text-align: center;
word-break: break-all;
line-height: 1.2;
max-width: 70px;
}

.folder-badge {
font-size: 9px;
color: #888;
background: #f0f0f0;
padding: 1px 4px;
border-radius: 3px;
margin-top: 3px;
max-width: 70px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.empty-state {
text-align: center;
padding: 40px;
color: #999;
font-style: italic;
background: white;
border-radius: 8px;
grid-column: 1 / -1;
}

.loading {
text-align: center;
padding: 20px;
color: #007bff;
grid-column: 1 / -1;
}

/* Группировка по папкам */
.folder-group {
margin-bottom: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
overflow: hidden;
}

.folder-header {
background: #f8f9fa;
padding: 12px 15px;
border-bottom: 1px solid #e9ecef;
font-weight: bold;
color: #495057;
font-size: 14px;
display: flex;
justify-content: between;
align-items: center;
}

.folder-count {
background: #007bff;
color: white;
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: normal;
margin-left: 10px;
}

.folder-icons {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
gap: 8px;
padding: 12px;
}

/* Режим просмотра */
.view-mode {
display: flex;
gap: 10px;
align-items: center;
}

.view-btn {
padding: 6px 12px;
border: 1px solid #ddd;
background: white;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}

.view-btn.active {
background: #007bff;
color: white;
border-color: #007bff;
}

/* Уведомление */
.notification {
position: fixed;
top: 20px;
right: 20px;
background: #28a745;
color: white;
padding: 10px 15px;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
z-index: 1000;
opacity: 0;
transform: translateY(-20px);
transition: all 0.3s ease;
}

.notification.show {
opacity: 1;
transform: translateY(0);
}

@media (max-width: 768px) {
.icons-grid {
grid-template-columns: repeat(auto-fill, minmax(70px, 1fr));
gap: 6px;
}

.folder-icons {
grid-template-columns: repeat(auto-fill, minmax(70px, 1fr));
gap: 6px;
}

.controls {
flex-direction: column;
align-items: stretch;
}

#searchInput {
min-width: auto;
}
}

@media (min-width: 1200px) {
.icons-grid {
grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
}

.folder-icons {
grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
}
}
</style>
</head>
<body>
<div class="container">
<h1>Просмотр SVG иконок</h1>

<div class="controls">
<input type="file" id="folderInput" webkitdirectory directory multiple>
<button id="loadIcons">Загрузить иконки</button>
<input type="text" id="searchInput" placeholder="Поиск по имени...">

<div class="view-mode">
<button class="view-btn active" data-view="all">Все вместе</button>
<button class="view-btn" data-view="grouped">По папкам</button>
</div>
</div>

<div class="stats" id="stats">
Выберите папку с SVG иконками
</div>

<div id="iconsContainer">
<div class="empty-state">
Иконки появятся здесь после выбора папки
</div>
</div>
</div>

<div id="notification" class="notification" style="display: none;">
SVG код скопирован в буфер обмена!
</div>

<script>
class IconViewer {
constructor() {
this.icons = [];
this.filteredIcons = [];
this.viewMode = 'all';
this.initializeEventListeners();
}

initializeEventListeners() {
document.getElementById('loadIcons').addEventListener('click', () => {
this.loadIconsFromFolder();
});

document.getElementById('searchInput').addEventListener('input', (e) => {
this.filterIcons(e.target.value);
});

document.getElementById('folderInput').addEventListener('change', () => {
this.loadIconsFromFolder();
});

// Переключение режимов просмотра
document.querySelectorAll('.view-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
document.querySelectorAll('.view-btn').forEach(b => b.classList.remove('active'));
e.target.classList.add('active');
this.viewMode = e.target.dataset.view;
this.renderIcons();
});
});
}

async loadIconsFromFolder() {
const folderInput = document.getElementById('folderInput');
const files = Array.from(folderInput.files);

const svgFiles = files.filter(file =>
file.name.toLowerCase().endsWith('.svg')
);

if (svgFiles.length === 0) {
this.showMessage('SVG файлы не найдены в выбранной папке');
return;
}

this.showLoading(`Найдено ${svgFiles.length} SVG файлов, загрузка...`);

this.icons = [];

for (const file of svgFiles) {
try {
const iconData = await this.processSvgFile(file);
this.icons.push(iconData);
} catch (error) {
console.error('Ошибка обработки файла:', file.name, error);
}
}

this.filteredIcons = [...this.icons];
this.renderIcons();
this.updateStats();
}

async processSvgFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();

reader.onload = (e) => {
const svgContent = e.target.result;
const imageUrl = URL.createObjectURL(file);

// Получаем путь папки
const fullPath = file.webkitRelativePath || file.name;
let folderPath = 'корневая папка';

if (fullPath.includes('/')) {
const parts = fullPath.split('/');
parts.pop(); // удаляем имя файла
folderPath = parts.join('/');
}

// Преобразуем SVG в одну строку
const singleLineSvg = this.svgToSingleLine(svgContent);

resolve({
name: file.name.replace('.svg', ''),
fullName: file.name,
url: imageUrl,
folder: folderPath,
fullPath: fullPath,
size: file.size,
svgCode: singleLineSvg,
originalSvg: svgContent
});
};

reader.onerror = reject;
reader.readAsText(file);
});
}

// Преобразует SVG в одну строку
svgToSingleLine(svgContent) {
return svgContent
.replace(/[\r\n]+/g, ' ') // Заменяем переносы строк на пробелы
.replace(/>\s+</g, '><') // Убираем пробелы между тегами
.replace(/\s+/g, ' ') // Заменяем множественные пробелы на один
.trim(); // Убираем пробелы в начале и конце
}

// Копирование в буфер обмена
async copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
this.showNotification('SVG код скопирован в буфер обмена!');
return true;
} catch (err) {
// Fallback для старых браузеров
const textArea = document.createElement('textarea');
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
this.showNotification('SVG код скопирован в буфер обмена!');
return true;
} catch (fallbackErr) {
this.showNotification('Ошибка копирования!', true);
return false;
} finally {
document.body.removeChild(textArea);
}
}
}

// Показ уведомления
showNotification(message, isError = false) {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.style.background = isError ? '#dc3545' : '#28a745';
notification.style.display = 'block';

setTimeout(() => {
notification.classList.add('show');
}, 10);

setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => {
notification.style.display = 'none';
}, 300);
}, 2000);
}

filterIcons(searchTerm) {
if (!searchTerm.trim()) {
this.filteredIcons = [...this.icons];
} else {
const term = searchTerm.toLowerCase();
this.filteredIcons = this.icons.filter(icon =>
icon.name.toLowerCase().includes(term) ||
icon.folder.toLowerCase().includes(term) ||
icon.fullName.toLowerCase().includes(term)
);
}
this.renderIcons();
this.updateStats();
}

renderIcons() {
const container = document.getElementById('iconsContainer');

if (this.filteredIcons.length === 0) {
container.innerHTML = '<div class="empty-state">Иконки не найдены</div>';
return;
}

if (this.viewMode === 'all') {
this.renderAllIcons(container);
} else {
this.renderGroupedIcons(container);
}
}

renderAllIcons(container) {
container.innerHTML = '<div class="icons-grid" id="iconsGrid"></div>';
const grid = document.getElementById('iconsGrid');

this.filteredIcons.forEach(icon => {
grid.appendChild(this.createIconElement(icon));
});
}

renderGroupedIcons(container) {
container.innerHTML = '';

// Группируем иконки по папкам
const folders = {};
this.filteredIcons.forEach(icon => {
if (!folders[icon.folder]) {
folders[icon.folder] = [];
}
folders[icon.folder].push(icon);
});

// Сортируем папки по имени
const sortedFolders = Object.keys(folders).sort();

sortedFolders.forEach(folderName => {
const folderIcons = folders[folderName];
const folderGroup = document.createElement('div');
folderGroup.className = 'folder-group';

const folderHeader = document.createElement('div');
folderHeader.className = 'folder-header';

const folderNameSpan = document.createElement('span');
folderNameSpan.textContent = folderName;

const folderCount = document.createElement('span');
folderCount.className = 'folder-count';
folderCount.textContent = folderIcons.length;

folderHeader.appendChild(folderNameSpan);
folderHeader.appendChild(folderCount);

const folderIconsGrid = document.createElement('div');
folderIconsGrid.className = 'folder-icons';

folderIcons.forEach(icon => {
const iconElement = this.createIconElement(icon);
// Убираем бейдж папки в режиме группировки
const folderBadge = iconElement.querySelector('.folder-badge');
if (folderBadge) {
folderBadge.remove();
}
folderIconsGrid.appendChild(iconElement);
});

folderGroup.appendChild(folderHeader);
folderGroup.appendChild(folderIconsGrid);
container.appendChild(folderGroup);
});
}

createIconElement(icon) {
const div = document.createElement('div');
div.className = 'icon-item';
div.title = `Кликните для копирования SVG кода\n${icon.fullName}\nПапка: ${icon.folder}`;

div.innerHTML = `
<img src="${icon.url}" alt="${icon.name}" class="icon"
onerror="this.style.display='none'; this.nextElementSibling.textContent='❌ ' + this.nextElementSibling.textContent">
<div class="icon-name">${this.truncateName(icon.name)}</div>
${this.viewMode === 'all' ? `<div class="folder-badge" title="${icon.folder}">${this.truncateFolder(icon.folder)}</div>` : ''}
`;

div.addEventListener('click', async () => {
const success = await this.copyToClipboard(icon.svgCode);

if (success) {
// Визуальная обратная связь
div.classList.add('copied');
setTimeout(() => {
div.classList.remove('copied');
}, 2000);
}
});

return div;
}

truncateName(name) {
return name.length > 12 ? name.substring(0, 10) + '...' : name;
}

truncateFolder(folder) {
if (folder === 'корневая папка') return 'корень';
return folder.length > 12 ? folder.substring(0, 10) + '...' : folder;
}

formatFileSize(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + ['B', 'KB', 'MB'][i];
}

updateStats() {
const stats = document.getElementById('stats');
if (this.icons.length === 0) {
stats.textContent = 'Выберите папку с SVG иконками';
return;
}

const total = this.icons.length;
const filtered = this.filteredIcons.length;
const folders = new Set(this.icons.map(icon => icon.folder)).size;

if (total === filtered) {
stats.textContent = `Найдено ${total} иконок в ${folders} папках`;
} else {
stats.textContent = `Показано ${filtered} из ${total} иконок (${folders} папок)`;
}
}

showMessage(message) {
document.getElementById('stats').textContent = message;
document.getElementById('iconsContainer').innerHTML =
`<div class="empty-state">${message}</div>`;
}

showLoading(message) {
document.getElementById('stats').textContent = message;
document.getElementById('iconsContainer').innerHTML =
`<div class="loading">${message}</div>`;
}
}

// Инициализация
document.addEventListener('DOMContentLoaded', () => {
new IconViewer();
});
</script>
</body>
</html>
Рорр
Прикрепления:
ddddd.noext (77.2 Kb)
<!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: Arial, sans-serif;
background-color: #fff;
padding: 10px;
font-size: 11px;
}

.container {
max-width: 1200px;
margin: 0 auto;
}

h1 {
text-align: center;
color: #333;
font-size: 16px;
margin-bottom: 15px;
font-weight: bold;
}

.search-container {
margin: 15px 0;
text-align: center;
}

.icon-search-input {
width: 300px;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}

.icon-search-input:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}

.controls-row {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
margin: 12px 0;
padding: 8px;
background: #f0f0f0;
border-radius: 4px;
border: 1px solid #ddd;
}

.stats {
font-size: 12px;
}

.group-select {
padding: 6px 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 12px;
background: white;
}

.show-groups-toggle {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
order: 2;
}

.toggle-checkbox {
width: 16px;
height: 16px;
}

.comparison-container {
display: flex;
flex-wrap: wrap;
gap: 15px;
align-items: flex-start;
justify-content: center;
}

.group-section {
break-inside: avoid;
}

.group-header {
background: #f8f9fa;
padding: 6px 10px;
border-radius: 4px;
margin-bottom: 8px;
font-weight: bold;
color: #495057;
font-size: 13px;
}

.icon-column {
border: 1px solid #e0e0e0;
border-radius: 5px;
padding: 8px;
background: #fafafa;
width: 380px;
flex-shrink: 0;
}

.column-header {
display: grid;
grid-template-columns: 32px 20px 32px 1fr;
gap: 8px;
align-items: center;
padding: 5px 3px;
margin-bottom: 5px;
border-bottom: 1px solid #ddd;
font-weight: bold;
text-align: center;
}

.header-label {
font-size: 11px;
color: #333;
white-space: nowrap; /* Запрещаем перенос текста */
}

.icon-row {
display: grid;
grid-template-columns: 32px 20px 32px 1fr;
gap: 8px;
align-items: center;
padding: 6px 3px;
border-bottom: 1px solid #f0f0f0;
min-height: 40px;
}

.icon-row:last-child {
border-bottom: none;
}

.icon-image {
width: 32px;
height: 32px;
object-fit: contain;
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
image-rendering: pixelated;
}

.bmp-processed {
filter: none;
}

.arrow {
text-align: center;
color: #999;
font-size: 14px;
font-weight: bold;
white-space: nowrap; /* Запрещаем перенос стрелки */
}

.icon-info {
padding-left: 8px;
padding-right: 5px;
min-width: 0;
width: 100%;
}

.icon-name {
color: #333;
line-height: 1.3;
word-wrap: break-word;
overflow-wrap: break-word;
hyphens: auto;
text-align: left;
font-size: 11px;
max-width: 200px;
}

.loading {
text-align: center;
padding: 20px;
color: #666;
width: 100%;
}

.no-results {
text-align: center;
padding: 40px;
color: #666;
width: 100%;
font-size: 14px;
}

.columns-container {
display: flex;
flex-wrap: wrap;
gap: 15px;
width: 100%;
justify-content: center;
}

.masonry-column {
display: flex;
flex-direction: column;
gap: 15px;
}

/* Адаптивность для мобильных устройств */
@media (max-width: 768px) {
.container {
max-width: 100%;
padding: 0 10px;
}

.icon-column {
width: 100%;
max-width: 380px;
}

.icon-search-input {
width: 100%;
max-width: 300px;
}

.controls-row {
flex-wrap: wrap;
gap: 10px;
}

/* На очень маленьких экранах уменьшаем отступы */
.column-header {
gap: 4px;
padding: 5px 2px;
}

.header-label {
font-size: 10px; /* Чуть уменьшаем шрифт на мобильных */
}
}

/* Для очень маленьких экранов */
@media (max-width: 480px) {
.icon-column {
width: 100%;
max-width: 100%;
}

.column-header {
grid-template-columns: 30px 16px 30px 1fr;
gap: 3px;
}

.header-label {
font-size: 9px;
}

.arrow {
font-size: 12px;
}
}
</style>
</head>
<body>
<div class="container">
<h1>Сравнение иконок ИСИХОГИ старой и новой версий</h1>

<div class="search-container">
<input type="text"
id="iconSearchInput"
class="icon-search-input"
placeholder="Поиск по названию иконки..."
autocomplete="off">
</div>

<div class="controls-row">
<div class="stats">
Всего иконок для сравнения: <span id="totalCount">0</span> |
Отображено: <span id="shownCount">0</span>
</div>
<select id="groupSelect" class="group-select" style="display: none;">
<option value="">Все группы</option>
</select>
<div class="show-groups-toggle">
<input type="checkbox" id="showGroupsToggle" class="toggle-checkbox" checked>
<label for="showGroupsToggle">Показывать группы</label>
</div>
</div>

<div class="comparison-container" id="iconsContainer">
<div class="loading">Загрузка иконок...</div>
</div>
</div>

<script>
let allIcons = [];
let filteredIcons = [];
let availableGroups = [];
let showGroups = true;

// Функция для разбивки больших групп на части
function splitLargeGroup(icons, maxItems = 15) {
if (icons.length <= maxItems) {
return [icons];
}

const chunks = [];
for (let i = 0; i < icons.length; i += maxItems) {
chunks.push(icons.slice(i, i + maxItems));
}
return chunks;
}

// Функция для расчета примерной высоты группы
function calculateGroupHeight(iconsCount) {
const headerHeight = 40;
const rowHeight = 32;
const padding = 16;
return headerHeight + (iconsCount * rowHeight) + padding;
}

// Функция для создания адаптивной masonry компоновки
function createAdaptiveMasonryLayout(groupsChunks) {
const container = document.getElementById('iconsContainer');
const containerWidth = container.clientWidth;
const columnWidth = 380 + 15;

const maxColumns = Math.floor(1200 / columnWidth);
const availableColumns = Math.min(maxColumns, Math.max(1, Math.floor(containerWidth / columnWidth)));

// Создаем колонки
const columns = Array.from({ length: availableColumns }, () => {
const col = document.createElement('div');
col.className = 'masonry-column';
return col;
});

// Массив для отслеживания высоты каждой колонки
const columnHeights = Array(availableColumns).fill(0);

// Распределяем группы по колонкам (алгоритм "наименьшая высота")
groupsChunks.forEach(group => {
let minHeight = Math.min(...columnHeights);
let columnIndex = columnHeights.indexOf(minHeight);

const section = createGroupSection(group.name, group.icons);
columns[columnIndex].appendChild(section);

columnHeights[columnIndex] += group.height;
});

return columns;
}

// Функция для отображения иконок с группировкой (masonry компоновка)
function renderIconsWithGroups() {
const container = document.getElementById('iconsContainer');
container.innerHTML = '';

if (filteredIcons.length === 0) {
container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>';
updateStats();
return;
}

// Группируем иконки
const { groups, noGroup } = groupIcons(filteredIcons);

// Собираем все группы и разбиваем большие
const allGroupChunks = [];

Object.entries(groups).forEach(([groupName, icons]) => {
const chunks = splitLargeGroup(icons, 15);
chunks.forEach((chunk, index) => {
const chunkName = chunks.length > 1 ? `${groupName} (${index + 1}/${chunks.length})` : groupName;
allGroupChunks.push({
name: chunkName,
icons: chunk,
originalGroup: groupName,
height: calculateGroupHeight(chunk.length)
});
});
});

// Добавляем иконки без группы
if (noGroup.length > 0) {
const noGroupChunks = splitLargeGroup(noGroup, 15);
noGroupChunks.forEach((chunk, index) => {
const chunkName = noGroupChunks.length > 1 ? `Прочие иконки (${index + 1}/${noGroupChunks.length})` : 'Прочие иконки';
allGroupChunks.push({
name: chunkName,
icons: chunk,
originalGroup: 'no_group',
height: calculateGroupHeight(chunk.length)
});
});
}

// Сортируем по высоте (от высоких к низким для лучшего заполнения)
allGroupChunks.sort((a, b) => b.height - a.height);

// Создаем адаптивную masonry компоновку
const columns = createAdaptiveMasonryLayout(allGroupChunks);

// Очищаем контейнер и добавляем колонки
container.innerHTML = '';
const columnsContainer = document.createElement('div');
columnsContainer.className = 'columns-container';

columns.forEach(col => {
if (col.children.length > 0) {
columnsContainer.appendChild(col);
}
});

container.appendChild(columnsContainer);
updateStats();
}

// Функция для создания секции группы
function createGroupSection(groupName, icons) {
const section = document.createElement('div');
section.className = 'group-section';

const header = document.createElement('div');
header.className = 'group-header';
header.textContent = groupName;
section.appendChild(header);

const column = createIconColumn(icons);
section.appendChild(column);

return section;
}

// Функция для создания колонки с иконками (с фиксированными заголовками)
function createIconColumn(icons) {
const column = document.createElement('div');
column.className = 'icon-column';

const header = document.createElement('div');
header.className = 'column-header';

const beforeLabel = document.createElement('div');
beforeLabel.className = 'header-label';
beforeLabel.textContent = 'ДО';
beforeLabel.style.whiteSpace = 'nowrap';

const arrowSpace = document.createElement('div');
arrowSpace.className = 'arrow';
arrowSpace.textContent = '→';
arrowSpace.style.whiteSpace = 'nowrap';

const afterLabel = document.createElement('div');
afterLabel.className = 'header-label';
afterLabel.textContent = 'ПОСЛЕ';
afterLabel.style.whiteSpace = 'nowrap';

const nameLabel = document.createElement('div');
nameLabel.className = 'header-label';
nameLabel.textContent = 'Название';
nameLabel.style.textAlign = 'center';
nameLabel.style.whiteSpace = 'nowrap';

header.appendChild(beforeLabel);
header.appendChild(arrowSpace);
header.appendChild(afterLabel);
header.appendChild(nameLabel);

column.appendChild(header);

icons.forEach(icon => {
column.appendChild(createIconRow(icon));
});

return column;
}

// Обработчик изменения размера окна
function setupResizeHandler() {
let resizeTimeout;
window.addEventListener('resize', function() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
if (showGroups && filteredIcons.length > 0) {
renderIconsWithGroups();
}
}, 250);
});
}

// Остальные функции
function processBMPWithPinkBackground(img) {
return new Promise((resolve) => {
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;

ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;

for (let i = 0; i < data.length; i += 4) {
if (data[i] === 255 && data[i + 1] === 0 && data[i + 2] === 255) {
data[i + 3] = 0;
}
}

ctx.putImageData(imageData, 0, 0);

const processedImg = new Image();
processedImg.onload = () => resolve({
element: processedImg,
processed: true
});
processedImg.src = canvas.toDataURL('image/png');
} catch (error) {
console.warn('Ошибка обработки BMP:', error);
resolve({
element: img,
processed: false
});
}
});
}

function loadImageWithBMPProcessing(src, alt) {
return new Promise((resolve) => {
const img = new Image();

img.onload = async function() {
try {
if (src.toLowerCase().endsWith('.bmp')) {
const result = await processBMPWithPinkBackground(img);
result.element.alt = alt;
resolve(result);
} else {
img.alt = alt;
resolve({ element: img, processed: false });
}
} catch (error) {
console.warn('Ошибка обработки изображения:', error, src);
img.alt = alt;
resolve({ element: img, processed: false });
}
};

img.onerror = function() {
console.warn('Ошибка загрузки изображения:', src);
const placeholder = new Image();
placeholder.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSIjRjVGNUY1Ii8+CjxwYXRoIGQ9Ik0xNiAxOEMxNi41NTIzIDE4IDE3IDE3LjU1MjMgMTcgMTdWMTFDMTcgMTAuNDQ3NyAxNi41NTIzIDEwIDE2IDEwQzE1LjQ0NzcgMTAgMTUgMTAuNDQ3NyAxNSAxMVYxN0MxNSAxNy41NTIzIDE1LjQ0NzcgMTggMTYgMThaIiBmaWxsPSIjOTk5OTk5Ii8+CjxwYXRoIGQ9Ik0xNiAyMkMxNi41NTIzIDIyIDE3IDIxLjU1MjMgMTcgMjFDMTcgMjAuNDQ3NyAxNi41NTIzIDIwIDE2IDIwQzE1LjQ0NzcgMjAgMTUgMjAuNDQ3NyAxNSAyMUMxNSAyMS41NTIzIDE1LjQ0NzcgMjIgMTYgMjJaIiBmaWxsPSIjOTk5OTk5Ii8+Cjwvc3ZnPgo=';
placeholder.alt = alt;
resolve({ element: placeholder, processed: false });
};

img.src = src;
});
}

function groupIcons(icons) {
const groups = {};
const noGroup = [];

icons.forEach(icon => {
if (icon.group && icon.group.trim() !== '') {
if (!groups[icon.group]) {
groups[icon.group] = [];
}
groups[icon.group].push(icon);
} else {
noGroup.push(icon);
}
});

return { groups, noGroup };
}

function createIconRow(icon) {
const row = document.createElement('div');
row.className = 'icon-row';

const beforeContainer = document.createElement('div');
const afterContainer = document.createElement('div');

Promise.all([
loadImageWithBMPProcessing(icon.before, `${icon.name} (до)`),
loadImageWithBMPProcessing(icon.after, `${icon.name} (после)`)
]).then(([beforeResult, afterResult]) => {
const beforeImg = beforeResult.element;
const afterImg = afterResult.element;

beforeImg.className = 'icon-image' + (beforeResult.processed ? ' bmp-processed' : '');
afterImg.className = 'icon-image' + (afterResult.processed ? ' bmp-processed' : '');

beforeContainer.appendChild(beforeImg);
afterContainer.appendChild(afterImg);
});

const arrow = document.createElement('div');
arrow.className = 'arrow';
arrow.textContent = '→';

const infoDiv = document.createElement('div');
infoDiv.className = 'icon-info';

const name = document.createElement('div');
name.className = 'icon-name';
name.textContent = icon.name;
name.title = icon.name;

infoDiv.appendChild(name);

row.appendChild(beforeContainer);
row.appendChild(arrow);
row.appendChild(afterContainer);
row.appendChild(infoDiv);

return row;
}

function renderIconsWithoutGroups() {
const container = document.getElementById('iconsContainer');
container.innerHTML = '';

if (filteredIcons.length === 0) {
container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>';
updateStats();
return;
}

const sortedIcons = [...filteredIcons].sort((a, b) => {
const groupA = a.group || 'zzz';
const groupB = b.group || 'zzz';

if (groupA !== groupB) {
return groupA.localeCompare(groupB);
}

return a.name.localeCompare(b.name);
});

const itemsPerColumn = 20;
const columnCount = Math.ceil(sortedIcons.length / itemsPerColumn);

for (let i = 0; i < columnCount; i++) {
const startIndex = i * itemsPerColumn;
const endIndex = startIndex + itemsPerColumn;
const columnIcons = sortedIcons.slice(startIndex, endIndex);
const column = createIconColumn(columnIcons);
container.appendChild(column);
}

updateStats();
}

function filterIcons(searchTerm, selectedGroup = '') {
let filtered = [...allIcons];

if (searchTerm) {
const term = searchTerm.toLowerCase();
filtered = filtered.filter(icon =>
icon.name.toLowerCase().includes(term) ||
(icon.group && icon.group.toLowerCase().includes(term))
);
}

if (selectedGroup) {
filtered = filtered.filter(icon => icon.group === selectedGroup);
}

filteredIcons = filtered;
renderFilteredIcons();
}

function renderFilteredIcons() {
if (showGroups) {
renderIconsWithGroups();
} else {
renderIconsWithoutGroups();
}
}

function updateStats() {
document.getElementById('totalCount').textContent = allIcons.length;
document.getElementById('shownCount').textContent = filteredIcons.length;
}

function initGroupSelect(groups) {
const groupSelect = document.getElementById('groupSelect');
groupSelect.innerHTML = '<option value="">Все группы</option>';

groups.forEach(group => {
const option = document.createElement('option');
option.value = group;
option.textContent = group;
groupSelect.appendChild(option);
});

groupSelect.style.display = groups.length > 0 ? 'block' : 'none';

groupSelect.addEventListener('change', function() {
const searchTerm = document.getElementById('iconSearchInput').value;
filterIcons(searchTerm, this.value);
});
}

function renderIcons(images, groups) {
allIcons = images;
filteredIcons = [...allIcons];
availableGroups = groups;

initGroupSelect(groups);
renderFilteredIcons();
}

async function loadIcons() {
try {
const script = document.createElement('script');
script.src = 'image_list.js';
document.head.appendChild(script);

script.onload = function() {
try {
const data = loadBeforeAfterImages();

if (data && data.images) {
const processedImages = data.images.map(icon => {
const displayName = icon.name.replace(/^32x32_/, '');
return {
...icon,
name: displayName
};
});

renderIcons(processedImages, data.availableGroups || []);
} else {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки данных</div>';
}
} catch (error) {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка обработки данных</div>';
}
};

script.onerror = function() {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки файла image_list.js</div>';
};

} catch (error) {
document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки иконок</div>';
}
}

function setupSearch() {
const searchInput = document.getElementById('iconSearchInput');
let searchTimeout;

searchInput.addEventListener('input', function(e) {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
const selectedGroup = document.getElementById('groupSelect').value;
filterIcons(e.target.value, selectedGroup);
}, 300);
});

searchInput.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
this.value = '';
const selectedGroup = document.getElementById('groupSelect').value;
filterIcons('', selectedGroup);
}
});

// Предотвращаем всплытие события, чтобы не активировался глобальный поиск Hugo
searchInput.addEventListener('keypress', function(e) {
e.stopPropagation();
});

searchInput.addEventListener('keydown', function(e) {
e.stopPropagation();
});
}

function setupGroupsToggle() {
const toggle = document.getElementById('showGroupsToggle');

toggle.addEventListener('change', function() {
showGroups = this.checked;
renderFilteredIcons();
});
}

window.addEventListener('DOMContentLoaded', function() {
loadIcons();
setupSearch();
setupGroupsToggle();
setupResizeHandler();
});
</script>
</body>
</html>
Поиск:
Новый ответ
Имя:
Текст сообщения: