Отзывы и предложения к софту от 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>
/* Стили остаются без изменений */
* {
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

body {
margin: 0;
padding: 20px;
background-color: #f5f7fa;
color: #333;
}

.container {
max-width: 1200px;
margin: 0 auto;
background-color: white;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
padding: 25px;
}

h1 {
color: #2c3e50;
text-align: center;
margin-bottom: 30px;
border-bottom: 2px solid #3498db;
padding-bottom: 15px;
}

.section {
margin-bottom: 30px;
padding: 20px;
border-radius: 8px;
background-color: #f8f9fa;
}

.section-title {
font-size: 1.3rem;
color: #2c3e50;
margin-bottom: 15px;
display: flex;
align-items: center;
}

.form-group {
margin-bottom: 15px;
}

label {
display: block;
margin-bottom: 5px;
font-weight: 600;
color: #2c3e50;
}

input, textarea, select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}

textarea {
min-height: 100px;
resize: vertical;
}

button {
background-color: #3498db;
color: white;
border: none;
padding: 12px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
font-weight: 600;
transition: background-color 0.3s;
}

button:hover {
background-color: #2980b9;
}

button.secondary {
background-color: #95a5a6;
}

button.secondary:hover {
background-color: #7f8c8d;
}

table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}

th, td {
border: 1px solid #ddd;
padding: 12px;
text-align: left;
}

th {
background-color: #3498db;
color: white;
}

tr:nth-child(even) {
background-color: #f2f2f2;
}

.preview-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-top: 20px;
}

.preview-item {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
width: 200px;
text-align: center;
background-color: white;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

.preview-svg {
height: 100px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
}

.preview-svg svg {
max-width: 100%;
max-height: 100%;
}

.preview-code {
font-weight: bold;
color: #2c3e50;
}

.preview-name {
color: #7f8c8d;
font-size: 0.9rem;
}

.hidden {
display: none;
}

.message {
padding: 15px;
border-radius: 4px;
margin: 15px 0;
}

.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}

.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}

.actions {
display: flex;
gap: 10px;
margin-top: 20px;
}

.file-input-container {
position: relative;
overflow: hidden;
display: inline-block;
width: 100%;
}

.file-input-container input[type=file] {
position: absolute;
left: 0;
top: 0;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
}

.file-input-button {
display: block;
padding: 10px;
background: #f8f9fa;
border: 2px dashed #3498db;
border-radius: 4px;
text-align: center;
color: #3498db;
font-weight: 600;
}

.file-list {
margin-top: 10px;
max-height: 150px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
}

.file-item {
display: flex;
justify-content: space-between;
padding: 5px 0;
border-bottom: 1px solid #eee;
}

.file-item:last-child {
border-bottom: none;
}

.file-name {
flex-grow: 1;
}

.file-size {
color: #7f8c8d;
font-size: 0.8rem;
}

.data-table-container {
overflow-x: auto;
}

.help-text {
font-size: 0.9rem;
color: #7f8c8d;
margin-top: 5px;
}
</style>
</head>
<body>
<div class="container">
<h1>Генератор шаблонов литологии</h1>

<div class="section">
<h2 class="section-title">1. Загрузка SVG-файлов с текстурами</h2>
<div class="form-group">
<label>Выберите папку с SVG-файлами:</label>
<div class="file-input-container">
<div class="file-input-button">Выбрать папку с SVG-файлами</div>
<input type="file" id="svgFolderInput" webkitdirectory multiple>
</div>
<div class="help-text">SVG-файлы должны быть названы в формате "Код_Породы Название.svg", например: "1000 Лед.svg"</div>
</div>

<div class="form-group">
<label>Путь к SVG файлам в программе:</label>
<input type="text" id="svgFilePath" placeholder="C:/Users/User/AppData/Local/OISTerra/OISTerraManager/svg/" value="C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/">
<div class="help-text">Укажите путь, который будет использоваться в сгенерированном JSON файле</div>
</div>

<div id="fileList" class="file-list hidden">
<!-- Список загруженных файлов будет здесь -->
</div>
</div>

<div class="section">
<h2 class="section-title">2. Ввод данных о породах</h2>
<div class="form-group">
<label>Добавить данные о породах:</label>
<div class="help-text">Введите данные в формате: Код_Породы Название (каждая порода с новой строки)</div>
<textarea id="rockDataInput" placeholder="1300 Суглинок
1700 Илы
1800 Торф
1400 Супесь
2300 Аргиллит
2400 Алевролит"></textarea>
</div>

<div class="actions">
<button id="parseDataBtn">Обработать данные</button>
<button id="addRockBtn" class="secondary">Добавить породу вручную</button>
</div>

<div id="rockTableContainer" class="data-table-container hidden">
<h3>Список пород:</h3>
<table id="rockTable">
<thead>
<tr>
<th>Код породы</th>
<th>Наименование породы</th>
<th>SVG файл</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="rockTableBody">
<!-- Данные о породах будут здесь -->
</tbody>
</table>
</div>
</div>

<div class="section">
<h2 class="section-title">3. Предварительный просмотр</h2>
<div id="previewContainer" class="preview-container">
<!-- Предварительный просмотр будет здесь -->
</div>
</div>

<div class="section">
<h2 class="section-title">4. Генерация шаблона</h2>
<div class="form-group">
<label>Название палитры:</label>
<input type="text" id="paletteName" value="тест_лито">
</div>

<div class="form-group">
<label>Название шаблона корреляции:</label>
<input type="text" id="templateName" value="Корреляционный шаблон">
</div>

<div class="actions">
<button id="generateBtn">Сгенерировать JSON шаблон</button>
<button id="downloadBtn" class="secondary hidden">Скачать шаблон</button>
</div>

<div id="messageArea"></div>

<div id="jsonOutput" class="hidden">
<h3>Сгенерированный JSON:</h3>
<pre id="jsonContent"></pre>
</div>
</div>
</div>

<script>
// Переменные для хранения данных
let svgFiles = {};
let rockData = [];

// Элементы DOM
const svgFolderInput = document.getElementById('svgFolderInput');
const svgFilePathInput = document.getElementById('svgFilePath');
const fileList = document.getElementById('fileList');
const rockDataInput = document.getElementById('rockDataInput');
const parseDataBtn = document.getElementById('parseDataBtn');
const addRockBtn = document.getElementById('addRockBtn');
const rockTableContainer = document.getElementById('rockTableContainer');
const rockTableBody = document.getElementById('rockTableBody');
const previewContainer = document.getElementById('previewContainer');
const paletteNameInput = document.getElementById('paletteName');
const templateNameInput = document.getElementById('templateName');
const generateBtn = document.getElementById('generateBtn');
const downloadBtn = document.getElementById('downloadBtn');
const messageArea = document.getElementById('messageArea');
const jsonOutput = document.getElementById('jsonOutput');
const jsonContent = document.getElementById('jsonContent');

// Функция для правильного форматирования SVG контента
function formatSvgContent(svgContent) {
// В рабочем примере SVG имеет специфичную структуру с дополнительными атрибутами
// Заменяем корневой тег SVG на тот, который используется в рабочем примере
let formatted = svgContent.replace(
/<svg[^>]*>/,
`<svg id="svg1" height="142px" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" viewBox="0 0 282 142" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" width="282px" xmlns:dc="http://purl.org/dc/elements/1.1/" version="1.1">`
);

// Добавляем метаданные, которые есть в рабочем примере
formatted = formatted.replace(
/<defs id="defs1"\/>/,
`<metadata> <rdf:RDF> <cc:Work> <dc:title>ИСИХОГИ_Литология</dc:title> </cc:Work> </rdf:RDF> </metadata> <defs id="defs1"/>`
);

// В рабочем примере используются одинарные кавычки для XML декларации
// и двойные для атрибутов SVG
formatted = formatted.replace(/<?xml version="1.0" encoding="UTF-8"?>/, `<?xml version='1.0' encoding='UTF-8'?>`);

// В рабочем примере теги line имеют другой формат атрибутов
formatted = formatted.replace(
/<line ([^>]*)\/>/g,
'<line $1/>'
);

// Экранируем только амперсанды
formatted = formatted.replace(/&(?!amp;|lt;|gt;|quot;|#)/g, '&');

return formatted;
}

// Обработка загрузки SVG файлов
svgFolderInput.addEventListener('change', function(e) {
const files = Array.from(e.target.files);
svgFiles = {};

// Очистка списка файлов
fileList.innerHTML = '';

// Фильтрация только SVG файлов
const svgFilesList = files.filter(file => file.name.toLowerCase().endsWith('.svg'));

if (svgFilesList.length === 0) {
fileList.innerHTML = '<div>SVG файлы не найдены</div>';
fileList.classList.remove('hidden');
return;
}

// Обработка каждого SVG файла
svgFilesList.forEach(file => {
const fileName = file.name;
const codeMatch = fileName.match(/^(\d+)/);

if (codeMatch) {
const code = codeMatch[1];

const reader = new FileReader();
reader.onload = function(e) {
const svgContent = e.target.result;
svgFiles[code] = {
name: fileName,
content: svgContent
};

// Добавление в список файлов
const fileItem = document.createElement('div');
fileItem.className = 'file-item';
fileItem.innerHTML = `
<div class="file-name">${fileName}</div>
<div class="file-size">${(file.size / 1024).toFixed(2)} KB</div>
`;
fileList.appendChild(fileItem);

// Обновление таблицы пород, если код уже есть
updateRockTableWithSvg(code);
};
reader.readAsText(file);
}
});

fileList.classList.remove('hidden');
showMessage(`Загружено ${svgFilesList.length} SVG файлов`, 'success');
});

// Обработка данных о породах
parseDataBtn.addEventListener('click', function() {
const inputText = rockDataInput.value.trim();
if (!inputText) {
showMessage('Введите данные о породах', 'error');
return;
}

const lines = inputText.split('\n');
rockData = [];

lines.forEach(line => {
const trimmedLine = line.trim();
if (trimmedLine) {
const parts = trimmedLine.split(/\s+/);
if (parts.length >= 2) {
const code = parts[0];
const name = parts.slice(1).join(' ');
rockData.push({
code: code,
name: name,
svgFile: svgFiles[code] ? svgFiles[code].name : null
});
}
}
});

updateRockTable();
updatePreview();
showMessage(`Обработано ${rockData.length} пород`, 'success');
});

// Добавление породы вручную
addRockBtn.addEventListener('click', function() {
const code = prompt('Введите код породы:');
if (!code) return;

const name = prompt('Введите название породы:');
if (!name) return;

rockData.push({
code: code,
name: name,
svgFile: svgFiles[code] ? svgFiles[code].name : null
});

updateRockTable();
updatePreview();
showMessage(`Добавлена порода: ${code} ${name}`, 'success');
});

// Обновление таблицы пород
function updateRockTable() {
rockTableBody.innerHTML = '';

if (rockData.length === 0) {
rockTableContainer.classList.add('hidden');
return;
}

rockData.forEach((rock, index) => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${rock.code}</td>
<td>${rock.name}</td>
<td>${rock.svgFile || 'Не найден'}</td>
<td>
<button onclick="removeRock(${index})">Удалить</button>
</td>
`;
rockTableBody.appendChild(row);
});

rockTableContainer.classList.remove('hidden');
}

// Обновление таблицы при наличии SVG
function updateRockTableWithSvg(code) {
rockData.forEach(rock => {
if (rock.code === code) {
rock.svgFile = svgFiles[code].name;
}
});

updateRockTable();
updatePreview();
}

// Удаление породы
function removeRock(index) {
rockData.splice(index, 1);
updateRockTable();
updatePreview();
showMessage('Порода удалена', 'success');
}

// Обновление предварительного просмотра
function updatePreview() {
previewContainer.innerHTML = '';

if (rockData.length === 0) {
previewContainer.innerHTML = '<p>Нет данных для предварительного просмотра</p>';
return;
}

rockData.forEach(rock => {
const previewItem = document.createElement('div');
previewItem.className = 'preview-item';

let svgPreview = '<div>SVG не загружен</div>';
if (rock.svgFile && svgFiles[rock.code]) {
svgPreview = svgFiles[rock.code].content;
}

previewItem.innerHTML = `
<div class="preview-svg">${svgPreview}</div>
<div class="preview-code">${rock.code}</div>
<div class="preview-name">${rock.name}</div>
`;

previewContainer.appendChild(previewItem);
});
}

// Генерация JSON шаблона
generateBtn.addEventListener('click', function() {
if (rockData.length === 0) {
showMessage('Нет данных о породах для генерации шаблона', 'error');
return;
}

const paletteName = paletteNameInput.value || 'тест_лито';
const templateName = templateNameInput.value || 'Корреляционный шаблон';
const svgFilePath = svgFilePathInput.value || 'C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/';

// Создание элементов палитры
const paletteItems = rockData.map(rock => {
let svgContent = '';

if (rock.svgFile && svgFiles[rock.code]) {
// Правильное форматирование SVG для XML
svgContent = formatSvgContent(svgFiles[rock.code].content);
}

// Генерация случайного ID
const id = Date.now() + Math.floor(Math.random() * 1000);

// Полный путь к файлу SVG
const fullSvgFilePath = svgFilePath + rock.svgFile;

// Точный порядок атрибутов как в рабочем примере
return {
"color": "#ffffffff",
"data": `<StyledFill name="" group="" brushColor="#000000" objectName="" id="${id}" penColor="#000000">\n <SvgFill penWidth="0.4" objectName="" patternWidth="20" lineWidth="0.4" svgFilePath="${fullSvgFilePath}" svgContent="${svgContent}"/>\n</StyledFill>\n`,
"desc": rock.name,
"value": parseInt(rock.code)
};
});

// Создание основного JSON объекта с фиксированными ID как в рабочем примере
const templateJson = {
"Info": "OISTerra CorrTemplates",
"Palettes": [
{
"Items": paletteItems,
"id": Date.now(),
"name": paletteName,
"transp": true,
"type": 1
}
],
"ScoPaletteVersion": 1,
"corrTemplates": [
{
"Tracks": [
{
"allHeight": false,
"hTxtSet": {
"vis": true
},
"lineStyle": {
"fCol": "#ffc8ffff"
},
"trackId": 1763096947027,
"type": 0
},
{
"Logs": [
{
"logDataVers": 1,
"logId": 1763096947080,
"logParams": [
{
"contId": 1763096947014,
"contName": "РИГИС",
"id": 1763096947013,
"nm": "литология"
}
],
"logType": 3,
"trackId": 1763096947055
}
],
"allHeight": false,
"hTxtSet": {
"vis": false
},
"lineStyle": {
"fCol": "#ffc8ffff"
},
"trackId": 1763096947055,
"width": 50.69166666666666
}
],
"Version": {
"build": 3,
"fillData": 0,
"horizonView": 0,
"logData": 1,
"logRange": 0,
"plastBuild": 0,
"plastData": 0,
"stratBuild": 0,
"stratData": 0,
"stratWidth": 0,
"timeDepth": 0,
"trackWidth": 1,
"view": 7
},
"header": {
"textBlockList": {
"textBlocks": [
{
"blockId": 1763096947023,
"textItems": [
{
"blockId": 1763096947023,
"content": "[WellName]",
"font": {
"b": true
},
"itemId": 1763096947024,
"paramsHash": [
{
"id": -1,
"key": "[WellName]"
}
]
}
]
}
]
}
},
"id": Date.now() + 1000,
"name": templateName,
"plastList": {
"columnId": 1763096947025,
"isPlast": true,
"type": 0
}
}
],
"corrTemplatesVersion": 0
};

// Отображение JSON
jsonContent.textContent = JSON.stringify(templateJson, null, 2);
jsonOutput.classList.remove('hidden');
downloadBtn.classList.remove('hidden');

// Сохранение JSON для скачивания
window.generatedJson = templateJson;

showMessage('JSON шаблон успешно сгенерирован', 'success');
});

// Скачивание JSON файла
downloadBtn.addEventListener('click', function() {
if (!window.generatedJson) {
showMessage('Нет сгенерированного JSON для скачивания', 'error');
return;
}

const dataStr = JSON.stringify(window.generatedJson, null, 2);
const dataBlob = new Blob([dataStr], {type: 'application/json'});

const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = 'литология_шаблон.json';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);

showMessage('Файл успешно скачан', 'success');
});

// Функция для отображения сообщений
function showMessage(message, type) {
messageArea.innerHTML = `<div class="message ${type}">${message}</div>`;

// Автоматическое скрытие сообщения через 5 секунд
setTimeout(() => {
messageArea.innerHTML = '';
}, 5000);
}

// Инициализация при загрузке страницы
window.onload = function() {
// Добавляем пример данных для демонстрации
rockDataInput.value = `1300 Суглинок
1700 Илы
1800 Торф
1400 Супесь
2300 Аргиллит
2400 Алевролит`;
};
</script>
</body>
</html>
нет код должен быть таким
"data": "<StyledFill name=\"\" group=\"\" brushColor=\"#000000\" objectName=\"\" id=\"1763111511311\" penColor=\"#000000\">\n <SvgFill penWidth=\"0.4\" objectName=\"\" patternWidth=\"20\" lineWidth=\"0.4\" svgFilePath=\"C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/1000 Лед.svg\" svgContent=\"<?xml version='1.0' encoding='UTF-8'?> <svg id="svg1" height="142px" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" viewBox="0 0 282 142" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" width="282px" xmlns:dc="http://purl.org/dc/elements/1.1/" version="1.1"> <metadata> <rdf:RDF> <cc:Work> <dc:title>ИСИХОГИ_Литология</dc:title> </cc:Work> </rdf:RDF> </metadata> <defs id="defs1"/> <g id="layer1"> <line id="19935" x1="74" y1="54" y2="54" stroke-width="2" style="stroke:#000000;" x2="127"/> <line id="19939" x1="144" y1="112" y2="91" stroke-width="2" style="stroke:#000000;" x2="144"/> <line id="35611" x1="127" y1="53" y2="75" stroke-width="2" style="stroke:#000000;" x2="127"/> <line id="35612" x1="143" y1="90" y2="91" stroke-width="2" style="stroke:#000000;" x2="198"/> <line id="35613" x1="199" y1="90" y2="112" stroke-width="2" style="stroke:#000000;" x2="198"/> <line id="35614" x1="143" y1="17" y2="17" stroke-width="2" style="stroke:#000000;" x2="199"/> <line id="35615" x1="144" y1="17" y2="38" stroke-width="2" style="stroke:#000000;" x2="143"/> <line id="35618" x1="0" y1="17" y2="17" stroke-width="2" style="stroke:#000000;" x2="58"/> <line id="35620" x1="22" y1="18" y2="18" stroke-width="2" style="stroke:#000000;" x2="22"/> <line id="35621" x1="56" y1="16" y2="37" stroke-width="2" style="stroke:#000000;" x2="57"/> <line id="35622" x1="1" y1="18" y2="37" stroke-width="2" style="stroke:#000000;" x2="0"/> <line id="35625" x1="2" y1="89" y2="89" stroke-width="2" style="stroke:#000000;" x2="56"/> <line id="35626" x1="1" y1="89" y2="108" stroke-width="2" style="stroke:#000000;" x2="0"/> <line id="35627" x1="56" y1="89" y2="108" stroke-width="2" style="stroke:#000000;" x2="55"/> <line id="28652" x1="22" y1="110" y2="110" stroke-width="2" style="stroke:#000000;" x2="22"/> <line id="28672" x1="321" y1="58" y2="79" stroke-width="2" style="stroke:#000000;" x2="320"/> <line id="40169" x1="72" y1="124" y2="125" stroke-width="2" style="stroke:#000000;" x2="126"/> <line id="40166" x1="199" y1="17" y2="38" stroke-width="2" style="stroke:#000000;" x2="198"/> <line id="40167" x1="74" y1="53" y2="74" stroke-width="2" style="stroke:#000000;" x2="73"/> <line id="40170" x1="71" y1="124" y2="145" stroke-width="2" style="stroke:#000000;" x2="72"/> <line id="40171" x1="126" y1="125" y2="145" stroke-width="2" style="stroke:#000000;" x2="127"/> <line id="36675" x1="215" y1="55" y2="54" stroke-width="2" style="stroke:#000000;" x2="271"/> <line id="36676" x1="217" y1="126" y2="125" stroke-width="2" style="stroke:#000000;" x2="268"/> <line id="36677" x1="216" y1="125" y2="144" stroke-width="2" style="stroke:#000000;" x2="217"/> <line id="36678" x1="269" y1="126" y2="143" stroke-width="2" style="stroke:#000000;" x2="269"/> <line id="36679" x1="216" y1="54" y2="74" stroke-width="2" style="stroke:#000000;" x2="217"/> <line id="36680" x1="271" y1="53" y2="74" stroke-width="2" style="stroke:#000000;" x2="270"/> </g> </svg> \"/>\n</StyledFill>\n",

а у тебя получается такой
"data": "<StyledFill name=\"\" group=\"\" brushColor=\"#000000\" objectName=\"\" id=\"1763113124411\" penColor=\"#000000\">\n <SvgFill penWidth=\"0.4\" objectName=\"\" patternWidth=\"20\" lineWidth=\"0.4\" svgFilePath=\"C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/1000 Лед.svg\" svgContent=\"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<svg id=\"svg1\" height=\"142px\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" viewBox=\"0 0 282 142\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:cc=\"http://creativecommons.org/ns#\" width=\"282px\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" version=\"1.1\">\n <metadata> <rdf:RDF> <cc:Work> <dc:title>ИСИХОГИ_Литология</dc:title> </cc:Work> </rdf:RDF> </metadata> <defs id=\"defs1\"/>\n <g id=\"layer1\">\n <line id=\"19935\" x1=\"74\" y1=\"54\" x2=\"127\" y2=\"54\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"19939\" x1=\"144\" y1=\"112\" x2=\"144\" y2=\"91\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35611\" x1=\"127\" y1=\"53\" x2=\"127\" y2=\"75\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35612\" x1=\"143\" y1=\"90\" x2=\"198\" y2=\"91\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35613\" x1=\"199\" y1=\"90\" x2=\"198\" y2=\"112\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35614\" x1=\"143\" y1=\"17\" x2=\"199\" y2=\"17\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35615\" x1=\"144\" y1=\"17\" x2=\"143\" y2=\"38\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35618\" x1=\"0\" y1=\"17\" x2=\"58\" y2=\"17\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35620\" x1=\"22\" y1=\"18\" x2=\"22\" y2=\"18\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35621\" x1=\"56\" y1=\"16\" x2=\"57\" y2=\"37\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35622\" x1=\"1\" y1=\"18\" x2=\"0\" y2=\"37\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35625\" x1=\"2\" y1=\"89\" x2=\"56\" y2=\"89\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35626\" x1=\"1\" y1=\"89\" x2=\"0\" y2=\"108\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35627\" x1=\"56\" y1=\"89\" x2=\"55\" y2=\"108\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"28652\" x1=\"22\" y1=\"110\" x2=\"22\" y2=\"110\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"28672\" x1=\"321\" y1=\"58\" x2=\"320\" y2=\"79\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"40169\" x1=\"72\" y1=\"124\" x2=\"126\" y2=\"125\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"40166\" x1=\"199\" y1=\"17\" x2=\"198\" y2=\"38\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"40167\" x1=\"74\" y1=\"53\" x2=\"73\" y2=\"74\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"40170\" x1=\"71\" y1=\"124\" x2=\"72\" y2=\"145\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"40171\" x1=\"126\" y1=\"125\" x2=\"127\" y2=\"145\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"36675\" x1=\"215\" y1=\"55\" x2=\"271\" y2=\"54\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"36676\" x1=\"217\" y1=\"126\" x2=\"268\" y2=\"125\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"36677\" x1=\"216\" y1=\"125\" x2=\"217\" y2=\"144\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"36678\" x1=\"269\" y1=\"126\" x2=\"269\" y2=\"143\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"36679\" x1=\"216\" y1=\"54\" x2=\"217\" y2=\"74\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"36680\" x1=\"271\" y1=\"53\" x2=\"270\" y2=\"74\" stroke-width=\"2\" style=\"stroke:#000000;\"/></g>\n</svg>\n\"/>\n</StyledFill>\n",

и с таким неработает он непрогружается
а вот сам файл свг из него должен получится
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" width="282px" height="142px" viewBox="0 0 282 142" version="1.1" id="svg1">
<defs id="defs1"/>
<g id="layer1">
<line id="19935" x1="74" y1="54" x2="127" y2="54" stroke-width="2" style="stroke:#000000;"/><line id="19939" x1="144" y1="112" x2="144" y2="91" stroke-width="2" style="stroke:#000000;"/><line id="35611" x1="127" y1="53" x2="127" y2="75" stroke-width="2" style="stroke:#000000;"/><line id="35612" x1="143" y1="90" x2="198" y2="91" stroke-width="2" style="stroke:#000000;"/><line id="35613" x1="199" y1="90" x2="198" y2="112" stroke-width="2" style="stroke:#000000;"/><line id="35614" x1="143" y1="17" x2="199" y2="17" stroke-width="2" style="stroke:#000000;"/><line id="35615" x1="144" y1="17" x2="143" y2="38" stroke-width="2" style="stroke:#000000;"/><line id="35618" x1="0" y1="17" x2="58" y2="17" stroke-width="2" style="stroke:#000000;"/><line id="35620" x1="22" y1="18" x2="22" y2="18" stroke-width="2" style="stroke:#000000;"/><line id="35621" x1="56" y1="16" x2="57" y2="37" stroke-width="2" style="stroke:#000000;"/><line id="35622" x1="1" y1="18" x2="0" y2="37" stroke-width="2" style="stroke:#000000;"/><line id="35625" x1="2" y1="89" x2="56" y2="89" stroke-width="2" style="stroke:#000000;"/><line id="35626" x1="1" y1="89" x2="0" y2="108" stroke-width="2" style="stroke:#000000;"/><line id="35627" x1="56" y1="89" x2="55" y2="108" stroke-width="2" style="stroke:#000000;"/><line id="28652" x1="22" y1="110" x2="22" y2="110" stroke-width="2" style="stroke:#000000;"/><line id="28672" x1="321" y1="58" x2="320" y2="79" stroke-width="2" style="stroke:#000000;"/><line id="40169" x1="72" y1="124" x2="126" y2="125" stroke-width="2" style="stroke:#000000;"/><line id="40166" x1="199" y1="17" x2="198" y2="38" stroke-width="2" style="stroke:#000000;"/><line id="40167" x1="74" y1="53" x2="73" y2="74" stroke-width="2" style="stroke:#000000;"/><line id="40170" x1="71" y1="124" x2="72" y2="145" stroke-width="2" style="stroke:#000000;"/><line id="40171" x1="126" y1="125" x2="127" y2="145" stroke-width="2" style="stroke:#000000;"/><line id="36675" x1="215" y1="55" x2="271" y2="54" stroke-width="2" style="stroke:#000000;"/><line id="36676" x1="217" y1="126" x2="268" y2="125" stroke-width="2" style="stroke:#000000;"/><line id="36677" x1="216" y1="125" x2="217" y2="144" stroke-width="2" style="stroke:#000000;"/><line id="36678" x1="269" y1="126" x2="269" y2="143" stroke-width="2" style="stroke:#000000;"/><line id="36679" x1="216" y1="54" x2="217" y2="74" stroke-width="2" style="stroke:#000000;"/><line id="36680" x1="271" y1="53" x2="270" y2="74" stroke-width="2" style="stroke:#000000;"/></g>
</svg>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Генератор шаблонов литологии</title>
<style>
/* Стили остаются без изменений */
* {
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

body {
margin: 0;
padding: 20px;
background-color: #f5f7fa;
color: #333;
}

.container {
max-width: 1200px;
margin: 0 auto;
background-color: white;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
padding: 25px;
}

h1 {
color: #2c3e50;
text-align: center;
margin-bottom: 30px;
border-bottom: 2px solid #3498db;
padding-bottom: 15px;
}

.section {
margin-bottom: 30px;
padding: 20px;
border-radius: 8px;
background-color: #f8f9fa;
}

.section-title {
font-size: 1.3rem;
color: #2c3e50;
margin-bottom: 15px;
display: flex;
align-items: center;
}

.form-group {
margin-bottom: 15px;
}

label {
display: block;
margin-bottom: 5px;
font-weight: 600;
color: #2c3e50;
}

input, textarea, select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}

textarea {
min-height: 100px;
resize: vertical;
}

button {
background-color: #3498db;
color: white;
border: none;
padding: 12px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
font-weight: 600;
transition: background-color 0.3s;
}

button:hover {
background-color: #2980b9;
}

button.secondary {
background-color: #95a5a6;
}

button.secondary:hover {
background-color: #7f8c8d;
}

table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}

th, td {
border: 1px solid #ddd;
padding: 12px;
text-align: left;
}

th {
background-color: #3498db;
color: white;
}

tr:nth-child(even) {
background-color: #f2f2f2;
}

.preview-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-top: 20px;
}

.preview-item {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
width: 200px;
text-align: center;
background-color: white;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

.preview-svg {
height: 100px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
}

.preview-svg svg {
max-width: 100%;
max-height: 100%;
}

.preview-code {
font-weight: bold;
color: #2c3e50;
}

.preview-name {
color: #7f8c8d;
font-size: 0.9rem;
}

.hidden {
display: none;
}

.message {
padding: 15px;
border-radius: 4px;
margin: 15px 0;
}

.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}

.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}

.actions {
display: flex;
gap: 10px;
margin-top: 20px;
}

.file-input-container {
position: relative;
overflow: hidden;
display: inline-block;
width: 100%;
}

.file-input-container input[type=file] {
position: absolute;
left: 0;
top: 0;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
}

.file-input-button {
display: block;
padding: 10px;
background: #f8f9fa;
border: 2px dashed #3498db;
border-radius: 4px;
text-align: center;
color: #3498db;
font-weight: 600;
}

.file-list {
margin-top: 10px;
max-height: 150px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
}

.file-item {
display: flex;
justify-content: space-between;
padding: 5px 0;
border-bottom: 1px solid #eee;
}

.file-item:last-child {
border-bottom: none;
}

.file-name {
flex-grow: 1;
}

.file-size {
color: #7f8c8d;
font-size: 0.8rem;
}

.data-table-container {
overflow-x: auto;
}

.help-text {
font-size: 0.9rem;
color: #7f8c8d;
margin-top: 5px;
}
</style>
</head>
<body>
<div class="container">
<h1>Генератор шаблонов литологии</h1>

<div class="section">
<h2 class="section-title">1. Загрузка SVG-файлов с текстурами</h2>
<div class="form-group">
<label>Выберите папку с SVG-файлами:</label>
<div class="file-input-container">
<div class="file-input-button">Выбрать папку с SVG-файлами</div>
<input type="file" id="svgFolderInput" webkitdirectory multiple>
</div>
<div class="help-text">SVG-файлы должны быть названы в формате "Код_Породы Название.svg", например: "1000 Лед.svg"</div>
</div>

<div class="form-group">
<label>Путь к SVG файлам в программе:</label>
<input type="text" id="svgFilePath" placeholder="C:/Users/User/AppData/Local/OISTerra/OISTerraManager/svg/" value="C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/">
<div class="help-text">Укажите путь, который будет использоваться в сгенерированном JSON файле</div>
</div>

<div id="fileList" class="file-list hidden">
<!-- Список загруженных файлов будет здесь -->
</div>
</div>

<div class="section">
<h2 class="section-title">2. Ввод данных о породах</h2>
<div class="form-group">
<label>Добавить данные о породах:</label>
<div class="help-text">Введите данные в формате: Код_Породы Название (каждая порода с новой строки)</div>
<textarea id="rockDataInput" placeholder="1300 Суглинок
1700 Илы
1800 Торф
1400 Супесь
2300 Аргиллит
2400 Алевролит"></textarea>
</div>

<div class="actions">
<button id="parseDataBtn">Обработать данные</button>
<button id="addRockBtn" class="secondary">Добавить породу вручную</button>
</div>

<div id="rockTableContainer" class="data-table-container hidden">
<h3>Список пород:</h3>
<table id="rockTable">
<thead>
<tr>
<th>Код породы</th>
<th>Наименование породы</th>
<th>SVG файл</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="rockTableBody">
<!-- Данные о породах будут здесь -->
</tbody>
</table>
</div>
</div>

<div class="section">
<h2 class="section-title">3. Предварительный просмотр</h2>
<div id="previewContainer" class="preview-container">
<!-- Предварительный просмотр будет здесь -->
</div>
</div>

<div class="section">
<h2 class="section-title">4. Генерация шаблона</h2>
<div class="form-group">
<label>Название палитры:</label>
<input type="text" id="paletteName" value="тест_лито">
</div>

<div class="form-group">
<label>Название шаблона корреляции:</label>
<input type="text" id="templateName" value="Корреляционный шаблон">
</div>

<div class="actions">
<button id="generateBtn">Сгенерировать JSON шаблон</button>
<button id="downloadBtn" class="secondary hidden">Скачать шаблон</button>
</div>

<div id="messageArea"></div>

<div id="jsonOutput" class="hidden">
<h3>Сгенерированный JSON:</h3>
<pre id="jsonContent"></pre>
</div>
</div>
</div>

<script>
// Переменные для хранения данных
let svgFiles = {};
let rockData = [];

// Элементы DOM
const svgFolderInput = document.getElementById('svgFolderInput');
const svgFilePathInput = document.getElementById('svgFilePath');
const fileList = document.getElementById('fileList');
const rockDataInput = document.getElementById('rockDataInput');
const parseDataBtn = document.getElementById('parseDataBtn');
const addRockBtn = document.getElementById('addRockBtn');
const rockTableContainer = document.getElementById('rockTableContainer');
const rockTableBody = document.getElementById('rockTableBody');
const previewContainer = document.getElementById('previewContainer');
const paletteNameInput = document.getElementById('paletteName');
const templateNameInput = document.getElementById('templateName');
const generateBtn = document.getElementById('generateBtn');
const downloadBtn = document.getElementById('downloadBtn');
const messageArea = document.getElementById('messageArea');
const jsonOutput = document.getElementById('jsonOutput');
const jsonContent = document.getElementById('jsonContent');

// Функция для правильного форматирования SVG контента
function formatSvgContent(svgContent) {
// Убираем лишние пробелы и переносы строк, но сохраняем структуру
let formatted = svgContent
.replace(/\s+/g, ' ')
.trim();

// В рабочем примере XML декларация использует одинарные кавычки
formatted = formatted.replace(/<\?xml version="1.0" encoding="UTF-8"\?>/g, "<?xml version='1.0' encoding='UTF-8'?>");

// Экранируем только амперсанды
formatted = formatted.replace(/&(?!amp;|lt;|gt;|quot;|#)/g, '&');

return formatted;
}

// Обработка загрузки SVG файлов
svgFolderInput.addEventListener('change', function(e) {
const files = Array.from(e.target.files);
svgFiles = {};

// Очистка списка файлов
fileList.innerHTML = '';

// Фильтрация только SVG файлов
const svgFilesList = files.filter(file => file.name.toLowerCase().endsWith('.svg'));

if (svgFilesList.length === 0) {
fileList.innerHTML = '<div>SVG файлы не найдены</div>';
fileList.classList.remove('hidden');
return;
}

// Обработка каждого SVG файла
svgFilesList.forEach(file => {
const fileName = file.name;
const codeMatch = fileName.match(/^(\d+)/);

if (codeMatch) {
const code = codeMatch[1];

const reader = new FileReader();
reader.onload = function(e) {
const svgContent = e.target.result;
svgFiles[code] = {
name: fileName,
content: svgContent
};

// Добавление в список файлов
const fileItem = document.createElement('div');
fileItem.className = 'file-item';
fileItem.innerHTML = `
<div class="file-name">${fileName}</div>
<div class="file-size">${(file.size / 1024).toFixed(2)} KB</div>
`;
fileList.appendChild(fileItem);

// Обновление таблицы пород, если код уже есть
updateRockTableWithSvg(code);
};
reader.readAsText(file);
}
});

fileList.classList.remove('hidden');
showMessage(`Загружено ${svgFilesList.length} SVG файлов`, 'success');
});

// Обработка данных о породах
parseDataBtn.addEventListener('click', function() {
const inputText = rockDataInput.value.trim();
if (!inputText) {
showMessage('Введите данные о породах', 'error');
return;
}

const lines = inputText.split('\n');
rockData = [];

lines.forEach(line => {
const trimmedLine = line.trim();
if (trimmedLine) {
const parts = trimmedLine.split(/\s+/);
if (parts.length >= 2) {
const code = parts[0];
const name = parts.slice(1).join(' ');
rockData.push({
code: code,
name: name,
svgFile: svgFiles[code] ? svgFiles[code].name : null
});
}
}
});

updateRockTable();
updatePreview();
showMessage(`Обработано ${rockData.length} пород`, 'success');
});

// Добавление породы вручную
addRockBtn.addEventListener('click', function() {
const code = prompt('Введите код породы:');
if (!code) return;

const name = prompt('Введите название породы:');
if (!name) return;

rockData.push({
code: code,
name: name,
svgFile: svgFiles[code] ? svgFiles[code].name : null
});

updateRockTable();
updatePreview();
showMessage(`Добавлена порода: ${code} ${name}`, 'success');
});

// Обновление таблицы пород
function updateRockTable() {
rockTableBody.innerHTML = '';

if (rockData.length === 0) {
rockTableContainer.classList.add('hidden');
return;
}

rockData.forEach((rock, index) => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${rock.code}</td>
<td>${rock.name}</td>
<td>${rock.svgFile || 'Не найден'}</td>
<td>
<button onclick="removeRock(${index})">Удалить</button>
</td>
`;
rockTableBody.appendChild(row);
});

rockTableContainer.classList.remove('hidden');
}

// Обновление таблицы при наличии SVG
function updateRockTableWithSvg(code) {
rockData.forEach(rock => {
if (rock.code === code) {
rock.svgFile = svgFiles[code].name;
}
});

updateRockTable();
updatePreview();
}

// Удаление породы
function removeRock(index) {
rockData.splice(index, 1);
updateRockTable();
updatePreview();
showMessage('Порода удалена', 'success');
}

// Обновление предварительного просмотра
function updatePreview() {
previewContainer.innerHTML = '';

if (rockData.length === 0) {
previewContainer.innerHTML = '<p>Нет данных для предварительного просмотра</p>';
return;
}

rockData.forEach(rock => {
const previewItem = document.createElement('div');
previewItem.className = 'preview-item';

let svgPreview = '<div>SVG не загружен</div>';
if (rock.svgFile && svgFiles[rock.code]) {
svgPreview = svgFiles[rock.code].content;
}

previewItem.innerHTML = `
<div class="preview-svg">${svgPreview}</div>
<div class="preview-code">${rock.code}</div>
<div class="preview-name">${rock.name}</div>
`;

previewContainer.appendChild(previewItem);
});
}

// Генерация JSON шаблона
generateBtn.addEventListener('click', function() {
if (rockData.length === 0) {
showMessage('Нет данных о породах для генерации шаблона', 'error');
return;
}

const paletteName = paletteNameInput.value || 'тест_лито';
const templateName = templateNameInput.value || 'Корреляционный шаблон';
const svgFilePath = svgFilePathInput.value || 'C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/';

// Создание элементов палитры
const paletteItems = rockData.map(rock => {
let svgContent = '';

if (rock.svgFile && svgFiles[rock.code]) {
// Правильное форматирование SVG для XML
svgContent = formatSvgContent(svgFiles[rock.code].content);
}

// Генерация случайного ID
const id = Date.now() + Math.floor(Math.random() * 1000);

// Полный путь к файлу SVG
const fullSvgFilePath = svgFilePath + rock.svgFile;

// Точный порядок атрибутов как в рабочем примере
return {
"color": "#ffffffff",
"data": `<StyledFill name="" group="" brushColor="#000000" objectName="" id="${id}" penColor="#000000">\n <SvgFill penWidth="0.4" objectName="" patternWidth="20" lineWidth="0.4" svgFilePath="${fullSvgFilePath}" svgContent="${svgContent}"/>\n</StyledFill>\n`,
"desc": rock.name,
"value": parseInt(rock.code)
};
});

// Создание основного JSON объекта с фиксированными ID как в рабочем примере
const templateJson = {
"Info": "OISTerra CorrTemplates",
"Palettes": [
{
"Items": paletteItems,
"id": Date.now(),
"name": paletteName,
"transp": true,
"type": 1
}
],
"ScoPaletteVersion": 1,
"corrTemplates": [
{
"Tracks": [
{
"allHeight": false,
"hTxtSet": {
"vis": true
},
"lineStyle": {
"fCol": "#ffc8ffff"
},
"trackId": 1763096947027,
"type": 0
},
{
"Logs": [
{
"logDataVers": 1,
"logId": 1763096947080,
"logParams": [
{
"contId": 1763096947014,
"contName": "РИГИС",
"id": 1763096947013,
"nm": "литология"
}
],
"logType": 3,
"trackId": 1763096947055
}
],
"allHeight": false,
"hTxtSet": {
"vis": false
},
"lineStyle": {
"fCol": "#ffc8ffff"
},
"trackId": 1763096947055,
"width": 50.69166666666666
}
],
"Version": {
"build": 3,
"fillData": 0,
"horizonView": 0,
"logData": 1,
"logRange": 0,
"plastBuild": 0,
"plastData": 0,
"stratBuild": 0,
"stratData": 0,
"stratWidth": 0,
"timeDepth": 0,
"trackWidth": 1,
"view": 7
},
"header": {
"textBlockList": {
"textBlocks": [
{
"blockId": 1763096947023,
"textItems": [
{
"blockId": 1763096947023,
"content": "[WellName]",
"font": {
"b": true
},
"itemId": 1763096947024,
"paramsHash": [
{
"id": -1,
"key": "[WellName]"
}
]
}
]
}
]
}
},
"id": Date.now() + 1000,
"name": templateName,
"plastList": {
"columnId": 1763096947025,
"isPlast": true,
"type": 0
}
}
],
"corrTemplatesVersion": 0
};

// Отображение JSON
jsonContent.textContent = JSON.stringify(templateJson, null, 2);
jsonOutput.classList.remove('hidden');
downloadBtn.classList.remove('hidden');

// Сохранение JSON для скачивания
window.generatedJson = templateJson;

showMessage('JSON шаблон успешно сгенерирован', 'success');
});

// Скачивание JSON файла
downloadBtn.addEventListener('click', function() {
if (!window.generatedJson) {
showMessage('Нет сгенерированного JSON для скачивания', 'error');
return;
}

const dataStr = JSON.stringify(window.generatedJson, null, 2);
const dataBlob = new Blob([dataStr], {type: 'application/json'});

const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = 'литология_шаблон.json';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);

showMessage('Файл успешно скачан', 'success');
});

// Функция для отображения сообщений
function showMessage(message, type) {
messageArea.innerHTML = `<div class="message ${type}">${message}</div>`;

// Автоматическое скрытие сообщения через 5 секунд
setTimeout(() => {
messageArea.innerHTML = '';
}, 5000);
}

// Инициализация при загрузке страницы
window.onload = function() {
// Добавляем пример данных для демонстрации
rockDataInput.value = `1300 Суглинок
1700 Илы
1800 Торф
1400 Супесь
2300 Аргиллит
2400 Алевролит`;
};
</script>
</body>
</html>

Добавлено (2025-11-14, 16:51)
---------------------------------------------
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Генератор шаблонов литологии</title>
<style>
/* Стили остаются без изменений */
* {
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

body {
margin: 0;
padding: 20px;
background-color: #f5f7fa;
color: #333;
}

.container {
max-width: 1200px;
margin: 0 auto;
background-color: white;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
padding: 25px;
}

h1 {
color: #2c3e50;
text-align: center;
margin-bottom: 30px;
border-bottom: 2px solid #3498db;
padding-bottom: 15px;
}

.section {
margin-bottom: 30px;
padding: 20px;
border-radius: 8px;
background-color: #f8f9fa;
}

.section-title {
font-size: 1.3rem;
color: #2c3e50;
margin-bottom: 15px;
display: flex;
align-items: center;
}

.form-group {
margin-bottom: 15px;
}

label {
display: block;
margin-bottom: 5px;
font-weight: 600;
color: #2c3e50;
}

input, textarea, select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}

textarea {
min-height: 100px;
resize: vertical;
}

button {
background-color: #3498db;
color: white;
border: none;
padding: 12px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
font-weight: 600;
transition: background-color 0.3s;
}

button:hover {
background-color: #2980b9;
}

button.secondary {
background-color: #95a5a6;
}

button.secondary:hover {
background-color: #7f8c8d;
}

table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}

th, td {
border: 1px solid #ddd;
padding: 12px;
text-align: left;
}

th {
background-color: #3498db;
color: white;
}

tr:nth-child(even) {
background-color: #f2f2f2;
}

.preview-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-top: 20px;
}

.preview-item {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
width: 200px;
text-align: center;
background-color: white;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

.preview-svg {
height: 100px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
}

.preview-svg svg {
max-width: 100%;
max-height: 100%;
}

.preview-code {
font-weight: bold;
color: #2c3e50;
}

.preview-name {
color: #7f8c8d;
font-size: 0.9rem;
}

.hidden {
display: none;
}

.message {
padding: 15px;
border-radius: 4px;
margin: 15px 0;
}

.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}

.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}

.actions {
display: flex;
gap: 10px;
margin-top: 20px;
}

.file-input-container {
position: relative;
overflow: hidden;
display: inline-block;
width: 100%;
}

.file-input-container input[type=file] {
position: absolute;
left: 0;
top: 0;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
}

.file-input-button {
display: block;
padding: 10px;
background: #f8f9fa;
border: 2px dashed #3498db;
border-radius: 4px;
text-align: center;
color: #3498db;
font-weight: 600;
}

.file-list {
margin-top: 10px;
max-height: 150px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
}

.file-item {
display: flex;
justify-content: space-between;
padding: 5px 0;
border-bottom: 1px solid #eee;
}

.file-item:last-child {
border-bottom: none;
}

.file-name {
flex-grow: 1;
}

.file-size {
color: #7f8c8d;
font-size: 0.8rem;
}

.data-table-container {
overflow-x: auto;
}

.help-text {
font-size: 0.9rem;
color: #7f8c8d;
margin-top: 5px;
}
</style>
</head>
<body>
<div class="container">
<h1>Генератор шаблонов литологии</h1>

<div class="section">
<h2 class="section-title">1. Загрузка SVG-файлов с текстурами</h2>
<div class="form-group">
<label>Выберите папку с SVG-файлами:</label>
<div class="file-input-container">
<div class="file-input-button">Выбрать папку с SVG-файлами</div>
<input type="file" id="svgFolderInput" webkitdirectory multiple>
</div>
<div class="help-text">SVG-файлы должны быть названы в формате "Код_Породы Название.svg", например: "1000 Лед.svg"</div>
</div>

<div class="form-group">
<label>Путь к SVG файлам в программе:</label>
<input type="text" id="svgFilePath" placeholder="C:/Users/User/AppData/Local/OISTerra/OISTerraManager/svg/" value="C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/">
<div class="help-text">Укажите путь, который будет использоваться в сгенерированном JSON файле</div>
</div>

<div id="fileList" class="file-list hidden">
<!-- Список загруженных файлов будет здесь -->
</div>
</div>

<div class="section">
<h2 class="section-title">2. Ввод данных о породах</h2>
<div class="form-group">
<label>Добавить данные о породах:</label>
<div class="help-text">Введите данные в формате: Код_Породы Название (каждая порода с новой строки)</div>
<textarea id="rockDataInput" placeholder="1300 Суглинок
1700 Илы
1800 Торф
1400 Супесь
2300 Аргиллит
2400 Алевролит"></textarea>
</div>

<div class="actions">
<button id="parseDataBtn">Обработать данные</button>
<button id="addRockBtn" class="secondary">Добавить породу вручную</button>
</div>

<div id="rockTableContainer" class="data-table-container hidden">
<h3>Список пород:</h3>
<table id="rockTable">
<thead>
<tr>
<th>Код породы</th>
<th>Наименование породы</th>
<th>SVG файл</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="rockTableBody">
<!-- Данные о породах будут здесь -->
</tbody>
</table>
</div>
</div>

<div class="section">
<h2 class="section-title">3. Предварительный просмотр</h2>
<div id="previewContainer" class="preview-container">
<!-- Предварительный просмотр будет здесь -->
</div>
</div>

<div class="section">
<h2 class="section-title">4. Генерация шаблона</h2>
<div class="form-group">
<label>Название палитры:</label>
<input type="text" id="paletteName" value="тест_лито">
</div>

<div class="form-group">
<label>Название шаблона корреляции:</label>
<input type="text" id="templateName" value="Корреляционный шаблон">
</div>

<div class="actions">
<button id="generateBtn">Сгенерировать JSON шаблон</button>
<button id="downloadBtn" class="secondary hidden">Скачать шаблон</button>
</div>

<div id="messageArea"></div>

<div id="jsonOutput" class="hidden">
<h3>Сгенерированный JSON:</h3>
<pre id="jsonContent"></pre>
</div>
</div>
</div>

<script>
// Переменные для хранения данных
let svgFiles = {};
let rockData = [];

// Элементы DOM
const svgFolderInput = document.getElementById('svgFolderInput');
const svgFilePathInput = document.getElementById('svgFilePath');
const fileList = document.getElementById('fileList');
const rockDataInput = document.getElementById('rockDataInput');
const parseDataBtn = document.getElementById('parseDataBtn');
const addRockBtn = document.getElementById('addRockBtn');
const rockTableContainer = document.getElementById('rockTableContainer');
const rockTableBody = document.getElementById('rockTableBody');
const previewContainer = document.getElementById('previewContainer');
const paletteNameInput = document.getElementById('paletteName');
const templateNameInput = document.getElementById('templateName');
const generateBtn = document.getElementById('generateBtn');
const downloadBtn = document.getElementById('downloadBtn');
const messageArea = document.getElementById('messageArea');
const jsonOutput = document.getElementById('jsonOutput');
const jsonContent = document.getElementById('jsonContent');

// Функция для правильного форматирования SVG контента
function formatSvgContent(svgContent) {
// Убираем лишние пробелы и переносы строк, но сохраняем структуру
let formatted = svgContent
.replace(/\s+/g, ' ')
.trim();

// В рабочем примере XML декларация использует одинарные кавычки
formatted = formatted.replace(/<\?xml version="1.0" encoding="UTF-8"\?>/g, "<?xml version='1.0' encoding='UTF-8'?>");

// Экранируем только амперсанды
formatted = formatted.replace(/&(?!amp;|lt;|gt;|quot;|#)/g, '&');

return formatted;
}

// Обработка загрузки SVG файлов
svgFolderInput.addEventListener('change', function(e) {
const files = Array.from(e.target.files);
svgFiles = {};

// Очистка списка файлов
fileList.innerHTML = '';

// Фильтрация только SVG файлов
const svgFilesList = files.filter(file => file.name.toLowerCase().endsWith('.svg'));

if (svgFilesList.length === 0) {
fileList.innerHTML = '<div>SVG файлы не найдены</div>';
fileList.classList.remove('hidden');
return;
}

// Обработка каждого SVG файла
svgFilesList.forEach(file => {
const fileName = file.name;
const codeMatch = fileName.match(/^(\d+)/);

if (codeMatch) {
const code = codeMatch[1];

const reader = new FileReader();
reader.onload = function(e) {
const svgContent = e.target.result;
svgFiles[code] = {
name: fileName,
content: svgContent
};

// Добавление в список файлов
const fileItem = document.createElement('div');
fileItem.className = 'file-item';
fileItem.innerHTML = `
<div class="file-name">${fileName}</div>
<div class="file-size">${(file.size / 1024).toFixed(2)} KB</div>
`;
fileList.appendChild(fileItem);

// Обновление таблицы пород, если код уже есть
updateRockTableWithSvg(code);
};
reader.readAsText(file);
}
});

fileList.classList.remove('hidden');
showMessage(`Загружено ${svgFilesList.length} SVG файлов`, 'success');
});

// Обработка данных о породах
parseDataBtn.addEventListener('click', function() {
const inputText = rockDataInput.value.trim();
if (!inputText) {
showMessage('Введите данные о породах', 'error');
return;
}

const lines = inputText.split('\n');
rockData = [];

lines.forEach(line => {
const trimmedLine = line.trim();
if (trimmedLine) {
const parts = trimmedLine.split(/\s+/);
if (parts.length >= 2) {
const code = parts[0];
const name = parts.slice(1).join(' ');
rockData.push({
code: code,
name: name,
svgFile: svgFiles[code] ? svgFiles[code].name : null
});
}
}
});

updateRockTable();
updatePreview();
showMessage(`Обработано ${rockData.length} пород`, 'success');
});

// Добавление породы вручную
addRockBtn.addEventListener('click', function() {
const code = prompt('Введите код породы:');
if (!code) return;

const name = prompt('Введите название породы:');
if (!name) return;

rockData.push({
code: code,
name: name,
svgFile: svgFiles[code] ? svgFiles[code].name : null
});

updateRockTable();
updatePreview();
showMessage(`Добавлена порода: ${code} ${name}`, 'success');
});

// Обновление таблицы пород
function updateRockTable() {
rockTableBody.innerHTML = '';

if (rockData.length === 0) {
rockTableContainer.classList.add('hidden');
return;
}

rockData.forEach((rock, index) => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${rock.code}</td>
<td>${rock.name}</td>
<td>${rock.svgFile || 'Не найден'}</td>
<td>
<button onclick="removeRock(${index})">Удалить</button>
</td>
`;
rockTableBody.appendChild(row);
});

rockTableContainer.classList.remove('hidden');
}

// Обновление таблицы при наличии SVG
function updateRockTableWithSvg(code) {
rockData.forEach(rock => {
if (rock.code === code) {
rock.svgFile = svgFiles[code].name;
}
});

updateRockTable();
updatePreview();
}

// Удаление породы
function removeRock(index) {
rockData.splice(index, 1);
updateRockTable();
updatePreview();
showMessage('Порода удалена', 'success');
}

// Обновление предварительного просмотра
function updatePreview() {
previewContainer.innerHTML = '';

if (rockData.length === 0) {
previewContainer.innerHTML = '<p>Нет данных для предварительного просмотра</p>';
return;
}

rockData.forEach(rock => {
const previewItem = document.createElement('div');
previewItem.className = 'preview-item';

let svgPreview = '<div>SVG не загружен</div>';
if (rock.svgFile && svgFiles[rock.code]) {
svgPreview = svgFiles[rock.code].content;
}

previewItem.innerHTML = `
<div class="preview-svg">${svgPreview}</div>
<div class="preview-code">${rock.code}</div>
<div class="preview-name">${rock.name}</div>
`;

previewContainer.appendChild(previewItem);
});
}

// Генерация JSON шаблона
generateBtn.addEventListener('click', function() {
if (rockData.length === 0) {
showMessage('Нет данных о породах для генерации шаблона', 'error');
return;
}

const paletteName = paletteNameInput.value || 'тест_лито';
const templateName = templateNameInput.value || 'Корреляционный шаблон';
const svgFilePath = svgFilePathInput.value || 'C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/';

// Создание элементов палитры
const paletteItems = rockData.map(rock => {
let svgContent = '';

if (rock.svgFile && svgFiles[rock.code]) {
// Правильное форматирование SVG для XML
svgContent = formatSvgContent(svgFiles[rock.code].content);
}

// Генерация случайного ID
const id = Date.now() + Math.floor(Math.random() * 1000);

// Полный путь к файлу SVG
const fullSvgFilePath = svgFilePath + rock.svgFile;

// Точный порядок атрибутов как в рабочем примере
return {
"color": "#ffffffff",
"data": `<StyledFill name="" group="" brushColor="#000000" objectName="" id="${id}" penColor="#000000">\n <SvgFill penWidth="0.4" objectName="" patternWidth="20" lineWidth="0.4" svgFilePath="${fullSvgFilePath}" svgContent="${svgContent}"/>\n</StyledFill>\n`,
"desc": rock.name,
"value": parseInt(rock.code)
};
});

// Создание основного JSON объекта с фиксированными ID как в рабочем примере
const templateJson = {
"Info": "OISTerra CorrTemplates",
"Palettes": [
{
"Items": paletteItems,
"id": Date.now(),
"name": paletteName,
"transp": true,
"type": 1
}
],
"ScoPaletteVersion": 1,
"corrTemplates": [
{
"Tracks": [
{
"allHeight": false,
"hTxtSet": {
"vis": true
},
"lineStyle": {
"fCol": "#ffc8ffff"
},
"trackId": 1763096947027,
"type": 0
},
{
"Logs": [
{
"logDataVers": 1,
"logId": 1763096947080,
"logParams": [
{
"contId": 1763096947014,
"contName": "РИГИС",
"id": 1763096947013,
"nm": "литология"
}
],
"logType": 3,
"trackId": 1763096947055
}
],
"allHeight": false,
"hTxtSet": {
"vis": false
},
"lineStyle": {
"fCol": "#ffc8ffff"
},
"trackId": 1763096947055,
"width": 50.69166666666666
}
],
"Version": {
"build": 3,
"fillData": 0,
"horizonView": 0,
"logData": 1,
"logRange": 0,
"plastBuild": 0,
"plastData": 0,
"stratBuild": 0,
"stratData": 0,
"stratWidth": 0,
"timeDepth": 0,
"trackWidth": 1,
"view": 7
},
"header": {
"textBlockList": {
"textBlocks": [
{
"blockId": 1763096947023,
"textItems": [
{
"blockId": 1763096947023,
"content": "[WellName]",
"font": {
"b": true
},
"itemId": 1763096947024,
"paramsHash": [
{
"id": -1,
"key": "[WellName]"
}
]
}
]
}
]
}
},
"id": Date.now() + 1000,
"name": templateName,
"plastList": {
"columnId": 1763096947025,
"isPlast": true,
"type": 0
}
}
],
"corrTemplatesVersion": 0
};

// Отображение JSON
jsonContent.textContent = JSON.stringify(templateJson, null, 2);
jsonOutput.classList.remove('hidden');
downloadBtn.classList.remove('hidden');

// Сохранение JSON для скачивания
window.generatedJson = templateJson;

showMessage('JSON шаблон успешно сгенерирован', 'success');
});

// Скачивание JSON файла
downloadBtn.addEventListener('click', function() {
if (!window.generatedJson) {
showMessage('Нет сгенерированного JSON для скачивания', 'error');
return;
}

const dataStr = JSON.stringify(window.generatedJson, null, 2);
const dataBlob = new Blob([dataStr], {type: 'application/json'});

const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = 'литология_шаблон.json';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);

showMessage('Файл успешно скачан', 'success');
});

// Функция для отображения сообщений
function showMessage(message, type) {
messageArea.innerHTML = `<div class="message ${type}">${message}</div>`;

// Автоматическое скрытие сообщения через 5 секунд
setTimeout(() => {
messageArea.innerHTML = '';
}, 5000);
}

// Инициализация при загрузке страницы
window.onload = function() {
// Добавляем пример данных для демонстрации
rockDataInput.value = `1300 Суглинок
1700 Илы
1800 Торф
1400 Супесь
2300 Аргиллит
2400 Алевролит`;
};
</script>
</body>
</html>

Добавлено (2025-11-14, 16:59)
---------------------------------------------
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Генератор шаблонов литологии</title>
<style>
/* Стили остаются без изменений */
* {
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

body {
margin: 0;
padding: 20px;
background-color: #f5f7fa;
color: #333;
}

.container {
max-width: 1200px;
margin: 0 auto;
background-color: white;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
padding: 25px;
}

h1 {
color: #2c3e50;
text-align: center;
margin-bottom: 30px;
border-bottom: 2px solid #3498db;
padding-bottom: 15px;
}

.section {
margin-bottom: 30px;
padding: 20px;
border-radius: 8px;
background-color: #f8f9fa;
}

.section-title {
font-size: 1.3rem;
color: #2c3e50;
margin-bottom: 15px;
display: flex;
align-items: center;
}

.form-group {
margin-bottom: 15px;
}

label {
display: block;
margin-bottom: 5px;
font-weight: 600;
color: #2c3e50;
}

input, textarea, select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}

textarea {
min-height: 100px;
resize: vertical;
}

button {
background-color: #3498db;
color: white;
border: none;
padding: 12px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
font-weight: 600;
transition: background-color 0.3s;
}

button:hover {
background-color: #2980b9;
}

button.secondary {
background-color: #95a5a6;
}

button.secondary:hover {
background-color: #7f8c8d;
}

table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}

th, td {
border: 1px solid #ddd;
padding: 12px;
text-align: left;
}

th {
background-color: #3498db;
color: white;
}

tr:nth-child(even) {
background-color: #f2f2f2;
}

.preview-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-top: 20px;
}

.preview-item {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
width: 200px;
text-align: center;
background-color: white;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

.preview-svg {
height: 100px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
}

.preview-svg svg {
max-width: 100%;
max-height: 100%;
}

.preview-code {
font-weight: bold;
color: #2c3e50;
}

.preview-name {
color: #7f8c8d;
font-size: 0.9rem;
}

.hidden {
display: none;
}

.message {
padding: 15px;
border-radius: 4px;
margin: 15px 0;
}

.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}

.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}

.actions {
display: flex;
gap: 10px;
margin-top: 20px;
}

.file-input-container {
position: relative;
overflow: hidden;
display: inline-block;
width: 100%;
}

.file-input-container input[type=file] {
position: absolute;
left: 0;
top: 0;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
}

.file-input-button {
display: block;
padding: 10px;
background: #f8f9fa;
border: 2px dashed #3498db;
border-radius: 4px;
text-align: center;
color: #3498db;
font-weight: 600;
}

.file-list {
margin-top: 10px;
max-height: 150px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
}

.file-item {
display: flex;
justify-content: space-between;
padding: 5px 0;
border-bottom: 1px solid #eee;
}

.file-item:last-child {
border-bottom: none;
}

.file-name {
flex-grow: 1;
}

.file-size {
color: #7f8c8d;
font-size: 0.8rem;
}

.data-table-container {
overflow-x: auto;
}

.help-text {
font-size: 0.9rem;
color: #7f8c8d;
margin-top: 5px;
}
</style>
</head>
<body>
<div class="container">
<h1>Генератор шаблонов литологии</h1>

<div class="section">
<h2 class="section-title">1. Загрузка SVG-файлов с текстурами</h2>
<div class="form-group">
<label>Выберите папку с SVG-файлами:</label>
<div class="file-input-container">
<div class="file-input-button">Выбрать папку с SVG-файлами</div>
<input type="file" id="svgFolderInput" webkitdirectory multiple>
</div>
<div class="help-text">SVG-файлы должны быть названы в формате "Код_Породы Название.svg", например: "1000 Лед.svg"</div>
</div>

<div class="form-group">
<label>Путь к SVG файлам в программе:</label>
<input type="text" id="svgFilePath" placeholder="C:/Users/User/AppData/Local/OISTerra/OISTerraManager/svg/" value="C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/">
<div class="help-text">Укажите путь, который будет использоваться в сгенерированном JSON файе</div>
</div>

<div id="fileList" class="file-list hidden">
<!-- Список загруженных файлов будет здесь -->
</div>
</div>

<div class="section">
<h2 class="section-title">2. Ввод данных о породах</h2>
<div class="form-group">
<label>Добавить данные о породах:</label>
<div class="help-text">Введите данные в формате: Код_Породы Название (каждая порода с новой строки)</div>
<textarea id="rockDataInput" placeholder="1300 Суглинок
1700 Илы
1800 Торф
1400 Супесь
2300 Аргиллит
2400 Алевролит"></textarea>
</div>

<div class="actions">
<button id="parseDataBtn">Обработать данные</button>
<button id="addRockBtn" class="secondary">Добавить породу вручную</button>
</div>

<div id="rockTableContainer" class="data-table-container hidden">
<h3>Список пород:</h3>
<table id="rockTable">
<thead>
<tr>
<th>Код породы</th>
<th>Наименование породы</th>
<th>SVG файл</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="rockTableBody">
<!-- Данные о породах будут здесь -->
</tbody>
</table>
</div>
</div>

<div class="section">
<h2 class="section-title">3. Предварительный просмотр</h2>
<div id="previewContainer" class="preview-container">
<!-- Предварительный просмотр будет здесь -->
</div>
</div>

<div class="section">
<h2 class="section-title">4. Генерация шаблона</h2>
<div class="form-group">
<label>Название палитры:</label>
<input type="text" id="paletteName" value="тест_лито">
</div>

<div class="form-group">
<label>Название шаблона корреляции:</label>
<input type="text" id="templateName" value="Корреляционный шаблон">
</div>

<div class="actions">
<button id="generateBtn">Сгенерировать JSON шаблон</button>
<button id="downloadBtn" class="secondary hidden">Скачать шаблон</button>
</div>

<div id="messageArea"></div>

<div id="jsonOutput" class="hidden">
<h3>Сгенерированный JSON:</h3>
<pre id="jsonContent"></pre>
</div>
</div>
</div>

<script>
// Переменные для хранения данных
let svgFiles = {};
let rockData = [];

// Элементы DOM
const svgFolderInput = document.getElementById('svgFolderInput');
const svgFilePathInput = document.getElementById('svgFilePath');
const fileList = document.getElementById('fileList');
const rockDataInput = document.getElementById('rockDataInput');
const parseDataBtn = document.getElementById('parseDataBtn');
const addRockBtn = document.getElementById('addRockBtn');
const rockTableContainer = document.getElementById('rockTableContainer');
const rockTableBody = document.getElementById('rockTableBody');
const previewContainer = document.getElementById('previewContainer');
const paletteNameInput = document.getElementById('paletteName');
const templateNameInput = document.getElementById('templateName');
const generateBtn = document.getElementById('generateBtn');
const downloadBtn = document.getElementById('downloadBtn');
const messageArea = document.getElementById('messageArea');
const jsonOutput = document.getElementById('jsonOutput');
const jsonContent = document.getElementById('jsonContent');

// Функция для правильного форматирования SVG контента
function formatSvgContent(svgContent) {
// Убираем лишние пробелы и переносы строк
let formatted = svgContent.replace(/\s+/g, ' ').trim();

// В рабочем примере XML декларация использует одинарные кавычки
formatted = formatted.replace(/<\?xml version="1.0" encoding="UTF-8"\?>/g, "<?xml version='1.0' encoding='UTF-8'?>");

// Экранируем все специальные символы для XML как в рабочем примере
formatted = formatted
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');

return formatted;
}

// Обработка загрузки SVG файлов
svgFolderInput.addEventListener('change', function(e) {
const files = Array.from(e.target.files);
svgFiles = {};

// Очистка списка файлов
fileList.innerHTML = '';

// Фильтрация только SVG файлов
const svgFilesList = files.filter(file => file.name.toLowerCase().endsWith('.svg'));

if (svgFilesList.length === 0) {
fileList.innerHTML = '<div>SVG файлы не найдены</div>';
fileList.classList.remove('hidden');
return;
}

// Обработка каждого SVG файла
svgFilesList.forEach(file => {
const fileName = file.name;
const codeMatch = fileName.match(/^(\d+)/);

if (codeMatch) {
const code = codeMatch[1];

const reader = new FileReader();
reader.onload = function(e) {
const svgContent = e.target.result;
svgFiles[code] = {
name: fileName,
content: svgContent
};

// Добавление в список файлов
const fileItem = document.createElement('div');
fileItem.className = 'file-item';
fileItem.innerHTML = `
<div class="file-name">${fileName}</div>
<div class="file-size">${(file.size / 1024).toFixed(2)} KB</div>
`;
fileList.appendChild(fileItem);

// Обновление таблицы пород, если код уже есть
updateRockTableWithSvg(code);
};
reader.readAsText(file);
}
});

fileList.classList.remove('hidden');
showMessage(`Загружено ${svgFilesList.length} SVG файлов`, 'success');
});

// Обработка данных о породах
parseDataBtn.addEventListener('click', function() {
const inputText = rockDataInput.value.trim();
if (!inputText) {
showMessage('Введите данные о породах', 'error');
return;
}

const lines = inputText.split('\n');
rockData = [];

lines.forEach(line => {
const trimmedLine = line.trim();
if (trimmedLine) {
const parts = trimmedLine.split(/\s+/);
if (parts.length >= 2) {
const code = parts[0];
const name = parts.slice(1).join(' ');
rockData.push({
code: code,
name: name,
svgFile: svgFiles[code] ? svgFiles[code].name : null
});
}
}
});

updateRockTable();
updatePreview();
showMessage(`Обработано ${rockData.length} пород`, 'success');
});

// Добавление породы вручную
addRockBtn.addEventListener('click', function() {
const code = prompt('Введите код породы:');
if (!code) return;

const name = prompt('Введите название породы:');
if (!name) return;

rockData.push({
code: code,
name: name,
svgFile: svgFiles[code] ? svgFiles[code].name : null
});

updateRockTable();
updatePreview();
showMessage(`Добавлена порода: ${code} ${name}`, 'success');
});

// Обновление таблицы пород
function updateRockTable() {
rockTableBody.innerHTML = '';

if (rockData.length === 0) {
rockTableContainer.classList.add('hidden');
return;
}

rockData.forEach((rock, index) => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${rock.code}</td>
<td>${rock.name}</td>
<td>${rock.svgFile || 'Не найден'}</td>
<td>
<button onclick="removeRock(${index})">Удалить</button>
</td>
`;
rockTableBody.appendChild(row);
});

rockTableContainer.classList.remove('hidden');
}

// Обновление таблицы при наличии SVG
function updateRockTableWithSvg(code) {
rockData.forEach(rock => {
if (rock.code === code) {
rock.svgFile = svgFiles[code].name;
}
});

updateRockTable();
updatePreview();
}

// Удаление породы
function removeRock(index) {
rockData.splice(index, 1);
updateRockTable();
updatePreview();
showMessage('Порода удалена', 'success');
}

// Обновление предварительного просмотра
function updatePreview() {
previewContainer.innerHTML = '';

if (rockData.length === 0) {
previewContainer.innerHTML = '<p>Нет данных для предварительного просмотра</p>';
return;
}

rockData.forEach(rock => {
const previewItem = document.createElement('div');
previewItem.className = 'preview-item';

let svgPreview = '<div>SVG не загружен</div>';
if (rock.svgFile && svgFiles[rock.code]) {
svgPreview = svgFiles[rock.code].content;
}

previewItem.innerHTML = `
<div class="preview-svg">${svgPreview}</div>
<div class="preview-code">${rock.code}</div>
<div class="preview-name">${rock.name}</div>
`;

previewContainer.appendChild(previewItem);
});
}

// Генерация JSON шаблона
generateBtn.addEventListener('click', function() {
if (rockData.length === 0) {
showMessage('Нет данных о породах для генерации шаблона', 'error');
return;
}

const paletteName = paletteNameInput.value || 'тест_лито';
const templateName = templateNameInput.value || 'Корреляционный шаблон';
const svgFilePath = svgFilePathInput.value || 'C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/';

// Создание элементов палитры
const paletteItems = rockData.map(rock => {
let svgContent = '';

if (rock.svgFile && svgFiles[rock.code]) {
// Правильное форматирование SVG для XML
svgContent = formatSvgContent(svgFiles[rock.code].content);
}

// Генерация случайного ID
const id = Date.now() + Math.floor(Math.random() * 1000);

// Полный путь к файлу SVG
const fullSvgFilePath = svgFilePath + rock.svgFile;

// Точный порядок атрибутов как в рабочем примере
return {
"color": "#ffffffff",
"data": `<StyledFill name="" group="" brushColor="#000000" objectName="" id="${id}" penColor="#000000">\n <SvgFill penWidth="0.4" objectName="" patternWidth="20" lineWidth="0.4" svgFilePath="${fullSvgFilePath}" svgContent="${svgContent}"/>\n</StyledFill>\n`,
"desc": rock.name,
"value": parseInt(rock.code)
};
});

// Создание основного JSON объекта с фиксированными ID как в рабочем примере
const templateJson = {
"Info": "OISTerra CorrTemplates",
"Palettes": [
{
"Items": paletteItems,
"id": Date.now(),
"name": paletteName,
"transp": true,
"type": 1
}
],
"ScoPaletteVersion": 1,
"corrTemplates": [
{
"Tracks": [
{
"allHeight": false,
"hTxtSet": {
"vis": true
},
"lineStyle": {
"fCol": "#ffc8ffff"
},
"trackId": 1763096947027,
"type": 0
},
{
"Logs": [
{
"logDataVers": 1,
"logId": 1763096947080,
"logParams": [
{
"contId": 1763096947014,
"contName": "РИГИС",
"id": 1763096947013,
"nm": "литология"
}
],
"logType": 3,
"trackId": 1763096947055
}
],
"allHeight": false,
"hTxtSet": {
"vis": false
},
"lineStyle": {
"fCol": "#ffc8ffff"
},
"trackId": 1763096947055,
"width": 50.69166666666666
}
],
"Version": {
"build": 3,
"fillData": 0,
"horizonView": 0,
"logData": 1,
"logRange": 0,
"plastBuild": 0,
"plastData": 0,
"stratBuild": 0,
"stratData": 0,
"stratWidth": 0,
"timeDepth": 0,
"trackWidth": 1,
"view": 7
},
"header": {
"textBlockList": {
"textBlocks": [
{
"blockId": 1763096947023,
"textItems": [
{
"blockId": 1763096947023,
"content": "[WellName]",
"font": {
"b": true
},
"itemId": 1763096947024,
"paramsHash": [
{
"id": -1,
"key": "[WellName]"
}
]
}
]
}
]
}
},
"id": Date.now() + 1000,
"name": templateName,
"plastList": {
"columnId": 1763096947025,
"isPlast": true,
"type": 0
}
}
],
"corrTemplatesVersion": 0
};

// Отображение JSON
jsonContent.textContent = JSON.stringify(templateJson, null, 2);
jsonOutput.classList.remove('hidden');
downloadBtn.classList.remove('hidden');

// Сохранение JSON для скачивания
window.generatedJson = templateJson;

showMessage('JSON шаблон успешно сгенерирован', 'success');
});

// Скачивание JSON файла
downloadBtn.addEventListener('click', function() {
if (!window.generatedJson) {
showMessage('Нет сгенерированного JSON для скачивания', 'error');
return;
}

const dataStr = JSON.stringify(window.generatedJson, null, 2);
const dataBlob = new Blob([dataStr], {type: 'application/json'});

const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = 'литология_шаблон.json';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);

showMessage('Файл успешно скачан', 'success');
});

// Функция для отображения сообщений
function showMessage(message, type) {
messageArea.innerHTML = `<div class="message ${type}">${message}</div>`;

// Автоматическое скрытие сообщения через 5 секунд
setTimeout(() => {
messageArea.innerHTML = '';
}, 5000);
}

// Инициализация при загрузке страницы
window.onload = function() {
// Добавляем пример данных для демонстрации
rockDataInput.value = `1300 Суглинок
1700 Илы
1800 Торф
1400 Супесь
2300 Аргиллит
2400 Алевролит`;
};
</script>
</body>
</html>

Добавлено (2025-11-14, 17:07)
---------------------------------------------
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Генератор шаблонов литологии</title>
<style>
/* Стили остаются без изменений */
* {
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

body {
margin: 0;
padding: 20px;
background-color: #f5f7fa;
color: #333;
}

.container {
max-width: 1200px;
margin: 0 auto;
background-color: white;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
padding: 25px;
}

h1 {
color: #2c3e50;
text-align: center;
margin-bottom: 30px;
border-bottom: 2px solid #3498db;
padding-bottom: 15px;
}

.section {
margin-bottom: 30px;
padding: 20px;
border-radius: 8px;
background-color: #f8f9fa;
}

.section-title {
font-size: 1.3rem;
color: #2c3e50;
margin-bottom: 15px;
display: flex;
align-items: center;
}

.form-group {
margin-bottom: 15px;
}

label {
display: block;
margin-bottom: 5px;
font-weight: 600;
color: #2c3e50;
}

input, textarea, select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}

textarea {
min-height: 100px;
resize: vertical;
}

button {
background-color: #3498db;
color: white;
border: none;
padding: 12px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
font-weight: 600;
transition: background-color 0.3s;
}

button:hover {
background-color: #2980b9;
}

button.secondary {
background-color: #95a5a6;
}

button.secondary:hover {
background-color: #7f8c8d;
}

table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}

th, td {
border: 1px solid #ddd;
padding: 12px;
text-align: left;
}

th {
background-color: #3498db;
color: white;
}

tr:nth-child(even) {
background-color: #f2f2f2;
}

.preview-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-top: 20px;
}

.preview-item {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
width: 200px;
text-align: center;
background-color: white;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

.preview-svg {
height: 100px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
}

.preview-svg svg {
max-width: 100%;
max-height: 100%;
}

.preview-code {
font-weight: bold;
color: #2c3e50;
}

.preview-name {
color: #7f8c8d;
font-size: 0.9rem;
}

.hidden {
display: none;
}

.message {
padding: 15px;
border-radius: 4px;
margin: 15px 0;
}

.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}

.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}

.actions {
display: flex;
gap: 10px;
margin-top: 20px;
}

.file-input-container {
position: relative;
overflow: hidden;
display: inline-block;
width: 100%;
}

.file-input-container input[type=file] {
position: absolute;
left: 0;
top: 0;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
}

.file-input-button {
display: block;
padding: 10px;
background: #f8f9fa;
border: 2px dashed #3498db;
border-radius: 4px;
text-align: center;
color: #3498db;
font-weight: 600;
}

.file-list {
margin-top: 10px;
max-height: 150px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
}

.file-item {
display: flex;
justify-content: space-between;
padding: 5px 0;
border-bottom: 1px solid #eee;
}

.file-item:last-child {
border-bottom: none;
}

.file-name {
flex-grow: 1;
}

.file-size {
color: #7f8c8d;
font-size: 0.8rem;
}

.data-table-container {
overflow-x: auto;
}

.help-text {
font-size: 0.9rem;
color: #7f8c8d;
margin-top: 5px;
}
</style>
</head>
<body>
<div class="container">
<h1>Генератор шаблонов литологии</h1>

<div class="section">
<h2 class="section-title">1. Загрузка SVG-файлов с текстурами</h2>
<div class="form-group">
<label>Выберите папку с SVG-файлами:</label>
<div class="file-input-container">
<div class="file-input-button">Выбрать папку с SVG-файлами</div>
<input type="file" id="svgFolderInput" webkitdirectory multiple>
</div>
<div class="help-text">SVG-файлы должны быть названы в формате "Код_Породы Название.svg", например: "1000 Лед.svg"</div>
</div>

<div class="form-group">
<label>Путь к SVG файлам в программе:</label>
<input type="text" id="svgFilePath" placeholder="C:/Users/User/AppData/Local/OISTerra/OISTerraManager/svg/" value="C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/">
<div class="help-text">Укажите путь, который будет использоваться в сгенерированном JSON файле</div>
</div>

<div id="fileList" class="file-list hidden">
<!-- Список загруженных файлов будет здесь -->
</div>
</div>

<div class="section">
<h2 class="section-title">2. Ввод данных о породах</h2>
<div class="form-group">
<label>Добавить данные о породах:</label>
<div class="help-text">Введите данные в формате: Код_Породы Название (каждая порода с новой строки)</div>
<textarea id="rockDataInput" placeholder="1300 Суглинок
1700 Илы
1800 Торф
1400 Супесь
2300 Аргиллит
2400 Алевролит"></textarea>
</div>

<div class="actions">
<button id="parseDataBtn">Обработать данные</button>
<button id="addRockBtn" class="secondary">Добавить породу вручную</button>
</div>

<div id="rockTableContainer" class="data-table-container hidden">
<h3>Список пород:</h3>
<table id="rockTable">
<thead>
<tr>
<th>Код породы</th>
<th>Наименование породы</th>
<th>SVG файл</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="rockTableBody">
<!-- Данные о породах будут здесь -->
</tbody>
</table>
</div>
</div>

<div class="section">
<h2 class="section-title">3. Предварительный просмотр</h2>
<div id="previewContainer" class="preview-container">
<!-- Предварительный просмотр будет здесь -->
</div>
</div>

<div class="section">
<h2 class="section-title">4. Генерация шаблона</h2>
<div class="form-group">
<label>Название палитры:</label>
<input type="text" id="paletteName" value="тест_лито">
</div>

<div class="form-group">
<label>Название шаблона корреляции:</label>
<input type="text" id="templateName" value="Корреляционный шаблон">
</div>

<div class="actions">
<button id="generateBtn">Сгенерировать JSON шаблон</button>
<button id="downloadBtn" class="secondary hidden">Скачать шаблон</button>
</div>

<div id="messageArea"></div>

<div id="jsonOutput" class="hidden">
<h3>Сгенерированный JSON:</h3>
<pre id="jsonContent"></pre>
</div>
</div>
</div>

<script>
// Переменные для хранения данных
let svgFiles = {};
let rockData = [];

// Элементы DOM
const svgFolderInput = document.getElementById('svgFolderInput');
const svgFilePathInput = document.getElementById('svgFilePath');
const fileList = document.getElementById('fileList');
const rockDataInput = document.getElementById('rockDataInput');
const parseDataBtn = document.getElementById('parseDataBtn');
const addRockBtn = document.getElementById('addRockBtn');
const rockTableContainer = document.getElementById('rockTableContainer');
const rockTableBody = document.getElementById('rockTableBody');
const previewContainer = document.getElementById('previewContainer');
const paletteNameInput = document.getElementById('paletteName');
const templateNameInput = document.getElementById('templateName');
const generateBtn = document.getElementById('generateBtn');
const downloadBtn = document.getElementById('downloadBtn');
const messageArea = document.getElementById('messageArea');
const jsonOutput = document.getElementById('jsonOutput');
const jsonContent = document.getElementById('jsonContent');

// Функция для правильного экранирования SVG контента
function formatSvgContent(svgContent) {
// Убираем лишние пробелы и переносы строк
let formatted = svgContent.replace(/\s+/g, ' ').trim();

// Заменяем XML декларацию на версию с одинарными кавычками
formatted = formatted.replace(/<\?xml version="1.0" encoding="UTF-8"\?>/g, "<?xml version='1.0' encoding='UTF-8'?>");

// Правильно экранируем специальные символы для XML
formatted = formatted
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');

return formatted;
}

// Обработка загрузки SVG файлов
svgFolderInput.addEventListener('change', function(e) {
const files = Array.from(e.target.files);
svgFiles = {};

// Очистка списка файлов
fileList.innerHTML = '';

// Фильтрация только SVG файлов
const svgFilesList = files.filter(file => file.name.toLowerCase().endsWith('.svg'));

if (svgFilesList.length === 0) {
fileList.innerHTML = '<div>SVG файлы не найдены</div>';
fileList.classList.remove('hidden');
return;
}

// Обработка каждого SVG файла
svgFilesList.forEach(file => {
const fileName = file.name;
const codeMatch = fileName.match(/^(\d+)/);

if (codeMatch) {
const code = codeMatch[1];

const reader = new FileReader();
reader.onload = function(e) {
const svgContent = e.target.result;
svgFiles[code] = {
name: fileName,
content: svgContent
};

// Добавление в список файлов
const fileItem = document.createElement('div');
fileItem.className = 'file-item';
fileItem.innerHTML = `
<div class="file-name">${fileName}</div>
<div class="file-size">${(file.size / 1024).toFixed(2)} KB</div>
`;
fileList.appendChild(fileItem);

// Обновление таблицы пород, если код уже есть
updateRockTableWithSvg(code);
};
reader.readAsText(file);
}
});

fileList.classList.remove('hidden');
showMessage(`Загружено ${svgFilesList.length} SVG файлов`, 'success');
});

// Обработка данных о породах
parseDataBtn.addEventListener('click', function() {
const inputText = rockDataInput.value.trim();
if (!inputText) {
showMessage('Введите данные о породах', 'error');
return;
}

const lines = inputText.split('\n');
rockData = [];

lines.forEach(line => {
const trimmedLine = line.trim();
if (trimmedLine) {
const parts = trimmedLine.split(/\s+/);
if (parts.length >= 2) {
const code = parts[0];
const name = parts.slice(1).join(' ');
rockData.push({
code: code,
name: name,
svgFile: svgFiles[code] ? svgFiles[code].name : null
});
}
}
});

updateRockTable();
updatePreview();
showMessage(`Обработано ${rockData.length} пород`, 'success');
});

// Добавление породы вручную
addRockBtn.addEventListener('click', function() {
const code = prompt('Введите код породы:');
if (!code) return;

const name = prompt('Введите название породы:');
if (!name) return;

rockData.push({
code: code,
name: name,
svgFile: svgFiles[code] ? svgFiles[code].name : null
});

updateRockTable();
updatePreview();
showMessage(`Добавлена порода: ${code} ${name}`, 'success');
});

// Обновление таблицы пород
function updateRockTable() {
rockTableBody.innerHTML = '';

if (rockData.length === 0) {
rockTableContainer.classList.add('hidden');
return;
}

rockData.forEach((rock, index) => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${rock.code}</td>
<td>${rock.name}</td>
<td>${rock.svgFile || 'Не найден'}</td>
<td>
<button onclick="removeRock(${index})">Удалить</button>
</td>
`;
rockTableBody.appendChild(row);
});

rockTableContainer.classList.remove('hidden');
}

// Обновление таблицы при наличии SVG
function updateRockTableWithSvg(code) {
rockData.forEach(rock => {
if (rock.code === code) {
rock.svgFile = svgFiles[code].name;
}
});

updateRockTable();
updatePreview();
}

// Удаление породы
function removeRock(index) {
rockData.splice(index, 1);
updateRockTable();
updatePreview();
showMessage('Порода удалена', 'success');
}

// Обновление предварительного просмотра
function updatePreview() {
previewContainer.innerHTML = '';

if (rockData.length === 0) {
previewContainer.innerHTML = '<p>Нет данных для предварительного просмотра</p>';
return;
}

rockData.forEach(rock => {
const previewItem = document.createElement('div');
previewItem.className = 'preview-item';

let svgPreview = '<div>SVG не загружен</div>';
if (rock.svgFile && svgFiles[rock.code]) {
svgPreview = svgFiles[rock.code].content;
}

previewItem.innerHTML = `
<div class="preview-svg">${svgPreview}</div>
<div class="preview-code">${rock.code}</div>
<div class="preview-name">${rock.name}</div>
`;

previewContainer.appendChild(previewItem);
});
}

// Генерация JSON шаблона
generateBtn.addEventListener('click', function() {
if (rockData.length === 0) {
showMessage('Нет данных о породах для генерации шаблона', 'error');
return;
}

const paletteName = paletteNameInput.value || 'тест_лито';
const templateName = templateNameInput.value || 'Корреляционный шаблон';
const svgFilePath = svgFilePathInput.value || 'C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/';

// Создание элементов палитры
const paletteItems = rockData.map(rock => {
let svgContent = '';

if (rock.svgFile && svgFiles[rock.code]) {
// Правильное форматирование SVG для XML
svgContent = formatSvgContent(svgFiles[rock.code].content);
}

// Генерация случайного ID
const id = Date.now() + Math.floor(Math.random() * 1000);

// Полный путь к файлу SVG
const fullSvgFilePath = svgFilePath + rock.svgFile;

// Точный порядок атрибутов как в рабочем примере
return {
"color": "#ffffffff",
"data": "<StyledFill name=\"\" group=\"\" brushColor=\"#000000\" objectName=\"\" id=\"" + id + "\" penColor=\"#000000\">\n <SvgFill penWidth=\"0.4\" objectName=\"\" patternWidth=\"20\" lineWidth=\"0.4\" svgFilePath=\"" + fullSvgFilePath + "\" svgContent=\"" + svgContent + "\"/>\n</StyledFill>\n",
"desc": rock.name,
"value": parseInt(rock.code)
};
});

// Создание основного JSON объекта с фиксированными ID как в рабочем примере
const templateJson = {
"Info": "OISTerra CorrTemplates",
"Palettes": [
{
"Items": paletteItems,
"id": Date.now(),
"name": paletteName,
"transp": true,
"type": 1
}
],
"ScoPaletteVersion": 1,
"corrTemplates": [
{
"Tracks": [
{
"allHeight": false,
"hTxtSet": {
"vis": true
},
"lineStyle": {
"fCol": "#ffc8ffff"
},
"trackId": 1763096947027,
"type": 0
},
{
"Logs": [
{
"logDataVers": 1,
"logId": 1763096947080,
"logParams": [
{
"contId": 1763096947014,
"contName": "РИГИС",
"id": 1763096947013,
"nm": "литология"
}
],
"logType": 3,
"trackId": 1763096947055
}
],
"allHeight": false,
"hTxtSet": {
"vis": false
},
"lineStyle": {
"fCol": "#ffc8ffff"
},
"trackId": 1763096947055,
"width": 50.69166666666666
}
],
"Version": {
"build": 3,
"fillData": 0,
"horizonView": 0,
"logData": 1,
"logRange": 0,
"plastBuild": 0,
"plastData": 0,
"stratBuild": 0,
"stratData": 0,
"stratWidth": 0,
"timeDepth": 0,
"trackWidth": 1,
"view": 7
},
"header": {
"textBlockList": {
"textBlocks": [
{
"blockId": 1763096947023,
"textItems": [
{
"blockId": 1763096947023,
"content": "[WellName]",
"font": {
"b": true
},
"itemId": 1763096947024,
"paramsHash": [
{
"id": -1,
"key": "[WellName]"
}
]
}
]
}
]
}
},
"id": Date.now() + 1000,
"name": templateName,
"plastList": {
"columnId": 1763096947025,
"isPlast": true,
"type": 0
}
}
],
"corrTemplatesVersion": 0
};

// Отображение JSON
jsonContent.textContent = JSON.stringify(templateJson, null, 2);
jsonOutput.classList.remove('hidden');
downloadBtn.classList.remove('hidden');

// Сохранение JSON для скачивания
window.generatedJson = templateJson;

showMessage('JSON шаблон успешно сгенерирован', 'success');
});

// Скачивание JSON файла
downloadBtn.addEventListener('click', function() {
if (!window.generatedJson) {
showMessage('Нет сгенерированного JSON для скачивания', 'error');
return;
}

const dataStr = JSON.stringify(window.generatedJson, null, 2);
const dataBlob = new Blob([dataStr], {type: 'application/json'});

const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = 'литология_шаблон.json';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);

showMessage('Файл успешно скачан', 'success');
});

// Функция для отображения сообщений
function showMessage(message, type) {
messageArea.innerHTML = '<div class="message ' + type + '">' + message + '</div>';

// Автоматическое скрытие сообщения через 5 секунд
setTimeout(function() {
messageArea.innerHTML = '';
}, 5000);
}

// Инициализация при загрузке страницы
window.onload = function() {
// Добавляем пример данных для демонстрации
rockDataInput.value = "1300 Суглинок\n1700 Илы\n1800 Торф\n1400 Супесь\n2300 Аргиллит\n2400 Алевролит";
};
</script>
</body>
</html>

234234234
Прикрепления:
0971144.zip (7.0 Kb)
отлично работает, теперь все работает как надо, большая часть svg теперь корректно отображается,

но есть некоторые которые не отображаются к примеру вот это изображение
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" width="282px" height="142px" viewBox="0 0 282 142" version="1.1" id="svg1">
<defs id="defs1"/>
<g id="layer1">
<path id="35707" d="M 221 30.5 A 9.75 10.5 0 0 0 240.5 30.5" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="41" d="M 240.5 30.5 A 9.75 10.5 0 0 1 260 30.5" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="35708" d="M 21 31 A 9.75 11 0 0 0 40.5 31" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="42" d="M 40.5 31 A 9.75 11 0 0 1 60 31" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="28684" d="M 291 19 A 14 11 0 0 0 319 19" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="23" d="M 319 19 A 14 11 0 0 1 347 19" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="28686" d="M 294 100.5 A 14 10.5 0 0 0 322 100.5" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="24" d="M 322 100.5 A 14 10.5 0 0 1 350 100.5" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="28792" d="M 120 30 A 10 10 0 0 0 140 30" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="26" d="M 140 30 A 10 10 0 0 1 160 30" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="28794" d="M 74 69.5 A 9.75 9.5 0 0 0 93.5 69.5" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="28" d="M 93.5 69.5 A 9.75 9.5 0 0 1 113 69.5" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="28795" d="M 169 70 A 10.25 10 0 0 0 189.5 70" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="29" d="M 189.5 70 A 10.25 10 0 0 1 210 70" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="28797" d="M 21 109.5 A 9.75 8.5 0 0 0 40.5 109.5" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="31" d="M 40.5 109.5 A 9.75 8.5 0 0 1 60 109.5" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="28798" d="M 120 108 A 10 9 0 0 0 140 108" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="32" d="M 140 108 A 10 9 0 0 1 160 108" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="28799" d="M 220 109 A 9.25 9 0 0 0 238.5 109" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="33" d="M 238.5 109 A 9.25 9 0 0 1 257 109" stroke-width="NA" style="fill:none;stroke:#000000;"/><ellipse id="3324" cx="319" cy="61" rx="4" ry="4" stroke-width="2" fill="#000000" stroke="#000000"/><ellipse id="34913" cx="96" cy="36" rx="4" ry="4" stroke-width="2" fill="#000000" stroke="#000000"/><ellipse id="34914" cx="194" cy="36" rx="4" ry="4" stroke-width="2" fill="#000000" stroke="#000000"/><ellipse id="34916" cx="46" cy="79" rx="4" ry="4" stroke-width="2" fill="#000000" stroke="#000000"/><ellipse id="34917" cx="146" cy="78" rx="4" ry="4" stroke-width="2" fill="#000000" stroke="#000000"/><ellipse id="34918" cx="246" cy="77" rx="4" ry="4" stroke-width="2" fill="#000000" stroke="#000000"/><ellipse id="34919" cx="97" cy="118" rx="4" ry="4" stroke-width="2" fill="#000000" stroke="#000000"/><ellipse id="34920" cx="188" cy="111" rx="4" ry="4" stroke-width="2" fill="#000000" stroke="#000000"/></g>
</svg>

получается для этого именно файла вот такое (хотя другие работают корректно):

"data": "<StyledFill id=\"1763342045616\" name=\"\" objectName=\"\" brushColor=\"#000000\" penColor=\"#000000\" group=\"\">\n <SvgFill patternWidth=\"20\" svgContent=\"<?xml version='1.0' encoding='UTF-8'?> <svg id="svg1" height="142px" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" viewBox="0 0 282 142" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" width="282px" xmlns:dc="http://purl.org/dc/elements/1.1/" version="1.1"> <metadata> <rdf:RDF> <cc:Work> <dc:title>ИСИХОГИ_Литология</dc:title> </cc:Work> </rdf:RDF> </metadata> <defs id="defs1"/> <g id="layer1"> </g> </svg> \" lineWidth=\"0.4\" objectName=\"\" penWidth=\"0.4\" svgFilePath=\"C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/1300 Суглинок.svg\"/>\n</StyledFill>\n",
"desc": "Суглинок",
"value": 1300

а должен получится таким:
"data": "<StyledFill name=\"\" brushColor=\"#000000\" penColor=\"#000000\" id=\"1763340634935\" objectName=\"\" group=\"\">\n <SvgFill penWidth=\"0.4\" lineWidth=\"0.4\" svgFilePath=\"C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/1300 Суглинок.svg\" patternWidth=\"20\" svgContent=\"<?xml version='1.0' encoding='UTF-8'?> <svg id="svg1" height="142px" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" viewBox="0 0 282 142" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" width="282px" xmlns:dc="http://purl.org/dc/elements/1.1/" version="1.1"> <metadata> <rdf:RDF> <cc:Work> <dc:title>ИСИХОГИ_Литология</dc:title> </cc:Work> </rdf:RDF> </metadata> <defs id="defs1"/> <g id="layer1"> <path id="35707" d="M 221 30.5 A 9.75 10.5 0 0 0 240.5 30.5" stroke-width="NA" style="fill:none;stroke:#000000;"/> <path id="41" d="M 240.5 30.5 A 9.75 10.5 0 0 1 260 30.5" stroke-width="NA" style="fill:none;stroke:#000000;"/> <path id="35708" d="M 21 31 A 9.75 11 0 0 0 40.5 31" stroke-width="NA" style="fill:none;stroke:#000000;"/> <path id="42" d="M 40.5 31 A 9.75 11 0 0 1 60 31" stroke-width="NA" style="fill:none;stroke:#000000;"/> <path id="28684" d="M 291 19 A 14 11 0 0 0 319 19" stroke-width="NA" style="fill:none;stroke:#000000;"/> <path id="23" d="M 319 19 A 14 11 0 0 1 347 19" stroke-width="NA" style="fill:none;stroke:#000000;"/> <path id="28686" d="M 294 100.5 A 14 10.5 0 0 0 322 100.5" stroke-width="NA" style="fill:none;stroke:#000000;"/> <path id="24" d="M 322 100.5 A 14 10.5 0 0 1 350 100.5" stroke-width="NA" style="fill:none;stroke:#000000;"/> <path id="28792" d="M 120 30 A 10 10 0 0 0 140 30" stroke-width="NA" style="fill:none;stroke:#000000;"/> <path id="26" d="M 140 30 A 10 10 0 0 1 160 30" stroke-width="NA" style="fill:none;stroke:#000000;"/> <path id="28794" d="M 74 69.5 A 9.75 9.5 0 0 0 93.5 69.5" stroke-width="NA" style="fill:none;stroke:#000000;"/> <path id="28" d="M 93.5 69.5 A 9.75 9.5 0 0 1 113 69.5" stroke-width="NA" style="fill:none;stroke:#000000;"/> <path id="28795" d="M 169 70 A 10.25 10 0 0 0 189.5 70" stroke-width="NA" style="fill:none;stroke:#000000;"/> <path id="29" d="M 189.5 70 A 10.25 10 0 0 1 210 70" stroke-width="NA" style="fill:none;stroke:#000000;"/> <path id="28797" d="M 21 109.5 A 9.75 8.5 0 0 0 40.5 109.5" stroke-width="NA" style="fill:none;stroke:#000000;"/> <path id="31" d="M 40.5 109.5 A 9.75 8.5 0 0 1 60 109.5" stroke-width="NA" style="fill:none;stroke:#000000;"/> <path id="28798" d="M 120 108 A 10 9 0 0 0 140 108" stroke-width="NA" style="fill:none;stroke:#000000;"/> <path id="32" d="M 140 108 A 10 9 0 0 1 160 108" stroke-width="NA" style="fill:none;stroke:#000000;"/> <path id="28799" d="M 220 109 A 9.25 9 0 0 0 238.5 109" stroke-width="NA" style="fill:none;stroke:#000000;"/> <path id="33" d="M 238.5 109 A 9.25 9 0 0 1 257 109" stroke-width="NA" style="fill:none;stroke:#000000;"/> <ellipse id="3324" cx="319" fill="#000000" rx="4" cy="61" stroke-width="2" stroke="#000000" ry="4"/> <ellipse id="34913" cx="96" fill="#000000" rx="4" cy="36" stroke-width="2" stroke="#000000" ry="4"/> <ellipse id="34914" cx="194" fill="#000000" rx="4" cy="36" stroke-width="2" stroke="#000000" ry="4"/> <ellipse id="34916" cx="46" fill="#000000" rx="4" cy="79" stroke-width="2" stroke="#000000" ry="4"/> <ellipse id="34917" cx="146" fill="#000000" rx="4" cy="78" stroke-width="2" stroke="#000000" ry="4"/> <ellipse id="34918" cx="246" fill="#000000" rx="4" cy="77" stroke-width="2" stroke="#000000" ry="4"/> <ellipse id="34919" cx="97" fill="#000000" rx="4" cy="118" stroke-width="2" stroke="#000000" ry="4"/> <ellipse id="34920" cx="188" fill="#000000" rx="4" cy="111" stroke-width="2" stroke="#000000" ry="4"/> </g> </svg> \" objectName=\"\"/>\n</StyledFill>\n",
"desc": "Сглинок",
"value": 1300

также смотри в каждом svg прописывается patternWidth=\"20\" - это размер маркера - добавь возможность перед генерацией выставлять размер для всех svg
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Генератор шаблонов литологии</title>
<style>
/* Стили остаются без изменений */
* {
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

body {
margin: 0;
padding: 20px;
background-color: #f5f7fa;
color: #333;
}

.container {
max-width: 1200px;
margin: 0 auto;
background-color: white;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
padding: 25px;
}

h1 {
color: #2c3e50;
text-align: center;
margin-bottom: 30px;
border-bottom: 2px solid #3498db;
padding-bottom: 15px;
}

.section {
margin-bottom: 30px;
padding: 20px;
border-radius: 8px;
background-color: #f8f9fa;
}

.section-title {
font-size: 1.3rem;
color: #2c3e50;
margin-bottom: 15px;
display: flex;
align-items: center;
}

.form-group {
margin-bottom: 15px;
}

label {
display: block;
margin-bottom: 5px;
font-weight: 600;
color: #2c3e50;
}

input, textarea, select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}

textarea {
min-height: 100px;
resize: vertical;
}

button {
background-color: #3498db;
color: white;
border: none;
padding: 12px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
font-weight: 600;
transition: background-color 0.3s;
}

button:hover {
background-color: #2980b9;
}

button.secondary {
background-color: #95a5a6;
}

button.secondary:hover {
background-color: #7f8c8d;
}

table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}

th, td {
border: 1px solid #ddd;
padding: 12px;
text-align: left;
}

th {
background-color: #3498db;
color: white;
}

tr:nth-child(even) {
background-color: #f2f2f2;
}

.preview-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-top: 20px;
}

.preview-item {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
width: 200px;
text-align: center;
background-color: white;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

.preview-svg {
height: 100px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
}

.preview-svg svg {
max-width: 100%;
max-height: 100%;
}

.preview-code {
font-weight: bold;
color: #2c3e50;
}

.preview-name {
color: #7f8c8d;
font-size: 0.9rem;
}

.hidden {
display: none;
}

.message {
padding: 15px;
border-radius: 4px;
margin: 15px 0;
}

.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}

.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}

.actions {
display: flex;
gap: 10px;
margin-top: 20px;
}

.file-input-container {
position: relative;
overflow: hidden;
display: inline-block;
width: 100%;
}

.file-input-container input[type=file] {
position: absolute;
left: 0;
top: 0;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
}

.file-input-button {
display: block;
padding: 10px;
background: #f8f9fa;
border: 2px dashed #3498db;
border-radius: 4px;
text-align: center;
color: #3498db;
font-weight: 600;
}

.file-list {
margin-top: 10px;
max-height: 150px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
}

.file-item {
display: flex;
justify-content: space-between;
padding: 5px 0;
border-bottom: 1px solid #eee;
}

.file-item:last-child {
border-bottom: none;
}

.file-name {
flex-grow: 1;
}

.file-size {
color: #7f8c8d;
font-size: 0.8rem;
}

.data-table-container {
overflow-x: auto;
}

.help-text {
font-size: 0.9rem;
color: #7f8c8d;
margin-top: 5px;
}

/* Новые стили для JSON отображения */
#jsonContent {
background-color: #f8f9fa;
border: 1px solid #ddd;
border-radius: 4px;
padding: 15px;
max-height: 500px;
overflow-y: auto;
white-space: pre-wrap;
word-wrap: break-word;
font-family: 'Courier New', monospace;
font-size: 12px;
line-height: 1.4;
}
</style>
</head>
<body>
<div class="container">
<h1>Генератор шаблонов литологии</h1>

<div class="section">
<h2 class="section-title">1. Загрузка SVG-файлов с текстурами</h2>
<div class="form-group">
<label>Выберите папку с SVG-файлами:</label>
<div class="file-input-container">
<div class="file-input-button">Выбрать папку с SVG-файлами</div>
<input type="file" id="svgFolderInput" webkitdirectory multiple>
</div>
<div class="help-text">SVG-файлы должны быть названы в формате "Код_Породы Название.svg", например: "1000 Лед.svg"</div>
</div>

<div class="form-group">
<label>Путь к SVG файлам в программе:</label>
<input type="text" id="svgFilePath" placeholder="C:/Users/User/AppData/Local/OISTerra/OISTerraManager/svg/" value="C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/">
<div class="help-text">Укажите путь, который будет использоваться в сгенерированном JSON файле</div>
</div>

<div id="fileList" class="file-list hidden">
<!-- Список загруженных файлов будет здесь -->
</div>
</div>

<div class="section">
<h2 class="section-title">2. Ввод данных о породах</h2>
<div class="form-group">
<label>Добавить данные о породах:</label>
<div class="help-text">Введите данные в формате: Код_Породы Название (каждая порода с новой строки)</div>
<textarea id="rockDataInput" placeholder="1300 Суглинок
1700 Илы
1800 Торф
1400 Супесь
2300 Аргиллит
2400 Алевролит"></textarea>
</div>

<div class="actions">
<button id="parseDataBtn">Обработать данные</button>
<button id="addRockBtn" class="secondary">Добавить породу вручную</button>
</div>

<div id="rockTableContainer" class="data-table-container hidden">
<h3>Список пород:</h3>
<table id="rockTable">
<thead>
<tr>
<th>Код породы</th>
<th>Наименование породы</th>
<th>SVG файл</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="rockTableBody">
<!-- Данные о породах будут здесь -->
</tbody>
</table>
</div>
</div>

<div class="section">
<h2 class="section-title">3. Предварительный просмотр</h2>
<div id="previewContainer" class="preview-container">
<!-- Предварительный просмотр будет здесь -->
</div>
</div>

<div class="section">
<h2 class="section-title">4. Генерация шаблона</h2>
<div class="form-group">
<label>Название палитры:</label>
<input type="text" id="paletteName" value="тест_лито">
</div>

<div class="form-group">
<label>Название шаблона корреляции:</label>
<input type="text" id="templateName" value="Корреляционный шаблон">
</div>

<div class="form-group">
<label>Размер patternWidth для SVG:</label>
<input type="number" id="patternWidth" value="20" min="1" max="100">
<div class="help-text">Размер маркера для всех SVG текстур (рекомендуется 20)</div>
</div>

<div class="actions">
<button id="generateBtn">Сгенерировать JSON шаблон</button>
<button id="downloadBtn" class="secondary hidden">Скачать шаблон</button>
</div>

<div id="messageArea"></div>

<div id="jsonOutput" class="hidden">
<h3>Сгенерированный JSON:</h3>
<pre id="jsonContent"></pre>
</div>
</div>
</div>

<script>
// Переменные для хранения данных
let svgFiles = {};
let rockData = [];

// Элементы DOM
const svgFolderInput = document.getElementById('svgFolderInput');
const svgFilePathInput = document.getElementById('svgFilePath');
const patternWidthInput = document.getElementById('patternWidth');
const fileList = document.getElementById('fileList');
const rockDataInput = document.getElementById('rockDataInput');
const parseDataBtn = document.getElementById('parseDataBtn');
const addRockBtn = document.getElementById('addRockBtn');
const rockTableContainer = document.getElementById('rockTableContainer');
const rockTableBody = document.getElementById('rockTableBody');
const previewContainer = document.getElementById('previewContainer');
const paletteNameInput = document.getElementById('paletteName');
const templateNameInput = document.getElementById('templateName');
const generateBtn = document.getElementById('generateBtn');
const downloadBtn = document.getElementById('downloadBtn');
const messageArea = document.getElementById('messageArea');
const jsonOutput = document.getElementById('jsonOutput');
const jsonContent = document.getElementById('jsonContent');

// Функция для преобразования SVG в формат как в оригинале
function convertSvgToOriginalFormat(svgContent) {
// Создаем временный div для парсинга SVG
const tempDiv = document.createElement('div');
tempDiv.innerHTML = svgContent;
const svgElement = tempDiv.querySelector('svg');

if (!svgElement) return svgContent;

// Создаем SVG с атрибутами как в оригинале
let originalSvg = `<?xml version='1.0' encoding='UTF-8'?>\n<svg id="svg1" height="142px" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" viewBox="0 0 282 142" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" width="282px" xmlns:dc="http://purl.org/dc/elements/1.1/" version="1.1">\n <metadata>\n <rdf:RDF>\n <cc:Work>\n <dc:title>ИСИХОГИ_Литология</dc:title>\n </cc:Work>\n </rdf:RDF>\n </metadata>\n <defs id="defs1"/>\n <g id="layer1">\n`;

// Добавляем все path элементы
const paths = svgElement.querySelectorAll('path');
paths.forEach(path => {
const id = path.getAttribute('id') || '';
const d = path.getAttribute('d') || '';
const strokeWidth = path.getAttribute('stroke-width') || '';
const style = path.getAttribute('style') || '';

originalSvg += ` <path id="${id}" d="${d}" stroke-width="${strokeWidth}" style="${style}"/>\n`;
});

// Добавляем все line элементы в формате оригинала
const lines = svgElement.querySelectorAll('line');
lines.forEach(line => {
const id = line.getAttribute('id') || '';
const x1 = line.getAttribute('x1') || '';
const y1 = line.getAttribute('y1') || '';
const x2 = line.getAttribute('x2') || '';
const y2 = line.getAttribute('y2') || '';
const strokeWidth = line.getAttribute('stroke-width') || '';
const style = line.getAttribute('style') || '';

originalSvg += ` <line id="${id}" x1="${x1}" y1="${y1}" y2="${y2}" stroke-width="${strokeWidth}" style="${style}" x2="${x2}"/>\n`;
});

// Добавляем все ellipse элементы в формате оригинала
const ellipses = svgElement.querySelectorAll('ellipse');
ellipses.forEach(ellipse => {
const id = ellipse.getAttribute('id') || '';
const cx = ellipse.getAttribute('cx') || '';
const cy = ellipse.getAttribute('cy') || '';
const rx = ellipse.getAttribute('rx') || '';
const ry = ellipse.getAttribute('ry') || '';
const strokeWidth = ellipse.getAttribute('stroke-width') || '';
const fill = ellipse.getAttribute('fill') || '';
const stroke = ellipse.getAttribute('stroke') || '';

originalSvg += ` <ellipse id="${id}" cx="${cx}" fill="${fill}" rx="${rx}" cy="${cy}" stroke-width="${strokeWidth}" stroke="${stroke}" ry="${ry}"/>\n`;
});

originalSvg += ` </g>\n</svg>\n`;

return originalSvg;
}

// Функция для правильного экранирования SVG контента для XML
function escapeSvgForXml(svgContent) {
// Сначала преобразуем SVG в формат оригинала
let convertedSvg = convertSvgToOriginalFormat(svgContent);

// Затем экранируем все специальные символы
let escaped = convertedSvg
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');

// Заменяем переносы строк
escaped = escaped.replace(/\n/g, ' ');

return escaped;
}

// Обработка загрузки SVG файлов
svgFolderInput.addEventListener('change', function(e) {
const files = Array.from(e.target.files);
svgFiles = {};

// Очистка списка файлов
fileList.innerHTML = '';

// Фильтрация только SVG файлов
const svgFilesList = files.filter(file => file.name.toLowerCase().endsWith('.svg'));

if (svgFilesList.length === 0) {
fileList.innerHTML = '<div>SVG файлы не найдены</div>';
fileList.classList.remove('hidden');
return;
}

// Обработка каждого SVG файла
svgFilesList.forEach(file => {
const fileName = file.name;
const codeMatch = fileName.match(/^(\d+)/);

if (codeMatch) {
const code = codeMatch[1];

const reader = new FileReader();
reader.onload = function(e) {
const svgContent = e.target.result;
svgFiles[code] = {
name: fileName,
content: svgContent
};

// Добавление в список файлов
const fileItem = document.createElement('div');
fileItem.className = 'file-item';
fileItem.innerHTML = `
<div class="file-name">${fileName}</div>
<div class="file-size">${(file.size / 1024).toFixed(2)} KB</div>
`;
fileList.appendChild(fileItem);

// Обновление таблицы пород, если код уже есть
updateRockTableWithSvg(code);
};
reader.readAsText(file);
}
});

fileList.classList.remove('hidden');
showMessage(`Загружено ${svgFilesList.length} SVG файлов`, 'success');
});

// Обработка данных о породах
parseDataBtn.addEventListener('click', function() {
const inputText = rockDataInput.value.trim();
if (!inputText) {
showMessage('Введите данные о породах', 'error');
return;
}

const lines = inputText.split('\n');
rockData = [];

lines.forEach(line => {
const trimmedLine = line.trim();
if (trimmedLine) {
const parts = trimmedLine.split(/\s+/);
if (parts.length >= 2) {
const code = parts[0];
const name = parts.slice(1).join(' ');
rockData.push({
code: code,
name: name,
svgFile: svgFiles[code] ? svgFiles[code].name : null
});
}
}
});

updateRockTable();
updatePreview();
showMessage(`Обработано ${rockData.length} пород`, 'success');
});

// Добавление породы вручную
addRockBtn.addEventListener('click', function() {
const code = prompt('Введите код породы:');
if (!code) return;

const name = prompt('Введите название породы:');
if (!name) return;

rockData.push({
code: code,
name: name,
svgFile: svgFiles[code] ? svgFiles[code].name : null
});

updateRockTable();
updatePreview();
showMessage(`Добавлена порода: ${code} ${name}`, 'success');
});

// Обновление таблицы пород
function updateRockTable() {
rockTableBody.innerHTML = '';

if (rockData.length === 0) {
rockTableContainer.classList.add('hidden');
return;
}

rockData.forEach((rock, index) => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${rock.code}</td>
<td>${rock.name}</td>
<td>${rock.svgFile || 'Не найден'}</td>
<td>
<button onclick="removeRock(${index})">Удалить</button>
</td>
`;
rockTableBody.appendChild(row);
});

rockTableContainer.classList.remove('hidden');
}

// Обновление таблицы при наличии SVG
function updateRockTableWithSvg(code) {
rockData.forEach(rock => {
if (rock.code === code) {
rock.svgFile = svgFiles[code].name;
}
});

updateRockTable();
updatePreview();
}

// Удаление породы
function removeRock(index) {
rockData.splice(index, 1);
updateRockTable();
updatePreview();
showMessage('Порода удалена', 'success');
}

// Обновление предварительного просмотра
function updatePreview() {
previewContainer.innerHTML = '';

if (rockData.length === 0) {
previewContainer.innerHTML = '<p>Нет данных для предварительного просмотра</p>';
return;
}

rockData.forEach(rock => {
const previewItem = document.createElement('div');
previewItem.className = 'preview-item';

let svgPreview = '<div>SVG не загружен</div>';
if (rock.svgFile && svgFiles[rock.code]) {
svgPreview = svgFiles[rock.code].content;
}

previewItem.innerHTML = `
<div class="preview-svg">${svgPreview}</div>
<div class="preview-code">${rock.code}</div>
<div class="preview-name">${rock.name}</div>
`;

previewContainer.appendChild(previewItem);
});
}

// Генерация JSON шаблона
generateBtn.addEventListener('click', function() {
if (rockData.length === 0) {
showMessage('Нет данных о породах для генерации шаблона', 'error');
return;
}

const paletteName = paletteNameInput.value || 'тест_лито';
const templateName = templateNameInput.value || 'Корреляционный шаблон';
const svgFilePath = svgFilePathInput.value || 'C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/';
const patternWidth = patternWidthInput.value || '20';

// Создание элементов палитры
const paletteItems = rockData.map(rock => {
let svgContent = '';

if (rock.svgFile && svgFiles[rock.code]) {
// Правильное экранирование SVG для XML
svgContent = escapeSvgForXml(svgFiles[rock.code].content);
}

// Генерация случайного ID
const id = Date.now() + Math.floor(Math.random() * 1000);

// Полный путь к файлу SVG
const fullSvgFilePath = svgFilePath + rock.svgFile;

// Точный порядок атрибутов как в оригинале
return {
"color": "#ffffffff",
"data": `<StyledFill id="${id}" name="" objectName="" brushColor="#000000" penColor="#000000" group="">\n <SvgFill patternWidth="${patternWidth}" svgContent="${svgContent}" lineWidth="0.4" objectName="" penWidth="0.4" svgFilePath="${fullSvgFilePath}"/>\n</StyledFill>\n`,
"desc": rock.name,
"value": parseInt(rock.code)
};
});

// Создание основного JSON объекта с фиксированными ID как в рабочем примере
const templateJson = {
"Info": "OISTerra CorrTemplates",
"Palettes": [
{
"Items": paletteItems,
"id": Date.now(),
"name": paletteName,
"transp": true,
"type": 1
}
],
"ScoPaletteVersion": 1,
"corrTemplates": [
{
"Tracks": [
{
"allHeight": false,
"hTxtSet": {
"vis": true
},
"lineStyle": {
"fCol": "#ffc8ffff"
},
"trackId": 1763096947027,
"type": 0
},
{
"Logs": [
{
"logDataVers": 1,
"logId": 1763096947080,
"logParams": [
{
"contId": 1763096947014,
"contName": "РИГИС",
"id": 1763096947013,
"nm": "литология"
}
],
"logType": 3,
"trackId": 1763096947055
}
],
"allHeight": false,
"hTxtSet": {
"vis": false
},
"lineStyle": {
"fCol": "#ffc8ffff"
},
"trackId": 1763096947055,
"width": 50.69166666666666
}
],
"Version": {
"build": 3,
"fillData": 0,
"horizonView": 0,
"logData": 1,
"logRange": 0,
"plastBuild": 0,
"plastData": 0,
"stratBuild": 0,
"stratData": 0,
"stratWidth": 0,
"timeDepth": 0,
"trackWidth": 1,
"view": 7
},
"header": {
"textBlockList": {
"textBlocks": [
{
"blockId": 1763096947023,
"textItems": [
{
"blockId": 1763096947023,
"content": "[WellName]",
"font": {
"b": true
},
"itemId": 1763096947024,
"paramsHash": [
{
"id": -1,
"key": "[WellName]"
}
]
}
]
}
]
}
},
"id": Date.now() + 1000,
"name": templateName,
"plastList": {
"columnId": 1763096947025,
"isPlast": true,
"type": 0
}
}
],
"corrTemplatesVersion": 0
};

// Отображение JSON с правильным форматированием
jsonContent.textContent = JSON.stringify(templateJson, null, 2);
jsonOutput.classList.remove('hidden');
downloadBtn.classList.remove('hidden');

// Сохранение JSON для скачивания
window.generatedJson = templateJson;

showMessage('JSON шаблон успешно сгенерирован', 'success');
});

// Скачивание JSON файла
downloadBtn.addEventListener('click', function() {
if (!window.generatedJson) {
showMessage('Нет сгенерированного JSON для скачивания', 'error');
return;
}

const dataStr = JSON.stringify(window.generatedJson, null, 2);
const dataBlob = new Blob([dataStr], {type: 'application/json'});

const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = 'литология_шаблон.json';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);

showMessage('Файл успешно скачан', 'success');
});

// Функция для отображения сообщений
function showMessage(message, type) {
messageArea.innerHTML = `<div class="message ${type}">${message}</div>`;

// Автоматическое скрытие сообщения через 5 секунд
setTimeout(() => {
messageArea.innerHTML = '';
}, 5000);
}

// Инициализация при загрузке страницы
window.onload = function() {
// Добавляем пример данных для демонстрации
rockDataInput.value = `1300 Суглинок
1700 Илы
1800 Торф
1400 Супесь
2300 Аргиллит
2400 Алевролит`;
};
</script>
</body>
</html>

Добавлено (2025-11-17, 11:29)
---------------------------------------------
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Генератор шаблонов литологии</title>
<style>
/* Все стили остаются без изменений */
* {
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

body {
margin: 0;
padding: 20px;
background-color: #f5f7fa;
color: #333;
}

.container {
max-width: 1200px;
margin: 0 auto;
background-color: white;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
padding: 25px;
}

h1 {
color: #2c3e50;
text-align: center;
margin-bottom: 30px;
border-bottom: 2px solid #3498db;
padding-bottom: 15px;
}

.section {
margin-bottom: 30px;
padding: 20px;
border-radius: 8px;
background-color: #f8f9fa;
}

.section-title {
font-size: 1.3rem;
color: #2c3e50;
margin-bottom: 15px;
display: flex;
align-items: center;
}

.form-group {
margin-bottom: 15px;
}

label {
display: block;
margin-bottom: 5px;
font-weight: 600;
color: #2c3e50;
}

input, textarea, select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}

textarea {
min-height: 100px;
resize: vertical;
}

button {
background-color: #3498db;
color: white;
border: none;
padding: 12px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
font-weight: 600;
transition: background-color 0.3s;
}

button:hover {
background-color: #2980b9;
}

button.secondary {
background-color: #95a5a6;
}

button.secondary:hover {
background-color: #7f8c8d;
}

table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}

th, td {
border: 1px solid #ddd;
padding: 12px;
text-align: left;
}

th {
background-color: #3498db;
color: white;
}

tr:nth-child(even) {
background-color: #f2f2f2;
}

.preview-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-top: 20px;
}

.preview-item {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
width: 200px;
text-align: center;
background-color: white;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

.preview-svg {
height: 100px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
}

.preview-svg svg {
max-width: 100%;
max-height: 100%;
}

.preview-code {
font-weight: bold;
color: #2c3e50;
}

.preview-name {
color: #7f8c8d;
font-size: 0.9rem;
}

.hidden {
display: none;
}

.message {
padding: 15px;
border-radius: 4px;
margin: 15px 0;
}

.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}

.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}

.actions {
display: flex;
gap: 10px;
margin-top: 20px;
}

.file-input-container {
position: relative;
overflow: hidden;
display: inline-block;
width: 100%;
}

.file-input-container input[type=file] {
position: absolute;
left: 0;
top: 0;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
}

.file-input-button {
display: block;
padding: 10px;
background: #f8f9fa;
border: 2px dashed #3498db;
border-radius: 4px;
text-align: center;
color: #3498db;
font-weight: 600;
}

.file-list {
margin-top: 10px;
max-height: 150px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
}

.file-item {
display: flex;
justify-content: space-between;
padding: 5px 0;
border-bottom: 1px solid #eee;
}

.file-item:last-child {
border-bottom: none;
}

.file-name {
flex-grow: 1;
}

.file-size {
color: #7f8c8d;
font-size: 0.8rem;
}

.data-table-container {
overflow-x: auto;
}

.help-text {
font-size: 0.9rem;
color: #7f8c8d;
margin-top: 5px;
}

/* Новые стили для JSON отображения */
#jsonContent {
background-color: #f8f9fa;
border: 1px solid #ddd;
border-radius: 4px;
padding: 15px;
max-height: 500px;
overflow-y: auto;
white-space: pre-wrap;
word-wrap: break-word;
font-family: 'Courier New', monospace;
font-size: 12px;
line-height: 1.4;
}
</style>
</head>
<body>
<div class="container">
<h1>Генератор шаблонов литологии</h1>

<div class="section">
<h2 class="section-title">1. Загрузка SVG-файлов с текстурами</h2>
<div class="form-group">
<label>Выберите папку с SVG-файлами:</label>
<div class="file-input-container">
<div class="file-input-button">Выбрать папку с SVG-файлами</div>
<input type="file" id="svgFolderInput" webkitdirectory multiple>
</div>
<div class="help-text">SVG-файлы должны быть названы в формате "Код_Породы Название.svg", например: "1000 Лед.svg"</div>
</div>

<div class="form-group">
<label>Путь к SVG файлам в программе:</label>
<input type="text" id="svgFilePath" placeholder="C:/Users/User/AppData/Local/OISTerra/OISTerraManager/svg/" value="C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/">
<div class="help-text">Укажите путь, который будет использоваться в сгенерированном JSON файле</div>
</div>

<div id="fileList" class="file-list hidden">
<!-- Список загруженных файлов будет здесь -->
</div>
</div>

<div class="section">
<h2 class="section-title">2. Ввод данных о породах</h2>
<div class="form-group">
<label>Добавить данные о породах:</label>
<div class="help-text">Введите данные в формате: Код_Породы Название (каждая порода с новой строки)</div>
<textarea id="rockDataInput" placeholder="1300 Суглинок
1700 Илы
1800 Торф
1400 Супесь
2300 Аргиллит
2400 Алевролит"></textarea>
</div>

<div class="actions">
<button id="parseDataBtn">Обработать данные</button>
<button id="addRockBtn" class="secondary">Добавить породу вручную</button>
</div>

<div id="rockTableContainer" class="data-table-container hidden">
<h3>Список пород:</h3>
<table id="rockTable">
<thead>
<tr>
<th>Код породы</th>
<th>Наименование породы</th>
<th>SVG файл</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="rockTableBody">
<!-- Данные о породах будут здесь -->
</tbody>
</table>
</div>
</div>

<div class="section">
<h2 class="section-title">3. Предварительный просмотр</h2>
<div id="previewContainer" class="preview-container">
<!-- Предварительный просмотр будет здесь -->
</div>
</div>

<div class="section">
<h2 class="section-title">4. Генерация шаблона</h2>
<div class="form-group">
<label>Название палитры:</label>
<input type="text" id="paletteName" value="тест_лито">
</div>

<div class="form-group">
<label>Название шаблона корреляции:</label>
<input type="text" id="templateName" value="Корреляционный шаблон">
</div>

<div class="form-group">
<label>Размер patternWidth для SVG:</label>
<input type="number" id="patternWidth" value="20" min="1" max="100">
<div class="help-text">Размер маркера для всех SVG текстур (рекомендуется 20)</div>
</div>

<div class="actions">
<button id="generateBtn">Сгенерировать JSON шаблон</button>
<button id="downloadBtn" class="secondary hidden">Скачать шаблон</button>
</div>

<div id="messageArea"></div>

<div id="jsonOutput" class="hidden">
<h3>Сгенерированный JSON:</h3>
<pre id="jsonContent"></pre>
</div>
</div>
</div>

<script>
// Переменные для хранения данных
let svgFiles = {};
let rockData = [];

// Элементы DOM
const svgFolderInput = document.getElementById('svgFolderInput');
const svgFilePathInput = document.getElementById('svgFilePath');
const patternWidthInput = document.getElementById('patternWidth');
const fileList = document.getElementById('fileList');
const rockDataInput = document.getElementById('rockDataInput');
const parseDataBtn = document.getElementById('parseDataBtn');
const addRockBtn = document.getElementById('addRockBtn');
const rockTableContainer = document.getElementById('rockTableContainer');
const rockTableBody = document.getElementById('rockTableBody');
const previewContainer = document.getElementById('previewContainer');
const paletteNameInput = document.getElementById('paletteName');
const templateNameInput = document.getElementById('templateName');
const generateBtn = document.getElementById('generateBtn');
const downloadBtn = document.getElementById('downloadBtn');
const messageArea = document.getElementById('messageArea');
const jsonOutput = document.getElementById('jsonOutput');
const jsonContent = document.getElementById('jsonContent');

// Функция для преобразования SVG в формат как в оригинале
function convertSvgToOriginalFormat(svgContent) {
// Создаем временный div для парсинга SVG
const tempDiv = document.createElement('div');
tempDiv.innerHTML = svgContent;
const svgElement = tempDiv.querySelector('svg');

if (!svgElement) return svgContent;

// Создаем SVG с атрибутами как в оригинале
let originalSvg = `<?xml version='1.0' encoding='UTF-8'?>\n<svg id="svg1" height="142px" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" viewBox="0 0 282 142" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" width="282px" xmlns:dc="http://purl.org/dc/elements/1.1/" version="1.1">\n <metadata>\n <rdf:RDF>\n <cc:Work>\n <dc:title>ИСИХОГИ_Литология</dc:title>\n </cc:Work>\n </rdf:RDF>\n </metadata>\n <defs id="defs1"/>\n <g id="layer1">\n`;

// Добавляем все path элементы
const paths = svgElement.querySelectorAll('path');
paths.forEach(path => {
const id = path.getAttribute('id') || '';
const d = path.getAttribute('d') || '';
const strokeWidth = path.getAttribute('stroke-width') || '';
const style = path.getAttribute('style') || '';

originalSvg += ` <path id="${id}" d="${d}" stroke-width="${strokeWidth}" style="${style}"/>\n`;
});

// Добавляем все line элементы в формате оригинала
const lines = svgElement.querySelectorAll('line');
lines.forEach(line => {
const id = line.getAttribute('id') || '';
const x1 = line.getAttribute('x1') || '';
const y1 = line.getAttribute('y1') || '';
const x2 = line.getAttribute('x2') || '';
const y2 = line.getAttribute('y2') || '';
const strokeWidth = line.getAttribute('stroke-width') || '';
const style = line.getAttribute('style') || '';

originalSvg += ` <line id="${id}" x1="${x1}" y1="${y1}" y2="${y2}" stroke-width="${strokeWidth}" style="${style}" x2="${x2}"/>\n`;
});

// Добавляем все ellipse элементы в формате оригинала
const ellipses = svgElement.querySelectorAll('ellipse');
ellipses.forEach(ellipse => {
const id = ellipse.getAttribute('id') || '';
const cx = ellipse.getAttribute('cx') || '';
const cy = ellipse.getAttribute('cy') || '';
const rx = ellipse.getAttribute('rx') || '';
const ry = ellipse.getAttribute('ry') || '';
const strokeWidth = ellipse.getAttribute('stroke-width') || '';
const fill = ellipse.getAttribute('fill') || '';
const stroke = ellipse.getAttribute('stroke') || '';

originalSvg += ` <ellipse id="${id}" cx="${cx}" fill="${fill}" rx="${rx}" cy="${cy}" stroke-width="${strokeWidth}" stroke="${stroke}" ry="${ry}"/>\n`;
});

originalSvg += ` </g>\n</svg>\n`;

return originalSvg;
}

// Функция для правильного экранирования SVG контента для XML
function escapeSvgForXml(svgContent) {
// Сначала преобразуем SVG в формат оригинала
let convertedSvg = convertSvgToOriginalFormat(svgContent);

// Затем экранируем все специальные символы - ИСПРАВЛЕНА ОШИБКА С КАВЫЧКАМИ
let escaped = convertedSvg
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');

// Заменяем переносы строк
escaped = escaped.replace(/\n/g, ' ');

return escaped;
}

// Обработка загрузки SVG файлов
svgFolderInput.addEventListener('change', function(e) {
const files = Array.from(e.target.files);
svgFiles = {};

// Очистка списка файлов
fileList.innerHTML = '';

// Фильтрация только SVG файлов
const svgFilesList = files.filter(file => file.name.toLowerCase().endsWith('.svg'));

if (svgFilesList.length === 0) {
fileList.innerHTML = '<div>SVG файлы не найдены</div>';
fileList.classList.remove('hidden');
return;
}

// Обработка каждого SVG файла
svgFilesList.forEach(file => {
const fileName = file.name;
const codeMatch = fileName.match(/^(\d+)/);

if (codeMatch) {
const code = codeMatch[1];

const reader = new FileReader();
reader.onload = function(e) {
const svgContent = e.target.result;
svgFiles[code] = {
name: fileName,
content: svgContent
};

// Добавление в список файлов
const fileItem = document.createElement('div');
fileItem.className = 'file-item';
fileItem.innerHTML = `
<div class="file-name">${fileName}</div>
<div class="file-size">${(file.size / 1024).toFixed(2)} KB</div>
`;
fileList.appendChild(fileItem);

// Обновление таблицы пород, если код уже есть
updateRockTableWithSvg(code);
};
reader.readAsText(file);
}
});

fileList.classList.remove('hidden');
showMessage(`Загружено ${svgFilesList.length} SVG файлов`, 'success');
});

// Обработка данных о породах
parseDataBtn.addEventListener('click', function() {
const inputText = rockDataInput.value.trim();
if (!inputText) {
showMessage('Введите данные о породах', 'error');
return;
}

const lines = inputText.split('\n');
rockData = [];

lines.forEach(line => {
const trimmedLine = line.trim();
if (trimmedLine) {
const parts = trimmedLine.split(/\s+/);
if (parts.length >= 2) {
const code = parts[0];
const name = parts.slice(1).join(' ');
rockData.push({
code: code,
name: name,
svgFile: svgFiles[code] ? svgFiles[code].name : null
});
}
}
});

updateRockTable();
updatePreview();
showMessage(`Обработано ${rockData.length} пород`, 'success');
});

// Добавление породы вручную
addRockBtn.addEventListener('click', function() {
const code = prompt('Введите код породы:');
if (!code) return;

const name = prompt('Введите название породы:');
if (!name) return;

rockData.push({
code: code,
name: name,
svgFile: svgFiles[code] ? svgFiles[code].name : null
});

updateRockTable();
updatePreview();
showMessage(`Добавлена порода: ${code} ${name}`, 'success');
});

// Обновление таблицы пород
function updateRockTable() {
rockTableBody.innerHTML = '';

if (rockData.length === 0) {
rockTableContainer.classList.add('hidden');
return;
}

rockData.forEach((rock, index) => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${rock.code}</td>
<td>${rock.name}</td>
<td>${rock.svgFile || 'Не найден'}</td>
<td>
<button onclick="removeRock(${index})">Удалить</button>
</td>
`;
rockTableBody.appendChild(row);
});

rockTableContainer.classList.remove('hidden');
}

// Обновление таблицы при наличии SVG
function updateRockTableWithSvg(code) {
rockData.forEach(rock => {
if (rock.code === code) {
rock.svgFile = svgFiles[code].name;
}
});

updateRockTable();
updatePreview();
}

// Удаление породы
function removeRock(index) {
rockData.splice(index, 1);
updateRockTable();
updatePreview();
showMessage('Порода удалена', 'success');
}

// Обновление предварительного просмотра
function updatePreview() {
previewContainer.innerHTML = '';

if (rockData.length === 0) {
previewContainer.innerHTML = '<p>Нет данных для предварительного просмотра</p>';
return;
}

rockData.forEach(rock => {
const previewItem = document.createElement('div');
previewItem.className = 'preview-item';

let svgPreview = '<div>SVG не загружен</div>';
if (rock.svgFile && svgFiles[rock.code]) {
svgPreview = svgFiles[rock.code].content;
}

previewItem.innerHTML = `
<div class="preview-svg">${svgPreview}</div>
<div class="preview-code">${rock.code}</div>
<div class="preview-name">${rock.name}</div>
`;

previewContainer.appendChild(previewItem);
});
}

// Генерация JSON шаблона
generateBtn.addEventListener('click', function() {
if (rockData.length === 0) {
showMessage('Нет данных о породах для генерации шаблона', 'error');
return;
}

const paletteName = paletteNameInput.value || 'тест_лито';
const templateName = templateNameInput.value || 'Корреляционный шаблон';
const svgFilePath = svgFilePathInput.value || 'C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/';
const patternWidth = patternWidthInput.value || '20';

// Создание элементов палитры
const paletteItems = rockData.map(rock => {
let svgContent = '';

if (rock.svgFile && svgFiles[rock.code]) {
// Правильное экранирование SVG для XML
svgContent = escapeSvgForXml(svgFiles[rock.code].content);
}

// Генерация случайного ID
const id = Date.now() + Math.floor(Math.random() * 1000);

// Полный путь к файлу SVG
const fullSvgFilePath = svgFilePath + rock.svgFile;

// Точный порядок атрибутов как в оригинале
return {
"color": "#ffffffff",
"data": `<StyledFill id="${id}" name="" objectName="" brushColor="#000000" penColor="#000000" group="">\n <SvgFill patternWidth="${patternWidth}" svgContent="${svgContent}" lineWidth="0.4" objectName="" penWidth="0.4" svgFilePath="${fullSvgFilePath}"/>\n</StyledFill>\n`,
"desc": rock.name,
"value": parseInt(rock.code)
};
});

// Создание основного JSON объекта с фиксированными ID как в рабочем примере
const templateJson = {
"Info": "OISTerra CorrTemplates",
"Palettes": [
{
"Items": paletteItems,
"id": Date.now(),
"name": paletteName,
"transp": true,
"type": 1
}
],
"ScoPaletteVersion": 1,
"corrTemplates": [
{
"Tracks": [
{
"allHeight": false,
"hTxtSet": {
"vis": true
},
"lineStyle": {
"fCol": "#ffc8ffff"
},
"trackId": 1763096947027,
"type": 0
},
{
"Logs": [
{
"logDataVers": 1,
"logId": 1763096947080,
"logParams": [
{
"contId": 1763096947014,
"contName": "РИГИС",
"id": 1763096947013,
"nm": "литология"
}
],
"logType": 3,
"trackId": 1763096947055
}
],
"allHeight": false,
"hTxtSet": {
"vis": false
},
"lineStyle": {
"fCol": "#ffc8ffff"
},
"trackId": 1763096947055,
"width": 50.69166666666666
}
],
"Version": {
"build": 3,
"fillData": 0,
"horizonView": 0,
"logData": 1,
"logRange": 0,
"plastBuild": 0,
"plastData": 0,
"stratBuild": 0,
"stratData": 0,
"stratWidth": 0,
"timeDepth": 0,
"trackWidth": 1,
"view": 7
},
"header": {
"textBlockList": {
"textBlocks": [
{
"blockId": 1763096947023,
"textItems": [
{
"blockId": 1763096947023,
"content": "[WellName]",
"font": {
"b": true
},
"itemId": 1763096947024,
"paramsHash": [
{
"id": -1,
"key": "[WellName]"
}
]
}
]
}
]
}
},
"id": Date.now() + 1000,
"name": templateName,
"plastList": {
"columnId": 1763096947025,
"isPlast": true,
"type": 0
}
}
],
"corrTemplatesVersion": 0
};

// Отображение JSON с правильным форматированием
jsonContent.textContent = JSON.stringify(templateJson, null, 2);
jsonOutput.classList.remove('hidden');
downloadBtn.classList.remove('hidden');

// Сохранение JSON для скачивания
window.generatedJson = templateJson;

showMessage('JSON шаблон успешно сгенерирован', 'success');
});

// Скачивание JSON файла
downloadBtn.addEventListener('click', function() {
if (!window.generatedJson) {
showMessage('Нет сгенерированного JSON для скачивания', 'error');
return;
}

const dataStr = JSON.stringify(window.generatedJson, null, 2);
const dataBlob = new Blob([dataStr], {type: 'application/json'});

const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = 'литология_шаблон.json';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);

showMessage('Файл успешно скачан', 'success');
});

// Функция для отображения сообщений
function showMessage(message, type) {
messageArea.innerHTML = `<div class="message ${type}">${message}</div>`;

// Автоматическое скрытие сообщения через 5 секунд
setTimeout(() => {
messageArea.innerHTML = '';
}, 5000);
}

// Инициализация при загрузке страницы
window.onload = function() {
// Добавляем пример данных для демонстрации
rockDataInput.value = `1300 Суглинок
1700 Илы
1800 Торф
1400 Супесь
2300 Аргиллит
2400 Алевролит`;
};
</script>
</body>
</html>

рр
Прикрепления:
ллл
Прикрепления:
{
"Info": "OISTerra CorrTemplates",
"Palettes": [
{
"Items": [
{
"color": "#ffff0000",
"data": "",
"desc": "красный",
"value": 1000
},
{
"color": "#ff00ff00",
"data": "",
"desc": "зеленый",
"value": 1001
}
],
"id": 1763358376409,
"name": "тест_возраст",
"transp": false,
"type": 1
}
],
"ScoPaletteVersion": 1,
"corrTemplates": [
],
"corrTemplatesVersion": 0
}

вводимые данные
0 гтвуаштув #ffffff
1100 QIII-IV #ffffeb
1000 QIV #fffff5
1200 QIII[4]=QIV #ffffeb
1300 QIII[3]=QIV #ffffe6
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Конвертер данных в JSON</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
}
.input-section, .output-section {
margin-bottom: 20px;
}
textarea {
width: 100%;
height: 200px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
resize: vertical;
font-family: monospace;
}
.example {
background-color: #f9f9f9;
padding: 10px;
border-radius: 4px;
margin-top: 10px;
font-size: 0.9em;
}
button {
background-color: #4CAF50;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
display: block;
margin: 0 auto;
}
button:hover {
background-color: #45a049;
}
.output-section {
display: none;
}
.color-preview {
display: inline-block;
width: 20px;
height: 20px;
border: 1px solid #ccc;
margin-right: 5px;
vertical-align: middle;
}
.success-message {
color: #4CAF50;
text-align: center;
margin-top: 10px;
}
</style>
</head>
<body>
<div class="container">
<h1>Конвертер данных в JSON</h1>

<div class="input-section">
<h2>Введите данные</h2>
<p>Формат: значение возраст_стратиграфии цвет</p>
<textarea id="inputData" placeholder="Пример:
1100 QIII-IV #ffffeb
1000 QIV #fffff5
1200 QIII[4]=QIV #ffffeb
1300 QIII[3]=QIV #ffffe6"></textarea>

<div class="example">
<strong>Пример заполнения:</strong><br>
1100 - значение вводится в value<br>
QIII-IV - возраст стратиграфии вводится в desc<br>
#ffffeb - цвет вводится в color<br>
id генерируется автоматически
</div>

<div>
<label for="paletteName">Название палитры:</label>
<input type="text" id="paletteName" value="тест_возраст">
</div>

<button id="convertBtn">Сгенерировать JSON</button>
</div>

<div class="output-section" id="outputSection">
<h2>Результат</h2>
<textarea id="outputJson" readonly></textarea>
<button id="downloadBtn">Скачать JSON файл</button>
<div class="success-message" id="successMessage"></div>
</div>
</div>

<script>
document.getElementById('convertBtn').addEventListener('click', function() {
const inputData = document.getElementById('inputData').value;
const paletteName = document.getElementById('paletteName').value || 'тест_возраст';

if (!inputData.trim()) {
alert('Пожалуйста, введите данные для конвертации');
return;
}

const items = [];
const lines = inputData.split('\n');

for (const line of lines) {
const trimmedLine = line.trim();
if (!trimmedLine) continue;

const parts = trimmedLine.split(/\s+/);
if (parts.length < 3) continue;

const value = parseInt(parts[0]);
if (isNaN(value)) continue;

const color = parts[parts.length - 1];
const desc = parts.slice(1, parts.length - 1).join(' ');

items.push({
color: color,
data: "",
desc: desc,
value: value
});
}

const jsonData = {
"Info": "OISTerra CorrTemplates",
"Palettes": [
{
"Items": items,
"id": Date.now(),
"name": paletteName,
"transp": false,
"type": 1
}
],
"ScoPaletteVersion": 1,
"corrTemplates": [],
"corrTemplatesVersion": 0
};

const outputJson = JSON.stringify(jsonData, null, 2);
document.getElementById('outputJson').value = outputJson;
document.getElementById('outputSection').style.display = 'block';
});

document.getElementById('downloadBtn').addEventListener('click', function() {
const jsonData = document.getElementById('outputJson').value;
const paletteName = document.getElementById('paletteName').value || 'тест_возраст';

const blob = new Blob([jsonData], { type: 'application/json' });
const url = URL.createObjectURL(blob);

const a = document.createElement('a');
a.href = url;
a.download = `${paletteName}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);

document.getElementById('successMessage').textContent = 'Файл успешно скачан!';
setTimeout(() => {
document.getElementById('successMessage').textContent = '';
}, 3000);
});
</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;
}

.group-toggle {
display: flex;
align-items: center;
gap: 5px;
font-size: 12px;
}

.group-toggle input {
margin: 0;
}

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

.group-section {
margin-bottom: 15px;
grid-column: 1 / -1;
}

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

.group-icons-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 15px;
}

.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 {
/* Стиль для обработанных 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;
grid-column: 1 / -1;
}

.no-results {
text-align: center;
padding: 40px;
color: #666;
grid-column: 1 / -1;
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="group-toggle" id="groupToggle" style="display: none;">
<input type="checkbox" id="showGroups" checked>
<label for="showGroups">Отображать группы</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.width;
canvas.height = img.height;

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

// Определяем розовый цвет для замены на прозрачный
// Основные варианты розового цвета в BMP масках
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];

// Ярко-розовый (255, 0, 255) - наиболее распространенный
if (r > 250 && g < 10 && b > 250) {
data[i + 3] = 0; // Прозрачность
}
// Светло-розовый (255, 192, 203)
else if (r > 250 && g > 180 && b > 190) {
data[i + 3] = 0;
}
// Другие оттенки розового
else if (r > 240 && g < 100 && b > 240) {
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
});
}
});
}

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

img.onload = async function() {
// Обрабатываем только BMP файлы
if (src.toLowerCase().endsWith('.bmp')) {
try {
const result = await processBMPWithPinkBackground(img);
resolve(result);
} catch (error) {
resolve({ element: img, processed: false });
}
} else {
resolve({ element: img, processed: false });
}
};

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

img.src = src;
img.alt = alt;
});
}

// Функция для группировки иконок
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 iconsContainer = document.createElement('div');
iconsContainer.className = 'group-icons-container';

// Создаем колонки для иконок группы
const itemsPerColumn = Math.ceil(icons.length / Math.ceil(icons.length / 20));

for (let i = 0; i < icons.length; i += itemsPerColumn) {
const columnIcons = icons.slice(i, i + itemsPerColumn);
const column = createIconColumn(columnIcons);
iconsContainer.appendChild(column);
}

section.appendChild(iconsContainer);
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 renderIconsWithoutGroups(icons) {
const container = document.getElementById('iconsContainer');
container.innerHTML = '';

// Сортируем иконки по алфавиту
const sortedIcons = [...icons].sort((a, b) => a.name.localeCompare(b.name));

// Создаем колонки для отсортированных иконок
const itemsPerColumn = Math.ceil(sortedIcons.length / Math.ceil(sortedIcons.length / 20));

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

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

// Группируем иконки
const { groups, noGroup } = groupIcons(filteredIcons);
const hasGroups = Object.keys(groups).length > 0;

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

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

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

const iconsContainer = document.createElement('div');
iconsContainer.className = 'group-icons-container';

const itemsPerColumn = Math.ceil(noGroup.length / Math.ceil(noGroup.length / 20));
for (let i = 0; i < noGroup.length; i += itemsPerColumn) {
const columnIcons = noGroup.slice(i, i + itemsPerColumn);
const column = createIconColumn(columnIcons);
iconsContainer.appendChild(column);
}

noGroupSection.appendChild(iconsContainer);
container.appendChild(noGroupSection);
} else {
// Если групп нет, просто отображаем иконки без заголовка
const itemsPerColumn = Math.ceil(noGroup.length / Math.ceil(noGroup.length / 20));
for (let i = 0; i < noGroup.length; i += itemsPerColumn) {
const columnIcons = noGroup.slice(i, i + itemsPerColumn);
const column = createIconColumn(columnIcons);
container.appendChild(column);
}
}
}
}

// Функция для фильтрации иконок по поисковому запросу и группе
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 (filteredIcons.length === 0) {
const container = document.getElementById('iconsContainer');
container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>';
updateStats();
return;
}

if (showGroups) {
renderIconsWithGroups();
} else {
renderIconsWithoutGroups(filteredIcons);
}

updateStats();
}

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

// Инициализация комбобокса групп и переключателя
function initGroupControls(groups) {
const groupSelect = document.getElementById('groupSelect');
const groupToggle = document.getElementById('groupToggle');

groupSelect.innerHTML = '<option value="">Все группы</option>';

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

const hasGroups = groups.length > 0;
groupSelect.style.display = hasGroups ? 'block' : 'none';
groupToggle.style.display = hasGroups ? 'flex' : 'none';

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

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

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

initGroupControls(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);
}
});
}

// Загружаем иконки при загрузке страницы
window.addEventListener('DOMContentLoaded', function() {
loadIcons();
setupSearch();
});
</script>
</body>
</html>

Добавлено (2025-11-25, 12:18)
---------------------------------------------
<!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;
}

.group-toggle {
display: flex;
align-items: center;
gap: 5px;
font-size: 12px;
}

.group-toggle input {
margin: 0;
}

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

.group-section {
flex: 0 1 auto;
min-width: 350px;
max-width: 100%;
}

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

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

.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="group-toggle" id="groupToggle" style="display: none;">
<input type="checkbox" id="showGroups" checked>
<label for="showGroups">Отображать группы</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 processBMPWithChromaKey(img) {
return new Promise((resolve) => {
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;

// Рисуем изображение на canvas
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) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];

// Основной хромакей - ярко-розовый (255, 0, 255)
const isBrightPink = r > 250 && g < 50 && b > 250;

// Светло-розовые оттенки
const isLightPink = r > 240 && g > 170 && b > 220 &&
Math.abs(r - b) < 30 &&
r - g > 50;

// Средние розовые оттенки
const isMediumPink = r > 220 && g > 120 && b > 200 &&
r - g > 40 && r - b < 50;

if (isBrightPink || isLightPink || isMediumPink) {
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
});
}
});
}

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

img.onload = async function() {
// Обрабатываем только BMP файлы
if (src.toLowerCase().endsWith('.bmp')) {
try {
console.log('Обработка BMP:', src);
const result = await processBMPWithChromaKey(img);
console.log('BMP обработан успешно');
resolve(result);
} catch (error) {
console.warn('Ошибка при обработке BMP:', error);
resolve({ element: img, processed: false });
}
} else {
// Для других форматов просто возвращаем изображение
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=';
resolve({ element: placeholder, processed: false });
};

img.src = src;
img.alt = alt;
});
}

// Функция для группировки иконок
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 renderIconsWithoutGroups(icons) {
const container = document.getElementById('iconsContainer');
container.innerHTML = '';

if (icons.length === 0) return;

// Сортируем иконки по алфавиту
const sortedIcons = [...icons].sort((a, b) => a.name.localeCompare(b.name));

// Создаем колонки для отсортированных иконок
const itemsPerColumn = Math.ceil(sortedIcons.length / Math.ceil(sortedIcons.length / 25));

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

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

if (filteredIcons.length === 0) return;

// Группируем иконки
const { groups, noGroup } = groupIcons(filteredIcons);
const hasGroups = Object.keys(groups).length > 0;

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

// Затем иконки без группы
if (noGroup.length > 0) {
if (hasGroups) {
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);
} else {
// Если групп нет, просто отображаем иконки
renderIconsWithoutGroups(noGroup);
}
}
}

// Функция для фильтрации иконок по поисковому запросу и группе
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 (filteredIcons.length === 0) {
const container = document.getElementById('iconsContainer');
container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>';
updateStats();
return;
}

if (showGroups) {
renderIconsWithGroups();
} else {
renderIconsWithoutGroups(filteredIcons);
}

updateStats();
}

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

// Инициализация комбобокса групп и переключателя
function initGroupControls(groups) {
const groupSelect = document.getElementById('groupSelect');
const groupToggle = document.getElementById('groupToggle');

groupSelect.innerHTML = '<option value="">Все группы</option>';

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

const hasGroups = groups.length > 0;
groupSelect.style.display = hasGroups ? 'block' : 'none';
groupToggle.style.display = hasGroups ? 'flex' : 'none';

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

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

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

initGroupControls(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);
}
});
}

// Загружаем иконки при загрузке страницы
window.addEventListener('DOMContentLoaded', function() {
loadIcons();
setupSearch();
});
</script>
</body>
</html>

Добавлено (2025-11-25, 12:36)
---------------------------------------------
<!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;
}

.group-toggle {
display: flex;
align-items: center;
gap: 5px;
font-size: 12px;
}

.group-toggle input {
margin: 0;
}

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

.group-section {
flex: 1;
min-width: 350px;
max-width: calc(33.333% - 10px);
}

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

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

.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;
}

.group-columns-container {
display: flex;
flex-wrap: wrap;
gap: 15px;
}

.group-column {
flex: 1;
min-width: 350px;
}
</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="group-toggle" id="groupToggle" style="display: none;">
<input type="checkbox" id="showGroups" checked>
<label for="showGroups">Отображать группы</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 processBMPWithChromaKey(img) {
return new Promise((resolve) => {
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;

// Рисуем изображение на canvas
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) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];

// Точное определение чистого розового (255, 0, 255)
const isPurePink = r === 255 && g === 0 && b === 255;

// Близкие к чистому розовому (допуск ±5)
const isNearPurePink =
r >= 250 && r <= 255 &&
g >= 0 && g <= 5 &&
b >= 250 && b <= 255;

// Другие распространенные розовые оттенки для хромакея
const isCommonChromaKey =
(r > 240 && g < 20 && b > 240) || // Близкие к чистому
(r === 255 && g === 0 && b === 255) || // Точный
(r === 254 && g === 0 && b === 254) || // Почти точный
(r === 255 && g === 1 && b === 255); // Почти точный

if (isPurePink || isNearPurePink || isCommonChromaKey) {
data[i + 3] = 0; // Полная прозрачность
}
}

// Записываем обработанные данные обратно
ctx.putImageData(imageData, 0, 0);

const processedImg = new Image();
processedImg.onload = () => {
console.log('BMP успешно обработан, розовый фон удален');
resolve({
element: processedImg,
processed: true
});
};
processedImg.src = canvas.toDataURL('image/png');
} catch (error) {
console.warn('Ошибка обработки BMP:', error);
resolve({
element: img,
processed: false
});
}
});
}

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

img.onload = async function() {
// Обрабатываем только BMP файлы
if (src.toLowerCase().endsWith('.bmp')) {
try {
console.log('Обработка BMP:', src);
const result = await processBMPWithChromaKey(img);
resolve(result);
} catch (error) {
console.warn('Ошибка при обработке BMP:', error);
resolve({ element: img, processed: false });
}
} else {
// Для других форматов просто возвращаем изображение
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=';
resolve({ element: placeholder, processed: false });
};

img.src = src;
img.alt = alt;
});
}

// Функция для разбиения массива на части
function splitArray(array, maxItemsPerPart) {
const parts = [];
for (let i = 0; i < array.length; i += maxItemsPerPart) {
parts.push(array.slice(i, i + maxItemsPerPart));
}
return parts;
}

// Функция для группировки иконок
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);

// Разбиваем иконки на колонки (максимум 15 иконок в колонке)
const iconParts = splitArray(icons, 15);

const columnsContainer = document.createElement('div');
columnsContainer.className = 'group-columns-container';

iconParts.forEach(part => {
const column = createIconColumn(part);
columnsContainer.appendChild(column);
});

section.appendChild(columnsContainer);
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 renderIconsWithoutGroups(icons) {
const container = document.getElementById('iconsContainer');
container.innerHTML = '';

if (icons.length === 0) return;

// Сортируем иконки по алфавиту
const sortedIcons = [...icons].sort((a, b) => a.name.localeCompare(b.name));

// Разбиваем на колонки по 15 иконок
const iconParts = splitArray(sortedIcons, 15);

iconParts.forEach(part => {
const column = createIconColumn(part);
container.appendChild(column);
});
}

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

if (filteredIcons.length === 0) return;

// Группируем иконки
const { groups, noGroup } = groupIcons(filteredIcons);
const hasGroups = Object.keys(groups).length > 0;

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

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

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

// Разбиваем прочие иконки на колонки
const noGroupParts = splitArray(noGroup, 15);
const columnsContainer = document.createElement('div');
columnsContainer.className = 'group-columns-container';

noGroupParts.forEach(part => {
const column = createIconColumn(part);
columnsContainer.appendChild(column);
});

noGroupSection.appendChild(columnsContainer);
container.appendChild(noGroupSection);
} else {
// Если групп нет, просто отображаем иконки
renderIconsWithoutGroups(noGroup);
}
}
}

// Функция для фильтрации иконок по поисковому запросу и группе
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 (filteredIcons.length === 0) {
const container = document.getElementById('iconsContainer');
container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>';
updateStats();
return;
}

if (showGroups) {
renderIconsWithGroups();
} else {
renderIconsWithoutGroups(filteredIcons);
}

updateStats();
}

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

// Инициализация комбобокса групп и переключателя
function initGroupControls(groups) {
const groupSelect = document.getElementById('groupSelect');
const groupToggle = document.getElementById('groupToggle');

groupSelect.innerHTML = '<option value="">Все группы</option>';

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

const hasGroups = groups.length > 0;
groupSelect.style.display = hasGroups ? 'block' : 'none';
groupToggle.style.display = hasGroups ? 'flex' : 'none';

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

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

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

initGroupControls(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);
}
});
}

// Загружаем иконки при загрузке страницы
window.addEventListener('DOMContentLoaded', function() {
loadIcons();
setupSearch();
});
</script>
</body>
</html>

Добавлено (2025-11-25, 13:41)
---------------------------------------------
<!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;
}

.group-toggle {
display: flex;
align-items: center;
gap: 5px;
font-size: 12px;
}

.group-toggle input {
margin: 0;
}

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

.group-section {
flex: 0 0 auto;
}

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

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

.icon-column {
border: 1px solid #e0e0e0;
border-radius: 5px;
padding: 8px;
background: #fafafa;
width: 350px;
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;
}

.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="group-toggle" id="groupToggle" style="display: none;">
<input type="checkbox" id="showGroups" checked>
<label for="showGroups">Отображать группы</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 processBMPWithChromaKey(img) {
return new Promise((resolve) => {
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;

// Рисуем изображение на canvas
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) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];

// Точное определение ярко-розового хромакея (255, 0, 255)
const isChromaKey = r === 255 && g === 0 && b === 255;

// Также обрабатываем близкие оттенки (допуск ±5)
const isNearChromaKey =
r >= 250 && r <= 255 &&
g >= 0 && g <= 5 &&
b >= 250 && b <= 255;

if (isChromaKey || isNearChromaKey) {
data[i + 3] = 0; // Устанавливаем полную прозрачность
}
}

// Записываем обработанные данные обратно
ctx.putImageData(imageData, 0, 0);

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

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

img.onload = async function() {
// Обрабатываем только BMP файлы
if (src.toLowerCase().endsWith('.bmp')) {
try {
console.log('Обработка BMP:', src);
const result = await processBMPWithChromaKey(img);
resolve(result);
} catch (error) {
console.warn('Ошибка при обработке BMP:', error);
resolve({ element: img, processed: false });
}
} else {
// Для других форматов просто возвращаем изображение
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=';
resolve({ element: placeholder, processed: false });
};

img.src = src;
img.alt = alt;
});
}

// Функция для группировки иконок
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 splitIconsIntoColumns(icons, maxIconsPerColumn = 25) {
const columns = [];
const totalIcons = icons.length;

if (totalIcons <= maxIconsPerColumn) {
columns.push(icons);
} else {
const numberOfColumns = Math.ceil(totalIcons / maxIconsPerColumn);
const iconsPerColumn = Math.ceil(totalIcons / numberOfColumns);

for (let i = 0; i < totalIcons; i += iconsPerColumn) {
columns.push(icons.slice(i, i + iconsPerColumn));
}
}

return columns;
}

// Функция для создания секции группы
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 columns = splitIconsIntoColumns(icons);
const columnsContainer = document.createElement('div');
columnsContainer.className = 'group-columns-container';

columns.forEach(columnIcons => {
const column = createIconColumn(columnIcons);
columnsContainer.appendChild(column);
});

section.appendChild(columnsContainer);
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 renderIconsWithoutGroups(icons) {
const container = document.getElementById('iconsContainer');
container.innerHTML = '';

if (icons.length === 0) return;

// Сортируем иконки по алфавиту
const sortedIcons = [...icons].sort((a, b) => a.name.localeCompare(b.name));

// Разбиваем на колонки
const columns = splitIconsIntoColumns(sortedIcons);

columns.forEach(columnIcons => {
const column = createIconColumn(columnIcons);
container.appendChild(column);
});
}

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

if (filteredIcons.length === 0) return;

// Группируем иконки
const { groups, noGroup } = groupIcons(filteredIcons);
const hasGroups = Object.keys(groups).length > 0;

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

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

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

// Разбиваем прочие иконки на несколько колонок
const columns = splitIconsIntoColumns(noGroup);
const columnsContainer = document.createElement('div');
columnsContainer.className = 'group-columns-container';

columns.forEach(columnIcons => {
const column = createIconColumn(columnIcons);
columnsContainer.appendChild(column);
});

noGroupSection.appendChild(columnsContainer);
container.appendChild(noGroupSection);
} else {
// Если групп нет, просто отображаем иконки
renderIconsWithoutGroups(noGroup);
}
}
}

// Функция для фильтрации иконок по поисковому запросу и группе
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 (filteredIcons.length === 0) {
const container = document.getElementById('iconsContainer');
container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>';
updateStats();
return;
}

if (showGroups) {
renderIconsWithGroups();
} else {
renderIconsWithoutGroups(filteredIcons);
}

updateStats();
}

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

// Инициализация комбобокса групп и переключателя
function initGroupControls(groups) {
const groupSelect = document.getElementById('groupSelect');
const groupToggle = document.getElementById('groupToggle');

groupSelect.innerHTML = '<option value="">Все группы</option>';

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

const hasGroups = groups.length > 0;
groupSelect.style.display = hasGroups ? 'block' : 'none';
groupToggle.style.display = hasGroups ? 'flex' : 'none';

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

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

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

initGroupControls(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);
}
});
}

// Загружаем иконки при загрузке страницы
window.addEventListener('DOMContentLoaded', function() {
loadIcons();
setupSearch();
});
</script>
</body>
</html>

Добавлено (2025-11-25, 14:14)
---------------------------------------------
Ррр

Роорр
Прикрепления:
rrrrrr.noext (47.0 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: 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;
}

.group-toggle {
display: flex;
align-items: center;
gap: 5px;
font-size: 12px;
}

.group-toggle input {
margin: 0;
}

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

.group-section {
width: 100%;
}

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

.group-columns-container {
display: flex;
flex-wrap: wrap;
gap: 15px;
}

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

.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="group-toggle" id="groupToggle" style="display: none;">
<input type="checkbox" id="showGroups" checked>
<label for="showGroups">Отображать группы</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 processBMPWithChromaKey(img) {
return new Promise((resolve) => {
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;

// Рисуем изображение на canvas
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) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];

// Точное определение ярко-розового хромакея (255, 0, 255)
const isChromaKey = r === 255 && g === 0 && b === 255;

// Также обрабатываем близкие оттенки (допуск ±5)
const isNearChromaKey =
r >= 250 && r <= 255 &&
g >= 0 && g <= 5 &&
b >= 250 && b <= 255;

if (isChromaKey || isNearChromaKey) {
data[i + 3] = 0; // Устанавливаем полную прозрачность
}
}

// Записываем обработанные данные обратно
ctx.putImageData(imageData, 0, 0);

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

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

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

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

img.src = src;
img.alt = alt;
});
}

// Функция для группировки иконок
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 splitIconsIntoColumns(icons, maxIconsPerColumn = 18) {
const columns = [];
const totalIcons = icons.length;

if (totalIcons <= maxIconsPerColumn) {
columns.push(icons);
} else {
const numberOfColumns = Math.ceil(totalIcons / maxIconsPerColumn);
const iconsPerColumn = Math.ceil(totalIcons / numberOfColumns);

for (let i = 0; i < totalIcons; i += iconsPerColumn) {
columns.push(icons.slice(i, i + iconsPerColumn));
}
}

return columns;
}

// Функция для создания секции группы
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 columns = splitIconsIntoColumns(icons);
const columnsContainer = document.createElement('div');
columnsContainer.className = 'group-columns-container';

columns.forEach(columnIcons => {
const column = createIconColumn(columnIcons);
columnsContainer.appendChild(column);
});

section.appendChild(columnsContainer);
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 renderIconsWithoutGroups(icons) {
const container = document.getElementById('iconsContainer');
container.innerHTML = '';

if (icons.length === 0) return;

// Сортируем иконки по алфавиту
const sortedIcons = [...icons].sort((a, b) => a.name.localeCompare(b.name));

// Разбиваем на колонки
const columns = splitIconsIntoColumns(sortedIcons);

columns.forEach(columnIcons => {
const column = createIconColumn(columnIcons);
container.appendChild(column);
});
}

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

if (filteredIcons.length === 0) return;

// Группируем иконки
const { groups, noGroup } = groupIcons(filteredIcons);
const hasGroups = Object.keys(groups).length > 0;

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

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

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

// Разбиваем прочие иконки на несколько колонок
const columns = splitIconsIntoColumns(noGroup);
const columnsContainer = document.createElement('div');
columnsContainer.className = 'group-columns-container';

columns.forEach(columnIcons => {
const column = createIconColumn(columnIcons);
columnsContainer.appendChild(column);
});

noGroupSection.appendChild(columnsContainer);
container.appendChild(noGroupSection);
} else {
// Если групп нет, просто отображаем иконки
renderIconsWithoutGroups(noGroup);
}
}
}

// Функция для фильтрации иконок по поисковому запросу и группе
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 (filteredIcons.length === 0) {
const container = document.getElementById('iconsContainer');
container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>';
updateStats();
return;
}

if (showGroups) {
renderIconsWithGroups();
} else {
renderIconsWithoutGroups(filteredIcons);
}

updateStats();
}

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

// Инициализация комбобокса групп и переключателя
function initGroupControls(groups) {
const groupSelect = document.getElementById('groupSelect');
const groupToggle = document.getElementById('groupToggle');

groupSelect.innerHTML = '<option value="">Все группы</option>';

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

const hasGroups = groups.length > 0;
groupSelect.style.display = hasGroups ? 'block' : 'none';
groupToggle.style.display = hasGroups ? 'flex' : 'none';

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

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

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

initGroupControls(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);
}
});
}

// Загружаем иконки при загрузке страницы
window.addEventListener('DOMContentLoaded', function() {
loadIcons();
setupSearch();
});
</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;
}

.group-toggle {
display: flex;
align-items: center;
gap: 5px;
font-size: 12px;
}

.group-toggle input {
margin: 0;
}

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

.group-section {
width: 100%;
margin-bottom: 20px;
}

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

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

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

.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="group-toggle" id="groupToggle" style="display: none;">
<input type="checkbox" id="showGroups" checked>
<label for="showGroups">Отображать группы</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 processBMPWithChromaKey(img) {
return new Promise((resolve) => {
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;

// Рисуем изображение на canvas
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) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];

// Точное определение ярко-розового хромакея (255, 0, 255)
const isChromaKey = r === 255 && g === 0 && b === 255;

// Также обрабатываем близкие оттенки (допуск ±5)
const isNearChromaKey =
r >= 250 && r <= 255 &&
g >= 0 && g <= 5 &&
b >= 250 && b <= 255;

if (isChromaKey || isNearChromaKey) {
data[i + 3] = 0; // Устанавливаем полную прозрачность
}
}

// Записываем обработанные данные обратно
ctx.putImageData(imageData, 0, 0);

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

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

img.onload = async function() {
// Обрабатываем только BMP файлы
if (src.toLowerCase().endsWith('.bmp')) {
try {
console.log('Обработка BMP:', src);
const result = await processBMPWithChromaKey(img);
resolve(result);
} catch (error) {
console.warn('Ошибка при обработке BMP:', error);
resolve({ element: img, processed: false });
}
} else {
// Для других форматов просто возвращаем изображение
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=';
resolve({ element: placeholder, processed: false });
};

img.src = src;
img.alt = alt;
});
}

// Функция для группировки иконок
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 splitIconsIntoColumns(icons, maxIconsPerColumn = 18) {
const columns = [];
const totalIcons = icons.length;

if (totalIcons <= maxIconsPerColumn) {
columns.push(icons);
} else {
const numberOfColumns = Math.ceil(totalIcons / maxIconsPerColumn);
const iconsPerColumn = Math.ceil(totalIcons / numberOfColumns);

for (let i = 0; i < totalIcons; i += iconsPerColumn) {
columns.push(icons.slice(i, i + iconsPerColumn));
}
}

return columns;
}

// Функция для создания секции группы
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 columns = splitIconsIntoColumns(icons);
const columnsContainer = document.createElement('div');
columnsContainer.className = 'group-columns-container';

columns.forEach(columnIcons => {
const column = createIconColumn(columnIcons);
columnsContainer.appendChild(column);
});

section.appendChild(columnsContainer);
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 renderIconsWithoutGroups(icons) {
const container = document.getElementById('iconsContainer');
container.innerHTML = '';

if (icons.length === 0) return;

// Сортируем иконки по алфавиту
const sortedIcons = [...icons].sort((a, b) => a.name.localeCompare(b.name));

// Разбиваем на колонки
const columns = splitIconsIntoColumns(sortedIcons);

columns.forEach(columnIcons => {
const column = createIconColumn(columnIcons);
container.appendChild(column);
});
}

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

if (filteredIcons.length === 0) return;

// Группируем иконки
const { groups, noGroup } = groupIcons(filteredIcons);
const hasGroups = Object.keys(groups).length > 0;

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

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

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

// Разбиваем прочие иконки на несколько колонок
const columns = splitIconsIntoColumns(noGroup);
const columnsContainer = document.createElement('div');
columnsContainer.className = 'group-columns-container';

columns.forEach(columnIcons => {
const column = createIconColumn(columnIcons);
columnsContainer.appendChild(column);
});

noGroupSection.appendChild(columnsContainer);
container.appendChild(noGroupSection);
} else {
// Если групп нет, просто отображаем иконки
renderIconsWithoutGroups(noGroup);
}
}
}

// Функция для фильтрации иконок по поисковому запросу и группе
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 (filteredIcons.length === 0) {
const container = document.getElementById('iconsContainer');
container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>';
updateStats();
return;
}

if (showGroups) {
renderIconsWithGroups();
} else {
renderIconsWithoutGroups(filteredIcons);
}

updateStats();
}

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

// Инициализация комбобокса групп и переключателя
function initGroupControls(groups) {
const groupSelect = document.getElementById('groupSelect');
const groupToggle = document.getElementById('groupToggle');

groupSelect.innerHTML = '<option value="">Все группы</option>';

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

const hasGroups = groups.length > 0;
groupSelect.style.display = hasGroups ? 'block' : 'none';
groupToggle.style.display = hasGroups ? 'flex' : 'none';

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

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

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

initGroupControls(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);
}
});
}

// Загружаем иконки при загрузке страницы
window.addEventListener('DOMContentLoaded', function() {
loadIcons();
setupSearch();
});
</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;
}

.group-toggle {
display: flex;
align-items: center;
gap: 5px;
font-size: 12px;
}

.group-toggle input {
margin: 0;
}

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

.group-section {
width: 100%;
margin-bottom: 20px;
}

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

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

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

.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="group-toggle" id="groupToggle" style="display: none;">
<input type="checkbox" id="showGroups" checked>
<label for="showGroups">Отображать группы</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 processBMPWithChromaKey(img) {
return new Promise((resolve) => {
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;

// Рисуем изображение на canvas
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) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];

// Точное определение ярко-розового хромакея (255, 0, 255)
const isChromaKey = r === 255 && g === 0 && b === 255;

// Также обрабатываем близкие оттенки (допуск ±5)
const isNearChromaKey =
r >= 250 && r <= 255 &&
g >= 0 && g <= 5 &&
b >= 250 && b <= 255;

if (isChromaKey || isNearChromaKey) {
data[i + 3] = 0; // Устанавливаем полную прозрачность
}
}

// Записываем обработанные данные обратно
ctx.putImageData(imageData, 0, 0);

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

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

img.onload = async function() {
// Обрабатываем только BMP файлы
if (src.toLowerCase().endsWith('.bmp')) {
try {
console.log('Обработка BMP:', src);
const result = await processBMPWithChromaKey(img);
resolve(result);
} catch (error) {
console.warn('Ошибка при обработке BMP:', error);
resolve({ element: img, processed: false });
}
} else {
// Для других форматов просто возвращаем изображение
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=';
resolve({ element: placeholder, processed: false });
};

img.src = src;
img.alt = alt;
});
}

// Функция для группировки иконок
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 splitIconsIntoColumns(icons, maxIconsPerColumn = 18) {
const columns = [];
const totalIcons = icons.length;

if (totalIcons <= maxIconsPerColumn) {
columns.push(icons);
} else {
const numberOfColumns = Math.ceil(totalIcons / maxIconsPerColumn);
const iconsPerColumn = Math.ceil(totalIcons / numberOfColumns);

for (let i = 0; i < totalIcons; i += iconsPerColumn) {
columns.push(icons.slice(i, i + iconsPerColumn));
}
}

return columns;
}

// Функция для создания секции группы
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 columns = splitIconsIntoColumns(icons);

columns.forEach(columnIcons => {
const column = createIconColumn(columnIcons);
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 renderIconsWithoutGroups(icons) {
const container = document.getElementById('iconsContainer');
container.innerHTML = '';

if (icons.length === 0) return;

// Сортируем иконки по алфавиту
const sortedIcons = [...icons].sort((a, b) => a.name.localeCompare(b.name));

// Разбиваем на колонки
const columns = splitIconsIntoColumns(sortedIcons);

columns.forEach(columnIcons => {
const column = createIconColumn(columnIcons);
container.appendChild(column);
});
}

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

if (filteredIcons.length === 0) return;

// Группируем иконки
const { groups, noGroup } = groupIcons(filteredIcons);
const hasGroups = Object.keys(groups).length > 0;

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

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

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

// Разбиваем прочие иконки на несколько колонок
const columns = splitIconsIntoColumns(noGroup);

columns.forEach(columnIcons => {
const column = createIconColumn(columnIcons);
noGroupSection.appendChild(column);
});

container.appendChild(noGroupSection);
} else {
// Если групп нет, просто отображаем иконки
renderIconsWithoutGroups(noGroup);
}
}
}

// Функция для фильтрации иконок по поисковому запросу и группе
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 (filteredIcons.length === 0) {
const container = document.getElementById('iconsContainer');
container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>';
updateStats();
return;
}

if (showGroups) {
renderIconsWithGroups();
} else {
renderIconsWithoutGroups(filteredIcons);
}

updateStats();
}

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

// Инициализация комбобокса групп и переключателя
function initGroupControls(groups) {
const groupSelect = document.getElementById('groupSelect');
const groupToggle = document.getElementById('groupToggle');

groupSelect.innerHTML = '<option value="">Все группы</option>';

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

const hasGroups = groups.length > 0;
groupSelect.style.display = hasGroups ? 'block' : 'none';
groupToggle.style.display = hasGroups ? 'flex' : 'none';

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

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

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

initGroupControls(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);
}
});
}

// Загружаем иконки при загрузке страницы
window.addEventListener('DOMContentLoaded', function() {
loadIcons();
setupSearch();
});
</script>
</body>
</html>
Поиск:
Новый ответ
Имя:
Текст сообщения: