<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
    <title>Пэйнт.рф</title>
    <link href="https://пэйнт.рф/feed.xml" rel="self" />
    <link href="https://пэйнт.рф" />
    <updated>2026-04-02T21:10:25+03:00</updated>
    <author>
        <name>пэйнт.рф</name>
    </author>
    <id>https://пэйнт.рф</id>

    <entry>
        <title>Сервис по поиску относительных цветов</title>
        <author>
            <name>пэйнт.рф</name>
        </author>
        <link href="https://пэйнт.рф/servis-7.html"/>
        <id>https://пэйнт.рф/servis-7.html</id>

        <updated>2026-03-15T13:38:07+03:00</updated>
            <summary type="html">
                <![CDATA[
                    🎨 Поиск относительных цветов Найдите идеальный оттенок на человеческом языке с помощью модели CIE LCh Настройте параметры и нажмите "Найти", чтобы получить список релевантных цветов&hellip;
                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                <header class="header">
<h1>🎨 Поиск относительных цветов</h1>
<p>Найдите идеальный оттенок на человеческом языке с помощью модели CIE LCh</p>
</header><main class="container">
<div class="main-content-wrapper"><!-- Левая колонка: Ввод и Управление -->
<aside class="controls-section">
<div class="card">
<h2 class="card-title">🖌️ 1. Исходный цвет</h2>
<div class="color-input-group">
<div class="color-picker-wrapper"><input type="color" id="colorPicker" value="#3498db"></div>
<div class="hex-input-wrapper"><input type="text" id="hexInput" placeholder="#3498db" value="#3498db"></div>
</div>
<div id="initialColorPreview" class="initial-color-preview"></div>
<div class="color-values">
<div class="value-box"><label>HEX</label> <span id="hexValue">#3498db</span></div>
<div class="value-box"><label>LCh</label> <span id="lchValue">-</span></div>
</div>
</div>
<div class="card">
<h2 class="card-title">🎛️ 2. Настройки</h2>
<div class="slider-container">
<div class="slider-label">Светлее / Темнее<span id="lightnessValue">0</span></div>
<input type="range" id="lightnessSlider" min="-50" max="50" value="0"></div>
<div class="slider-container">
<div class="slider-label">Насыщеннее / Глухее<span id="chromaValue">0</span></div>
<input type="range" id="chromaSlider" min="-50" max="50" value="0"></div>
<div class="slider-container">
<div class="slider-label">Теплее / Холоднее<span id="hueValue">0°</span></div>
<input type="range" id="hueSlider" min="-180" max="180" value="0"></div>
<div class="action-buttons"><button id="resetBtn" class="btn btn-secondary">Сброс</button> <button id="findBtn" class="btn btn-primary">Найти</button></div>
</div>
</aside>
<!-- Правая колонка: Результаты -->
<section class="results-section">
<div class="card">
<h2 class="card-title">🎯 Результаты поиска</h2>
<div id="resultsContainer" class="results-grid">
<p style="color: var(--light-text-color); grid-column: 1/-1; text-align: center; padding: 2rem 0;">Настройте параметры и нажмите "Найти", чтобы получить список релевантных цветов из базы.</p>
</div>
</div>
</section>
</div>
<section class="card info-section">
<h2 class="card-title">📖 Что такое CIE LCh?</h2>
<p><strong>CIE LCh (CIELCh)</strong> — это цветовое пространство, которое описывает цвет так, как его воспринимает человеческий глаз, в отличие от технических форматов RGB или HEX.</p>
<ul>
<li><strong>L* (Lightness) — Светлота:</strong> Яркость цвета (от 0 до 100).</li>
<li><strong>C* (Chroma) — Насыщенность:</strong> "Чистота" или интенсивность цвета (от 0 до 100+).</li>
<li><strong>h* (Hue) — Цветовой тон:</strong> Оттенок цвета по кругу (от 0° до 360°). Это и есть "теплота".</li>
</ul>
<p>Именно поэтому, меняя эти параметры, мы можем говорить о цвете на понятном языке: <em>"сделай его теплее (увеличь h*) и светлее (увеличь L*)"</em>.</p>
</section>
</main>
<div id="copyTooltip" class="copy-tooltip">Скопировано!</div>
<p>
<script>
		document.addEventListener('DOMContentLoaded', () => {
			// --- DOM Элементы ---
			const colorPicker = document.getElementById('colorPicker');
			const hexInput = document.getElementById('hexInput');
			const initialColorPreview = document.getElementById('initialColorPreview');
			const hexValueSpan = document.getElementById('hexValue');
			const lchValueSpan = document.getElementById('lchValue');

			const lightnessSlider = document.getElementById('lightnessSlider');
			const chromaSlider = document.getElementById('chromaSlider');
			const hueSlider = document.getElementById('hueSlider');
			const lightnessValue = document.getElementById('lightnessValue');
			const chromaValue = document.getElementById('chromaValue');
			const hueValue = document.getElementById('hueValue');

			const findBtn = document.getElementById('findBtn');
			const resetBtn = document.getElementById('resetBtn');
			const resultsContainer = document.getElementById('resultsContainer');
			const copyTooltip = document.getElementById('copyTooltip');

			// --- Состояние и данные ---
			let baseColorHex = '#3498db';
			let baseColorLCh = {
				l: 0,
				c: 0,
				h: 0
			};
			let paintDatabase = [];

			async function loadPalettes() {
				try {
					const response = await fetch('/data/palettes-list.json');
					const palettes = await response.json();

					const results = [];
					for (const p of palettes) {
						try {
							const res = await fetch(p.file);
							if (res.ok) {
								const data = await res.json();
								results.push(...data);
							}
						} catch (e) {
							console.warn('Пропущен файл:', p.file);
						}
					}

					paintDatabase = results.map(c => ({
						name: c.name,
						brand: c.series || c.ColorFamily || c.collection?.split('/')[0] || '',
						hex: c.hex
					})).filter(c => c.hex);

					resultsContainer.innerHTML = '<p style="color: var(--light-text-color); grid-column: 1/-1; text-align: center; padding: 2rem 0;">Загружено ' + paintDatabase.length + ' цветов. Настройте параметры и нажмите "Найти".</p>';
				} catch (e) {
					console.error('Ошибка загрузки палитр:', e);
					resultsContainer.innerHTML = '<p style="color: red; grid-column: 1/-1;">Ошибка загрузки базы цветов</p>';
				}
			}

			// --- Функции преобразования цветов (Hex <-> LCh) ---
			function hex2lch(hex) {
				let r = parseInt(hex.slice(1, 3), 16) / 255,
					g = parseInt(hex.slice(3, 5), 16) / 255,
					b = parseInt(hex.slice(5, 7), 16) / 255;
				r = (r > 0.04045) ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
				g = (g > 0.04045) ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
				b = (b > 0.04045) ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
				let x = r * 0.4124 + g * 0.3576 + b * 0.1805,
					y = r * 0.2126 + g * 0.7152 + b * 0.0722,
					z = r * 0.0193 + g * 0.1192 + b * 0.9505;
				x /= 0.95047;
				y /= 1.00000;
				z /= 1.08883;
				let l = 116 * y - 16;
				let a = 500 * (x - y);
				let b_lab = 200 * (y - z);
				let c = Math.sqrt(a * a + b_lab * b_lab);
				let h = Math.atan2(b_lab, a) * (180 / Math.PI);
				if (h < 0) h += 360;
				return [l, c, h];
			}

			function lch2hex(l, c, h) {
				const h_rad = h * (Math.PI / 180);
				const a = c * Math.cos(h_rad);
				const b = c * Math.sin(h_rad);
				let y = (l + 16) / 116;
				let x = a / 500 + y;
				let z = y - b / 200;
				const x2 = x * x * x,
					y2 = y * y * y,
					z2 = z * z * z;
				x = (x2 > 0.008856) ? x2 : (x - 16 / 116) / 7.787;
				y = (y2 > 0.008856) ? y2 : (y - 16 / 116) / 7.787;
				z = (z2 > 0.008856) ? z2 : (z - 16 / 116) / 7.787;
				x *= 0.95047;
				y *= 1.00000;
				z *= 1.08883;
				let r = x * 3.2406 - y * 1.5372 - z * 0.4986;
				let g = -x * 0.9689 + y * 1.8758 + z * 0.0415;
				let b_s = x * 0.0557 - y * 0.2040 + z * 1.0570;
				r = (r > 0.0031308) ? 1.055 * Math.pow(r, 1 / 2.4) - 0.055 : 12.92 * r;
				g = (g > 0.0031308) ? 1.055 * Math.pow(g, 1 / 2.4) - 0.055 : 12.92 * g;
				b_s = (b_s > 0.0031308) ? 1.055 * Math.pow(b_s, 1 / 2.4) - 0.055 : 12.92 * b_s;
				const toHex = c => Math.round(Math.max(0, Math.min(1, c)) * 255).toString(16).padStart(2, '0');
				return `#${toHex(r)}${toHex(g)}${toHex(b_s)}`;
			}

			// --- Основная логика ---
			function updateColorDisplay(hex) {
				baseColorHex = hex;
				baseColorLCh = hex2lch(hex);
				colorPicker.value = hex;
				hexInput.value = hex;
				hexValueSpan.textContent = hex;
				lchValueSpan.textContent = `L:${baseColorLCh[0].toFixed(1)} C:${baseColorLCh[1].toFixed(1)} h:${baseColorLCh[2].toFixed(1)}°`;
				initialColorPreview.style.backgroundColor = hex;
				updatePreviewFromSliders();
			}

			function updatePreviewFromSliders() {
				const lOffset = parseFloat(lightnessSlider.value),
					cOffset = parseFloat(chromaSlider.value),
					hOffset = parseFloat(hueSlider.value);
				lightnessValue.textContent = `+${lOffset}`;
				chromaValue.textContent = `+${cOffset}`;
				hueValue.textContent = `${hOffset > 0 ? '+' : ''}${hOffset}°`;

				if (lOffset === 0 && cOffset === 0 && hOffset === 0) {
					initialColorPreview.style.backgroundColor = baseColorHex;
				} else {
					const newL = Math.max(0, Math.min(100, baseColorLCh[0] + lOffset));
					const newC = Math.max(0, baseColorLCh[1] + cOffset);
					let newH = baseColorLCh[2] + hOffset;
					if (newH < 0) newH += 360;
					if (newH > 360) newH -= 360;
					initialColorPreview.style.backgroundColor = lch2hex(newL, newC, newH);
				}
			}

			function findRelativeColors() {
				const targetL = Math.max(0, Math.min(100, baseColorLCh[0] + parseFloat(lightnessSlider.value)));
				const targetC = Math.max(0, baseColorLCh[1] + parseFloat(chromaSlider.value));
				let targetH = baseColorLCh[2] + parseFloat(hueSlider.value);
				if (targetH < 0) targetH += 360;
				if (targetH > 360) targetH -= 360;

				const colorsWithDistance = paintDatabase.map(paint => {
					const paintLch = hex2lch(paint.hex);
					const distance = Math.sqrt(Math.pow(targetL - paintLch[0], 2) + Math.pow(targetC - paintLch[1], 2) + Math.pow(targetH - paintLch[2], 2));
					return {
						...paint,
						distance
					};
				}).sort((a, b) => a.distance - b.distance);

				renderResults(colorsWithDistance.slice(0, 12));
			}

			function renderResults(results) {
				resultsContainer.innerHTML = '';
				if (results.length === 0) {
					resultsContainer.innerHTML = '<p>Ничего не найдено.</p>';
					return;
				}
				results.forEach(paint => {
					const card = document.createElement('div');
					card.className = 'color-card';
					const paintLch = hex2lch(paint.hex);
					card.innerHTML = `
                <div class="color-card-swatch" style="background-color: ${paint.hex};"></div>
                <div class="color-card-body">
                    <h3 class="color-card-title">${paint.name}</h3>
                    <p class="color-card-brand">${paint.brand}</p>
                    <div class="color-card-codes" data-hex="${paint.hex}">${paint.hex}</div>
                </div>
            `;
					resultsContainer.appendChild(card);
				});
				document.querySelectorAll('.color-card-codes').forEach(codeBox => {
					codeBox.addEventListener('click', function() {
						navigator.clipboard.writeText(this.dataset.hex).then(() => {
							copyTooltip.classList.add('show');
							setTimeout(() => copyTooltip.classList.remove('show'), 1500);
						});
					});
				});
			}

			function resetControls() {
				lightnessSlider.value = 0;
				chromaSlider.value = 0;
				hueSlider.value = 0;
				updatePreviewFromSliders();
			}

			// --- Слушатели событий ---
			colorPicker.addEventListener('input', (e) => updateColorDisplay(e.target.value));
			hexInput.addEventListener('input', (e) => {
				const hex = e.target.value;
				if (/^#[0-9A-F]{6}$/i.test(hex)) updateColorDisplay(hex);
			});
			[lightnessSlider, chromaSlider, hueSlider].forEach(slider => slider.addEventListener('input', updatePreviewFromSliders));
			findBtn.addEventListener('click', findRelativeColors);
			resetBtn.addEventListener('click', resetControls);

			// --- Инициализация ---
			updateColorDisplay(baseColorHex);
			loadPalettes();
		});
	</script>
</p>
            ]]>
        </content>
    </entry>
    <entry>
        <title>Сервис 5</title>
        <author>
            <name>пэйнт.рф</name>
        </author>
        <link href="https://пэйнт.рф/servis-5.html"/>
        <id>https://пэйнт.рф/servis-5.html</id>

        <updated>2026-03-14T22:02:55+03:00</updated>
            <summary type="html">
                <![CDATA[
                    ⭐️ LCh Сервис цветов Выберите цвет L (светлота)50 C (насыщенность)20 h (тон)210 L 50 a 0 b 0 Корректировка #7A92A3 Светлота50% Насыщенность20 Тон210° ☀️ +Светлее🌑&hellip;
                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                <div class="c">
<h1>⭐️ LCh Сервис цветов</h1>
<div class="g">
<div class="p">
<h3>Выберите цвет</h3>
<div class="i"><input type="color" id="p" value="#7a92a3"><input type="text" id="h" value="#7A92A3"></div>
<div class="v">
<div class="r">L (светлота)<span id="lv">50</span></div>
<div class="b">
<div id="lb" class="f" style="width: 50%; background: linear-gradient(90deg,#000,#fff);"></div>
</div>
<div class="r">C (насыщенность)<span id="cv">20</span></div>
<div class="b">
<div id="cb" class="f" style="width: 20%; background: linear-gradient(90deg,#94a3b8,#1e293b);"></div>
</div>
<div class="r">h (тон)<span id="hv">210</span></div>
<div class="b">
<div id="hb" class="f" style="width: 58%; background: linear-gradient(90deg,#ef4444,#22c55e,#3b82f6);"></div>
</div>
</div>
<div class="lab">
<div class="lb">
<div class="lb_l">L</div>
<div id="labL" class="lb_v">50</div>
</div>
<div class="lb">
<div class="lb_l">a</div>
<div id="labA" class="lb_v">0</div>
</div>
<div class="lb">
<div class="lb_l">b</div>
<div id="labB" class="lb_v">0</div>
</div>
</div>
</div>
<div class="p">
<h3>Корректировка</h3>
<div id="pv" style="background: #7a92a3;"><span id="ph">#7A92A3</span></div>
<div class="sl">
<div class="sh">Светлота<span id="slv">50%</span></div>
<input type="range" id="sL" min="0" max="100" value="50"></div>
<div class="sl">
<div class="sh">Насыщенность<span id="scv">20</span></div>
<input type="range" id="sC" min="0" max="100" value="20"></div>
<div class="sl">
<div class="sh">Тон<span id="shv">210°</span></div>
<input type="range" id="sH" min="0" max="360" value="210"></div>
<div class="bt">☀️ +Светлее🌑 -Темнее🌈 +Насыщеннее🌫 -Бледнее🔥 +Теплее❄️ -Холоднее</div>
</div>
<div class="p">
<div class="rs">Ближайшие цвета</div>
<select id="paletteSelect" style="width: 100%; padding: .4rem; margin-bottom: .5rem; border: 1px solid var(--border); border-radius: 6px;">
<option>Загрузка палитр...</option>
</select>
<div id="ml" class="ml"></div>
</div>
</div>
</div>
<p>
<script>
		function hexToRgb(h) {
			var r = parseInt(h.slice(1, 3), 16),
				g = parseInt(h.slice(3, 5), 16),
				b = parseInt(h.slice(5, 7), 16);
			return {
				r: r,
				g: g,
				b: b
			}
		}

		function rgbToHex(r, g, b) {
			var t = function(n) {
				n = Math.max(0, Math.min(255, Math.round(n))).toString(16);
				return n.length === 1 ? '0' + n : n
			};
			return '#' + t(r) + t(g) + t(b)
		}

		function rgbToXyz(r, g, b) {
			r = r / 255;
			g = g / 255;
			b = b / 255;
			r = r > .04045 ? Math.pow((r + .055) / 1.055, 2.4) : r / 12.92;
			g = g > .04045 ? Math.pow((g + .055) / 1.055, 2.4) : g / 12.92;
			b = b > .04045 ? Math.pow((b + .055) / 1.055, 2.4) : b / 12.92;
			return {
				x: (r * .4124 + g * .3576 + b * .1805) * 100,
				y: (r * .2126 + g * .7152 + b * .0722) * 100,
				z: (r * .0193 + g * .1192 + b * .9505) * 100
			}
		}

		function xyzToLab(x, y, z) {
			var refX = 95.047,
				refY = 100,
				refZ = 108.883;
			x /= refX;
			y /= refY;
			z /= refZ;
			x = x > .008856 ? Math.pow(x, 1 / 3) : 7.787 * x + 16 / 116;
			y = y > .008856 ? Math.pow(y, 1 / 3) : 7.787 * y + 16 / 116;
			z = z > .008856 ? Math.pow(z, 1 / 3) : 7.787 * z + 16 / 116;
			return {
				L: 116 * y - 16,
				a: 500 * (x - y),
				b: 200 * (y - z)
			}
		}

		function labToXyz(L, a, b) {
			var refX = 95.047,
				refY = 100,
				refZ = 108.883,
				y = (L + 16) / 116,
				x = a / 500 + y,
				z = y - b / 200,
				x3 = Math.pow(x, 3),
				y3 = Math.pow(y, 3),
				z3 = Math.pow(z, 3);
			return {
				x: (x3 > .008856 ? x3 : (x - 16 / 116) / 7.787) * refX,
				y: (y3 > .008856 ? y3 : (y - 16 / 116) / 7.787) * refY,
				z: (z3 > .008856 ? z3 : (z - 16 / 116) / 7.787) * refZ
			}
		}

		function xyzToRgb(x, y, z) {
			x /= 100;
			y /= 100;
			z /= 100;
			var r = x * 3.2406 + y * -1.5372 + z * -.4986,
				g = x * -.9689 + y * 1.8758 + z * .0415,
				b = x * .0557 + y * -.204 + z * 1.057;
			r = r > .0031308 ? 1.055 * Math.pow(r, 1 / 2.4) - .055 : 12.92 * r;
			g = g > .0031308 ? 1.055 * Math.pow(g, 1 / 2.4) - .055 : 12.92 * g;
			b = b > .0031308 ? 1.055 * Math.pow(b, 1 / 2.4) - .055 : 12.92 * b;
			return {
				r: r * 255,
				g: g * 255,
				b: b * 255
			}
		}

		function labToLch(L, a, b) {
			var C = Math.sqrt(a * a + b * b),
				h = Math.atan2(b, a) * 180 / Math.PI;
			return {
				L: L,
				C: C,
				h: h < 0 ? h + 360 : h
			}
		}

		function lchToLab(L, C, h) {
			var hRad = h * Math.PI / 180;
			return {
				L: L,
				a: C * Math.cos(hRad),
				b: C * Math.sin(hRad)
			}
		}

		function hexToLch(hex) {
			var rgb = hexToRgb(hex),
				xyz = rgbToXyz(rgb.r, rgb.g, rgb.b),
				lab = xyzToLab(xyz.x, xyz.y, xyz.z);
			return labToLch(lab.L, lab.a, lab.b)
		}

		function lchToHex(L, C, h) {
			var lab = lchToLab(L, C, h),
				xyz = labToXyz(lab.L, lab.a, lab.b),
				rgb = xyzToRgb(xyz.x, xyz.y, xyz.z);
			return rgbToHex(rgb.r, rgb.g, rgb.b)
		}
		var L = 50,
			C = 20,
			H = 210,
			colors = [];
		var p = document.getElementById('p'),
			h = document.getElementById('h'),
			pv = document.getElementById('pv'),
			ph = document.getElementById('ph'),
			sL = document.getElementById('sL'),
			sC = document.getElementById('sC'),
			sH = document.getElementById('sH'),
			ml = document.getElementById('ml'),
			ps = document.getElementById('paletteSelect');

		function init(hex) {
			var lch = hexToLch(hex);
			L = Math.max(0, Math.min(100, lch.L));
			C = Math.max(0, lch.C);
			H = lch.h;
			draw()
		}

		function draw() {
			var hex = lchToHex(L, C, H),
				rgb = hexToRgb(hex),
				xyz = rgbToXyz(rgb.r, rgb.g, rgb.b),
				lab = xyzToLab(xyz.x, xyz.y, xyz.z);
			pv.style.background = hex;
			ph.textContent = hex.toUpperCase();
			h.value = hex.toUpperCase();
			p.value = hex;
			sL.value = L;
			sC.value = C;
			sH.value = H;
			document.getElementById('lv').textContent = Math.round(L);
			document.getElementById('cv').textContent = Math.round(C);
			document.getElementById('hv').textContent = Math.round(H);
			document.getElementById('slv').textContent = Math.round(L) + '%';
			document.getElementById('scv').textContent = Math.round(C);
			document.getElementById('shv').textContent = Math.round(H) + '°';
			document.getElementById('lb').style.width = L + '%';
			document.getElementById('cb').style.width = Math.min(100, C) + '%';
			document.getElementById('hb').style.width = (H / 360 * 100) + '%';
			document.getElementById('labL').textContent = Math.round(lab.L);
			document.getElementById('labA').textContent = Math.round(lab.a);
			document.getElementById('labB').textContent = Math.round(lab.b);
			find(lab)
		}

		function find(tl) {
			var m = colors.map(function(c) {
				var dr = hexToRgb(c.hex),
					dx = rgbToXyz(dr.r, dr.g, dr.b),
					dl = xyzToLab(dx.x, dx.y, dx.z),
					de = Math.sqrt(Math.pow(tl.L - dl.L, 2) + Math.pow(tl.a - dl.a, 2) + Math.pow(tl.b - dl.b, 2));
				return {
					n: c.name,
					h: c.hex,
					b: c.brand,
					dE: de
				}
			}).sort(function(a, b) {
				return a.dE - b.dE
			});
			render(m.slice(0, 12))
		}

		function render(list) {
			ml.innerHTML = list.map(function(x, i) {
				var c = x.dE < 2 ? 'gd' : x.dE < 5 ? 'yd' : 'rd';
				return '<div class="m" onclick="init(\'' + x.h + '\')"><div class="s" style="background:' + x.h + '"></div><div class="if"><div class="n">' + x.n + '</div><div class="bn">' + x.b + '</div></div><div class="d"><div class="dl">ΔE</div><div class="dv ' + c + '">' + x.dE.toFixed(1) + '</div></div>' + (i === 0 ? '<span class="bb">Ближ.</span>' : '') + '</div>'
			}).join('')
		}

		function aL(d) {
			L = Math.max(0, Math.min(100, L + d));
			draw()
		}

		function aC(d) {
			C = Math.max(0, C + d);
			draw()
		}

		function aH(d) {
			H = (H + d + 360) % 360;
			draw()
		}
		p.addEventListener('input', function() {
			init(this.value)
		});
		h.addEventListener('input', function() {
			if (/^#[0-9A-Fa-f]{6}$/.test(this.value)) init(this.value)
		});
		sL.addEventListener('input', function() {
			L = +this.value;
			draw()
		});
		sC.addEventListener('input', function() {
			C = +this.value;
			draw()
		});
		sH.addEventListener('input', function() {
			H = +this.value;
			draw()
		});
		ps.addEventListener('change', function() {
			loadPalette(this.value)
		});
		async function loadPalettes() {
			try {
				var r = await fetch('/data/palettes-list.json');
				var list = await r.json();
				ps.innerHTML = list.map(function(p) {
					return '<option value="' + p.file.replace('data/', '') + '">' + p.name + '</option>'
				}).join('');
				loadPalette('benjamin-moore-color-preview.json')
			} catch (e) {
				ps.innerHTML = '<option>Ошибка</option>'
			}
		}
		async function loadPalette(file) {
			try {
				var r = await fetch('/data/' + file);
				var data = await r.json();
				colors = data.map(function(c) {
					return {
						name: c.name || c.Name || '--',
						hex: c.hex || c.Hex || '#000000',
						brand: c.collection || c.Series || ''
					}
				});
				draw()
			} catch (e) {
				colors = [];
				ml.innerHTML = '<div class="lo">Ошибка загрузки</div>'
			}
		}
		loadPalettes();
		init('#7a92a3');
	</script>
</p>
            ]]>
        </content>
    </entry>
    <entry>
        <title>Лаборатория цвета</title>
        <author>
            <name>пэйнт.рф</name>
        </author>
        <link href="https://пэйнт.рф/laboratoriya-cveta.html"/>
        <id>https://пэйнт.рф/laboratoriya-cveta.html</id>

        <updated>2026-03-14T12:45:06+03:00</updated>
            <summary type="html">
                <![CDATA[
                    Здесь представлены вариации использования сервиса подбора цвета по цветовому кругу Поиск относительных цветов по интерьерным палитрам на CIE LCh Сервис 2 Сервис 3 Сервис 4&hellip;
                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                <p>Здесь представлены вариации использования сервиса подбора цвета по цветовому кругу</p>
<p><a href="https://пэйнт.рф/servis.html">Поиск относительных цветов по интерьерным палитрам на CIE LCh</a></p>
<p><a href="https://пэйнт.рф/servis2.html">Сервис 2</a></p>
<p><a href="https://пэйнт.рф/servis3.html">Сервис 3</a></p>
<p><a href="https://пэйнт.рф/servis4.html">Сервис 4</a></p>
<p><a href="https://пэйнт.рф/servis-5.html">Сервис 5</a></p>
<p><a href="https://пэйнт.рф/servis-7.html">Сервис 7</a></p>
            ]]>
        </content>
    </entry>
    <entry>
        <title>Сервис4</title>
        <author>
            <name>пэйнт.рф</name>
        </author>
        <link href="https://пэйнт.рф/servis4.html"/>
        <id>https://пэйнт.рф/servis4.html</id>

        <updated>2026-03-14T12:29:06+03:00</updated>
            <summary type="html">
                <![CDATA[
                    ChromaLab CIE LCh* · Интерьерные палитры · v2.1 Цветовое пространство LCh* Текущий цвет #C8916A L* 65 · C* 42 · h* 48° Lab: 65 /&hellip;
                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                <header></header>
<div class="app">
<div class="logo-service">ChromaLab</div>
<div class="logo-sub">CIE LCh* · Интерьерные палитры · v2.1</div>
<!-- ── LEFT PANEL ── -->
<div class="panel-left"><!-- Color Wheel -->
<div>
<div class="section-title">Цветовое пространство LCh*</div>
<div class="wheel-container"><canvas id="colorWheel"></canvas>
<div id="wheelCursor" class="wheel-cursor"></div>
</div>
</div>
<!-- Preview -->
<div>
<div class="section-title">Текущий цвет</div>
<div class="color-preview-block">
<div id="mainSwatch" class="color-swatch-main"></div>
<div class="color-values">
<div id="hexDisplay" class="color-hex">#C8916A</div>
<div id="lchDisplay" class="color-lch-display">L* 65 · C* 42 · h* 48°</div>
<div id="labDisplay" class="color-lch-display">Lab: 65 / 28 / 31</div>
</div>
</div>
</div>
<!-- LCh Sliders -->
<div>
<div class="section-title">Параметры LCh*</div>
<div class="slider-group">
<div class="slider-row">
<div class="slider-label"><span class="slider-name"><span class="lch-letter">L*</span> Светлота</span> <span id="valL" class="slider-value L">65</span></div>
<input type="range" id="sliderL" min="0" max="100" value="65"></div>
<div class="slider-row">
<div class="slider-label"><span class="slider-name"><span class="lch-letter">C*</span> Насыщенность</span> <span id="valC" class="slider-value C">42</span></div>
<input type="range" id="sliderC" min="0" max="150" value="42"></div>
<div class="slider-row">
<div class="slider-label"><span class="slider-name"><span class="lch-letter">h*</span> Оттенок</span> <span id="valH" class="slider-value H">48°</span></div>
<input type="range" id="sliderH" min="0" max="360" value="48"></div>
</div>
</div>
<!-- HEX input -->
<div>
<div class="section-title">Ввод HEX / Lab</div>
<div class="hex-input-row"><input type="text" class="hex-input" id="hexInput" placeholder="#C8916A или 65,28,31" maxlength="20"> <button class="btn">→</button></div>
</div>
<!-- Modifiers -->
<div>
<div class="section-title">Быстрые модификаторы</div>
<div class="modifiers">
<div class="mod-pill" data-dl="10">светлее</div>
<div class="mod-pill" data-dl="-10">темнее</div>
<div class="mod-pill" data-dh="-20">теплее</div>
<div class="mod-pill" data-dh="20">холоднее</div>
<div class="mod-pill" data-dc="20">насыщеннее</div>
<div class="mod-pill" data-dc="-20">пастельнее</div>
<div class="mod-pill" data-dh="30">зеленее</div>
<div class="mod-pill" data-dh="-30">краснее</div>
</div>
</div>
</div>
<!-- ── RIGHT PANEL ── -->
<div class="panel-right">
<div class="search-header"><input type="text" class="search-natural" id="naturalSearch" placeholder="Теплее и светлее, почти бежевый…" onkeydown="if(event.key==='Enter') naturalSearch()"> <button class="search-btn">НАЙТИ</button></div>
<div class="tabs">
<div class="tab active">Похожие LCh</div>
<div class="tab">По брендам</div>
<div class="tab">Аналогичные</div>
</div>
<div id="resultsArea" class="results-area"><!-- filled by JS --></div>
</div>
</div>
<!-- ── MODAL ── -->
<div id="modalOverlay" class="modal-overlay">
<div class="modal">
<div id="modalSwatch" class="modal-swatch"><button class="modal-close">✕</button></div>
<div class="modal-body">
<div id="modalName" class="modal-name">—</div>
<div id="modalBrand" class="modal-brand">—</div>
<div class="modal-specs">
<div class="spec-item">
<div class="spec-label">HEX</div>
<div id="mHex" class="spec-val">—</div>
</div>
<div class="spec-item">
<div class="spec-label">ΔE (расстояние)</div>
<div id="mDelta" class="spec-val">—</div>
</div>
<div class="spec-item">
<div class="spec-label">L* · C* · h*</div>
<div id="mLch" class="spec-val">—</div>
</div>
<div class="spec-item">
<div class="spec-label">Lab</div>
<div id="mLab" class="spec-val">—</div>
</div>
</div>
<div class="modal-actions">
<div class="btn-outline">⬅ Использовать как источник</div>
<div class="btn-outline">Скопировать HEX</div>
</div>
</div>
</div>
</div>
            ]]>
        </content>
    </entry>
    <entry>
        <title>Сервис3</title>
        <author>
            <name>пэйнт.рф</name>
        </author>
        <link href="https://пэйнт.рф/servis3.html"/>
        <id>https://пэйнт.рф/servis3.html</id>

        <updated>2026-03-14T12:28:12+03:00</updated>
            <summary type="html">
                <![CDATA[
                    ⭐️ Сервис интерьерных цветов Выберите цвет L (светлота)50 C (насыщенность)20 h (тон)210 L 50 a 0 b 0 Корректировка #7A92A3 Светлота50% Насыщенность20 Тон210° ☀️ Светлее&hellip;
                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                <div class="container">
<h1>⭐️ Сервис интерьерных цветов</h1>
<div class="grid">
<div class="panel">
<h3>Выберите цвет</h3>
<div class="color-input"><input type="color" id="colorPicker" value="#7a92a3"> <input type="text" id="hexInput" value="#7A92A3"></div>
<div class="lch-box">
<div class="lch-row">L (светлота)<span id="lVal">50</span></div>
<div class="bar">
<div id="lBar" class="bar-fill" style="width: 50%; background: linear-gradient(90deg,#000,#fff);"></div>
</div>
<div class="lch-row">C (насыщенность)<span id="cVal">20</span></div>
<div class="bar">
<div id="cBar" class="bar-fill" style="width: 20%; background: linear-gradient(90deg,#999,#000);"></div>
</div>
<div class="lch-row">h (тон)<span id="hVal">210</span></div>
<div class="bar">
<div id="hBar" class="bar-fill" style="width: 58%; background: linear-gradient(90deg,red,yellow,lime,cyan,blue,magenta,red);"></div>
</div>
</div>
<div class="lab-row">
<div class="lab-box">
<div class="lab-label">L</div>
<div id="labL" class="lab-val">50</div>
</div>
<div class="lab-box">
<div class="lab-label">a</div>
<div id="labA" class="lab-val">0</div>
</div>
<div class="lab-box">
<div class="lab-label">b</div>
<div id="labB" class="lab-val">0</div>
</div>
</div>
</div>
<div class="panel">
<h3>Корректировка</h3>
<div id="preview" style="background: #7a92a3;"><span id="previewHex">#7A92A3</span></div>
<div class="slider-row">
<div class="slider-header">Светлота<span id="sliderLVal">50%</span></div>
<input type="range" id="sliderL" min="0" max="100" value="50"></div>
<div class="slider-row">
<div class="slider-header">Насыщенность<span id="sliderCVal">20</span></div>
<input type="range" id="sliderC" min="0" max="100" value="20"></div>
<div class="slider-row">
<div class="slider-header">Тон<span id="sliderHVal">210°</span></div>
<input type="range" id="sliderH" min="0" max="360" value="210"></div>
<div class="btns"><button>☀️ Светлее</button> <button>🌑 Темнее</button> <button>🌈 Насыщеннее</button> <button>🌫 Бледнее</button> <button style="background: #fff3e0; color: #e65100;">🔥 Теплее</button> <button style="background: #e3f2fd; color: #1565c0;">❄️ Холоднее</button></div>
</div>
<div class="panel">
<div class="results-title">Ближайшие цвета</div>
<div id="matches" class="match-list"></div>
</div>
</div>
</div>
<p>
<script>
        // Цветовые преобразования CIE LCh (без внешних библиотек)
        
        function hexToRgb(hex) {
            var r = parseInt(hex.slice(1,3), 16);
            var g = parseInt(hex.slice(3,5), 16);
            var b = parseInt(hex.slice(5,7), 16);
            return {r:r, g:g, b:b};
        }
        
        function rgbToHex(r, g, b) {
            var toHex = function(n) {
                var h = Math.max(0, Math.min(255, Math.round(n))).toString(16);
                return h.length === 1 ? '0' + h : h;
            };
            return '#' + toHex(r) + toHex(g) + toHex(b);
        }
        
        function rgbToXyz(r, g, b) {
            var r = r / 255, g = g / 255, b = b / 255;
            r = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
            g = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
            b = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
            var x = (r * 0.4124 + g * 0.3576 + b * 0.1805) * 100;
            var y = (r * 0.2126 + g * 0.7152 + b * 0.0722) * 100;
            var z = (r * 0.0193 + g * 0.1192 + b * 0.9505) * 100;
            return {x:x, y:y, z:z};
        }
        
        function xyzToLab(x, y, z) {
            var refX = 95.047, refY = 100.000, refZ = 108.883;
            x = x / refX; y = y / refY; z = z / refZ;
            x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + 16/116;
            y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + 16/116;
            z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + 16/116;
            var L = (116 * y) - 16;
            var a = 500 * (x - y);
            var bVal = 200 * (y - z);
            return {L:L, a:a, b:bVal};
        }
        
        function labToXyz(L, a, b) {
            var refX = 95.047, refY = 100.000, refZ = 108.883;
            var y = (L + 16) / 116;
            var x = a / 500 + y;
            var z = y - b / 200;
            var y3 = Math.pow(y, 3), x3 = Math.pow(x, 3), z3 = Math.pow(z, 3);
            y = y3 > 0.008856 ? y3 : (y - 16/116) / 7.787;
            x = x3 > 0.008856 ? x3 : (x - 16/116) / 7.787;
            z = z3 > 0.008856 ? z3 : (z - 16/116) / 7.787;
            return {x:x*refX, y:y*refY, z:z*refZ};
        }
        
        function xyzToRgb(x, y, z) {
            x = x / 100; y = y / 100; z = z / 100;
            var r = x * 3.2406 + y * -1.5372 + z * -0.4986;
            var g = x * -0.9689 + y * 1.8758 + z * 0.0415;
            var b = x * 0.0557 + y * -0.2040 + z * 1.0570;
            r = r > 0.0031308 ? 1.055 * Math.pow(r, 1/2.4) - 0.055 : 12.92 * r;
            g = g > 0.0031308 ? 1.055 * Math.pow(g, 1/2.4) - 0.055 : 12.92 * g;
            b = b > 0.0031308 ? 1.055 * Math.pow(b, 1/2.4) - 0.055 : 12.92 * b;
            return {r:r*255, g:g*255, b:b*255};
        }
        
        function labToLch(L, a, b) {
            var C = Math.sqrt(a*a + b*b);
            var h = Math.atan2(b, a) * 180 / Math.PI;
            if (h < 0) h += 360;
            return {L:L, C:C, h:h};
        }
        
        function lchToLab(L, C, h) {
            var hRad = h * Math.PI / 180;
            var a = C * Math.cos(hRad);
            var bVal = C * Math.sin(hRad);
            return {L:L, a:a, b:bVal};
        }
        
        function hexToLch(hex) {
            var rgb = hexToRgb(hex);
            var xyz = rgbToXyz(rgb.r, rgb.g, rgb.b);
            var lab = xyzToLab(xyz.x, xyz.y, xyz.z);
            return labToLch(lab.L, lab.a, lab.b);
        }
        
        function lchToHex(L, C, h) {
            var lab = lchToLab(L, C, h);
            var xyz = labToXyz(lab.L, lab.a, lab.b);
            var rgb = xyzToRgb(xyz.x, xyz.y, xyz.z);
            return rgbToHex(rgb.r, rgb.g, rgb.b);
        }

        // База интерьерных цветов (загружается динамически)
        var colors = [];

        async function loadColors() {
            try {
                const response = await fetch('/data/palettes-list.json');
                const palettes = await response.json();
                
                const results = [];
                for (const p of palettes) {
                    try {
                        const res = await fetch(p.file);
                        if (res.ok) {
                            const data = await res.json();
                            results.push(...data);
                        }
                    } catch (e) {}
                }
                
                colors = results.map(c => ({
                    n: c.name,
                    h: c.hex,
                    b: c.series || c.ColorFamily || c.collection?.split('/')[0] || ''
                })).filter(c => c.h && /^#[0-9A-Fa-f]{6}$/.test(c.h));
                
                init(document.getElementById('colorPicker').value);
            } catch (e) {
                console.error('Ошибка загрузки:', e);
            }
        }

        // Приложение
        var L = 50, C = 20, H = 210;
        
        var p = document.getElementById('colorPicker');
        var h = document.getElementById('hexInput');
        var pre = document.getElementById('preview');
        var preHex = document.getElementById('previewHex');
        var sL = document.getElementById('sliderL');
        var sC = document.getElementById('sliderC');
        var sH = document.getElementById('sliderH');
        var mat = document.getElementById('matches');

        function init(hex) {
            var lch = hexToLch(hex);
            L = Math.max(0, Math.min(100, lch.L));
            C = Math.max(0, lch.C);
            H = lch.h;
            draw();
        }

        function draw() {
            var hex = lchToHex(L, C, H);
            var rgb = hexToRgb(hex);
            var xyz = rgbToXyz(rgb.r, rgb.g, rgb.b);
            var lab = xyzToLab(xyz.x, xyz.y, xyz.z);
            
            pre.style.background = hex;
            preHex.textContent = hex.toUpperCase();
            h.value = hex.toUpperCase();
            p.value = hex;
            
            sL.value = L;
            sC.value = C;
            sH.value = H;
            
            document.getElementById('lVal').textContent = Math.round(L);
            document.getElementById('cVal').textContent = Math.round(C);
            document.getElementById('hVal').textContent = Math.round(H);
            document.getElementById('sliderLVal').textContent = Math.round(L) + '%';
            document.getElementById('sliderCVal').textContent = Math.round(C);
            document.getElementById('sliderHVal').textContent = Math.round(H) + '°';
            document.getElementById('lBar').style.width = L + '%';
            document.getElementById('cBar').style.width = Math.min(100, C) + '%';
            document.getElementById('hBar').style.width = (H/360*100) + '%';
            document.getElementById('labL').textContent = Math.round(lab.L);
            document.getElementById('labA').textContent = Math.round(lab.a);
            document.getElementById('labB').textContent = Math.round(lab.b);
            
            find(lab);
        }

        function find(targetLab) {
            var list = colors.map(function(x) {
                var dbRgb = hexToRgb(x.h);
                var dbXyz = rgbToXyz(dbRgb.r, dbRgb.g, dbRgb.b);
                var dbLab = xyzToLab(dbXyz.x, dbXyz.y, dbXyz.z);
                var dE = Math.sqrt(
                    Math.pow(targetLab.L - dbLab.L, 2) +
                    Math.pow(targetLab.a - dbLab.a, 2) +
                    Math.pow(targetLab.b - dbLab.b, 2)
                );
                return {n:x.n, h:x.h, b:x.b, dE:dE};
            }).sort(function(a,b){return a.dE-b.dE});
            
            render(list.slice(0,8));
        }

        function render(list) {
            mat.innerHTML = list.map(function(x,i) {
                var cls = x.dE < 2 ? 'color:green' : x.dE < 5 ? 'color:orange' : 'color:red';
                return '<div class="match" onclick="init(\''+x.h+'\')">'+
                    '<div class="swatch" style="background:'+x.h+'"></div>'+
                    '<div class="info"><div class="name">'+x.n+'</div><div class="brand">'+x.b+'</div></div>'+
                    '<div class="delta"><div style="font-size:10px;color:#888">ΔE</div><div class="delta-val" style="'+cls+'">'+x.dE.toFixed(1)+'</div></div>'+
                    (i===0?'<span class="best">Ближайший</span>':'')+
                '</div>';
            }).join('');
        }

        function adjL(d){ L=Math.max(0,Math.min(100,L+d)); draw(); }
        function adjC(d){ C=Math.max(0,C+d); draw(); }
        function adjH(d){ H=(H+d+360)%360; draw(); }

        p.addEventListener('input',function(){init(this.value)});
        h.addEventListener('input',function(){if(/^#[0-9A-Fa-f]{6}$/.test(this.value))init(this.value)});
        sL.addEventListener('input',function(){L=+this.value;draw()});
        sC.addEventListener('input',function(){C=+this.value;draw()});
        sH.addEventListener('input',function(){H=+this.value;draw()});

        init('#7a92a3');
        loadColors();
    </script>
</p>
            ]]>
        </content>
    </entry>
    <entry>
        <title>Подбор интерьерных цветов</title>
        <author>
            <name>пэйнт.рф</name>
        </author>
        <link href="https://пэйнт.рф/servis2.html"/>
        <id>https://пэйнт.рф/servis2.html</id>

        <updated>2026-03-14T12:27:57+03:00</updated>
            <summary type="html">
                <![CDATA[
                    Поиск интерьерных цветов (Мульти-загрузка) Подбор интерьерных цветов — важный этап при создании гармоничного дизайна помещения. Онлайн-сервис «Пэйнт.рф» позволяет быстро и точно подобрать нужный оттенок, сравнить&hellip;
                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                <div class="container">
<h2>Поиск интерьерных цветов (Мульти-загрузка)</h2>
<!-- КОНТЕЙНЕР ВИДЖЕТА -->
<div id="lch-service-widget"></div>
</div>
<p>
<script>
		(function() {
			// === 1. ВСТРОЕННАЯ БИБЛИОТЕКА ЦВЕТА ===
			const ColorMath = {
				isValidHex: (hex) => /^#([0-9A-F]{3}){1,2}$/i.test(hex),
				hexToRgb: (hex) => {
					hex = hex.replace('#', '');
					if (hex.length === 3) hex = hex.split('').map(c => c + c).join('');
					const int = parseInt(hex, 16);
					return [(int >> 16) & 255, (int >> 8) & 255, int & 255];
				},
				rgbToLab: (r, g, b) => {
					let [lr, lg, lb] = [r, g, b].map(v => {
						v /= 255;
						return v > 0.04045 ? Math.pow((v + 0.055) / 1.055, 2.4) : v / 12.92;
					});
					lr *= 100;
					lg *= 100;
					lb *= 100;
					let x = lr * 0.4124 + lg * 0.3576 + lb * 0.1805;
					let y = lr * 0.2126 + lg * 0.7152 + lb * 0.0722;
					let z = lr * 0.0193 + lg * 0.1192 + lb * 0.9505;
					x /= 95.047;
					y /= 100.000;
					z /= 108.883;
					[x, y, z] = [x, y, z].map(v => v > 0.008856 ? Math.pow(v, 1 / 3) : (7.787 * v) + (16 / 116));
					return [(116 * y) - 16, 500 * (x - y), 200 * (y - z)];
				},
				labToLch: (l, a, b) => {
					const c = Math.sqrt(a * a + b * b);
					let h = Math.atan2(b, a) * (180 / Math.PI);
					if (h < 0) h += 360;
					return [l, c, h];
				},
				hexToLch: (hex) => {
					if (!ColorMath.isValidHex(hex)) return null;
					const [r, g, b] = ColorMath.hexToRgb(hex);
					const [l, a, b_val] = ColorMath.rgbToLab(r, g, b);
					return ColorMath.labToLch(l, a, b_val);
				},
				parseLCHString: (str) => {
					if (!str) return [0, 0, 0];
					return str.split(',').map(s => parseFloat(s.trim()));
				},
				calcDeltaE: (target, pLCH) => {
					const dL = target.L - pLCH[0];
					const dC = target.C - pLCH[1];
					let dH = Math.abs(target.h - pLCH[2]);
					if (dH > 180) dH = 360 - dH;
					return Math.sqrt((dL * 1.5) ** 2 + (dC * 1.2) ** 2 + (dH * 1.0) ** 2);
				}
			};

			// === 2. КОНФИГУРАЦИЯ ===
			const CONFIG = {
				indexUrl: '/data/palettes-list.json', // Файл-индекс со списком палитр
				defaultHex: '#D25362'
			};

			// === 3. ИНИЦИАЛИЗАЦИЯ ===
			const host = document.getElementById('lch-service-widget');
			if (!host) return;

			const shadow = host.attachShadow({
				mode: 'open'
			});
			const pfx = 'lcs-';

			// === 4. СТИЛИ ===
			const styles = `
    <style>
        :host { all: initial; display: block; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; color: #333; }
        .${pfx}wrapper { background: #fff; border: 1px solid #e0e0e0; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.05); }
        .${pfx}grid { display: flex; flex-wrap: wrap; }
        .${pfx}sidebar { flex: 0 0 320px; background: #f9fafb; padding: 20px; border-right: 1px solid #eee; min-width: 280px; }
        .${pfx}content { flex: 1; padding: 20px; min-width: 300px; }
        @media (max-width: 768px) { .${pfx}sidebar { flex: 0 0 100%; border-right: none; border-bottom: 1px solid #eee; } }
        
        .${pfx}color-preview { background: #fff; padding: 15px; border-radius: 8px; border: 1px solid #eee; text-align: center; margin-bottom: 20px; }
        .${pfx}big-swatch { width: 100%; height: 90px; border-radius: 6px; margin-bottom: 10px; border: 1px solid rgba(0,0,0,0.1); }
        .${pfx}hex-display { font-size: 20px; font-weight: 700; font-family: monospace; color: #2c3e50; }
        .${pfx}lch-badges { display: flex; justify-content: center; gap: 8px; margin-top: 8px; }
        .${pfx}badge { background: #eef2f7; color: #555; padding: 3px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; }

        .${pfx}input-group { margin-bottom: 15px; }
        .${pfx}label { display: block; font-size: 13px; font-weight: 600; margin-bottom: 6px; color: #444; }
        .${pfx}input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; font-size: 14px; box-sizing: border-box; }
        
        .${pfx}btn { width: 100%; padding: 11px; background: #007bff; color: white; border: none; border-radius: 6px; font-weight: 600; cursor: pointer; transition: background 0.2s; font-size: 14px; }
        .${pfx}btn:hover { background: #0056b3; }
        .${pfx}btn-secondary { background: #6c757d; margin-top: 5px; }
        .${pfx}btn-secondary:hover { background: #545b62; }
        .${pfx}btn:disabled { background: #ccc; cursor: not-allowed; opacity: 0.7; }
        .${pfx}btn-loading { position: relative; pointer-events: none; }
        .${pfx}btn-loading::after { content: ''; position: absolute; left: 50%; top: 50%; width: 16px; height: 16px; margin: -8px 0 0 -8px; border: 2px solid #fff; border-top-color: transparent; border-radius: 50%; animation: spin 0.8s linear infinite; }

        @keyframes spin { to { transform: rotate(360deg); } }

        .${pfx}mod-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin: 15px 0; }
        .${pfx}mod-btn { padding: 8px 2px; font-size: 11px; background: #fff; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; }
        .${pfx}mod-btn:hover { background: #f0f0f0; }

        .${pfx}slider-wrap { margin-bottom: 12px; }
        .${pfx}slider-row { display: flex; justify-content: space-between; font-size: 12px; font-weight: 600; margin-bottom: 4px; color: #555; }
        .${pfx}range { width: 100%; accent-color: #007bff; }

        .${pfx}palette-box { border: 1px solid #ddd; border-radius: 6px; background: #fff; max-height: 250px; overflow-y: auto; margin-bottom: 10px; }
        .${pfx}p-item { padding: 8px 10px; border-bottom: 1px solid #f0f0f0; font-size: 13px; display: flex; align-items: center; }
        .${pfx}p-item:last-child { border-bottom: none; }
        .${pfx}checkbox { margin-right: 8px; }
        .${pfx}select-all { padding: 8px; background: #f1f3f5; font-size: 12px; font-weight: bold; text-align: center; cursor: pointer; border-bottom: 1px solid #ddd; }

        .${pfx}tabs { display: flex; gap: 8px; margin-bottom: 20px; border-bottom: 1px solid #eee; padding-bottom: 10px; flex-wrap: wrap; }
        .${pfx}tab { padding: 8px 16px; border-radius: 20px; font-size: 13px; font-weight: 600; cursor: pointer; background: #f1f3f5; color: #555; }
        .${pfx}tab.active { background: #007bff; color: white; }
        
        .${pfx}results-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 15px; }
        .${pfx}card { border: 1px solid #eee; border-radius: 8px; overflow: hidden; background: #fff; cursor: pointer; transition: transform 0.2s; position: relative; }
        .${pfx}card:hover { transform: translateY(-3px); box-shadow: 0 6px 12px rgba(0,0,0,0.1); }
        .${pfx}card-swatch { height: 110px; width: 100%; }
        .${pfx}delta-badge { position: absolute; top: 8px; right: 8px; background: rgba(0,0,0,0.75); color: #fff; font-size: 10px; padding: 3px 6px; border-radius: 4px; }
        .${pfx}card-body { padding: 12px; }
        .${pfx}card-name { font-weight: 700; font-size: 14px; margin-bottom: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        .${pfx}card-series { font-size: 11px; color: #888; margin-bottom: 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        .${pfx}card-meta { font-size: 10px; color: #aaa; font-family: monospace; background: #f8f9fa; padding: 3px; border-radius: 3px; text-align: center; }

        .${pfx}status-msg { text-align: center; padding: 40px 20px; color: #666; }
        .${pfx}error-msg { text-align: center; padding: 20px; color: #d9534f; background: #fdf7f7; border: 1px solid #f5c6cb; border-radius: 6px; margin-bottom: 15px; }
        .${pfx}log-area { font-size: 11px; color: #666; margin-top: 10px; max-height: 100px; overflow-y: auto; background: #f9f9f9; padding: 5px; border-radius: 4px; }
    </style>
    `;

			// === 5. HTML СТРУКТУРА ===
			const html = `
        <div class="${pfx}wrapper">
            <div class="${pfx}grid">
                <div class="${pfx}sidebar">
                    <div class="${pfx}color-preview">
                        <div id="${pfx}swatch" class="${pfx}big-swatch" style="background: #cccccc;"></div>
                        <div id="${pfx}hexVal" class="${pfx}hex-display">#CCCCCC</div>
                        <div id="${pfx}lchVals" class="${pfx}lch-badges">
                            <span class="${pfx}badge">L: -</span>
                            <span class="${pfx}badge">C: -</span>
                            <span class="${pfx}badge">h: -°</span>
                        </div>
                    </div>

                    <div class="${pfx}input-group">
                        <label class="${pfx}label">Исходный цвет (HEX)</label>
                        <input type="text" id="${pfx}inpHex" class="${pfx}input" value="${CONFIG.defaultHex}">
                        <button id="${pfx}btnApply" class="${pfx}btn">Применить цвет</button>
                    </div>

                    <div class="${pfx}input-group">
                        <label class="${pfx}label">Модификаторы</label>
                        <div class="${pfx}mod-grid">
                            <button class="${pfx}mod-btn" data-type="L" data-val="10">Светлее</button>
                            <button class="${pfx}mod-btn" data-type="L" data-val="-10">Темнее</button>
                            <button class="${pfx}mod-btn" data-type="C" data-val="10">Насыщ.</button>
                            <button class="${pfx}mod-btn" data-type="C" data-val="-10">Пастель</button>
                            <button class="${pfx}mod-btn" data-type="H" data-val="15">Теплее</button>
                            <button class="${pfx}mod-btn" data-type="H" data-val="-15">Холодн.</button>
                        </div>
                        <div class="${pfx}slider-wrap">
                            <div class="${pfx}slider-row"><span>L</span><span id="${pfx}lblL">0</span></div>
                            <input type="range" id="${pfx}rngL" class="${pfx}range" min="-40" max="40" value="0">
                        </div>
                        <div class="${pfx}slider-wrap">
                            <div class="${pfx}slider-row"><span>C</span><span id="${pfx}lblC">0</span></div>
                            <input type="range" id="${pfx}rngC" class="${pfx}range" min="-40" max="40" value="0">
                        </div>
                        <div class="${pfx}slider-wrap">
                            <div class="${pfx}slider-row"><span>h</span><span id="${pfx}lblH">0°</span></div>
                            <input type="range" id="${pfx}rngH" class="${pfx}range" min="-90" max="90" value="0">
                        </div>
                        <button id="${pfx}btnReset" class="${pfx}btn ${pfx}btn-secondary">Сброс</button>
                    </div>

                    <div class="${pfx}input-group">
                        <label class="${pfx}label">Палитры (из индекса)</label>
                        <div id="${pfx}selectAll" class="${pfx}select-all">Выбрать все / Снять все</div>
                        <div id="${pfx}paletteList" class="${pfx}palette-box">
                            <div style="padding:15px; text-align:center; color:#999;">Загрузка списка палитр...</div>
                        </div>
                        <button id="${pfx}btnLoad" class="${pfx}btn" disabled>Загрузить выбранные палитры</button>
                        <div id="${pfx}loadLog" class="${pfx}log-area"></div>
                    </div>
                </div>

                <div class="${pfx}content">
                    <div class="${pfx}tabs">
                        <div class="${pfx}tab active" data-mode="closest">Ближайшие (ΔE)</div>
                        <div class="${pfx}tab" data-mode="byBrand">Лучшие по брендам</div>
                        <div class="${pfx}tab" data-mode="related">Родственные</div>
                    </div>
                    <div id="${pfx}results" class="${pfx}results-grid">
                        <div class="${pfx}status-msg">Выберите палитры и нажмите «Загрузить».</div>
                    </div>
                </div>
            </div>
        </div>
    `;

			shadow.innerHTML = styles + html;

			// === 6. ЛОГИКА ===
			let paletteIndex = []; // Данные из palettes-list.json
			let loadedColors = []; // Объединенный массив всех цветов
			let baseColor = null;
			let modifiers = {
				L: 0,
				C: 0,
				H: 0
			};
			let searchMode = 'closest';

			const $ = (id) => shadow.getElementById(id);
			const els = {
				inpHex: $(`${pfx}inpHex`),
				btnApply: $(`${pfx}btnApply`),
				swatch: $(`${pfx}swatch`),
				hexVal: $(`${pfx}hexVal`),
				lchVals: $(`${pfx}lchVals`),
				rngL: $(`${pfx}rngL`),
				rngC: $(`${pfx}rngC`),
				rngH: $(`${pfx}rngH`),
				lblL: $(`${pfx}lblL`),
				lblC: $(`${pfx}lblC`),
				lblH: $(`${pfx}lblH`),
				btnReset: $(`${pfx}btnReset`),
				modBtns: shadow.querySelectorAll(`.${pfx}mod-btn`),
				paletteList: $(`${pfx}paletteList`),
				selectAll: $(`${pfx}selectAll`),
				btnLoad: $(`${pfx}btnLoad`),
				results: $(`${pfx}results`),
				tabs: shadow.querySelectorAll(`.${pfx}tab`),
				loadLog: $(`${pfx}loadLog`)
			};

			function log(msg) {
				const div = document.createElement('div');
				div.textContent = msg;
				els.loadLog.appendChild(div);
				els.loadLog.scrollTop = els.loadLog.scrollHeight;
			}

			function updateBaseColorUI(hex) {
				if (!ColorMath.isValidHex(hex)) return;
				const lch = ColorMath.hexToLch(hex);
				baseColor = {
					hex,
					L: lch[0],
					C: lch[1],
					h: lch[2]
				};
				els.swatch.style.background = hex;
				els.hexVal.textContent = hex.toUpperCase();
				els.lchVals.innerHTML = `
            <span class="${pfx}badge">L: ${lch[0].toFixed(1)}</span>
            <span class="${pfx}badge">C: ${lch[1].toFixed(1)}</span>
            <span class="${pfx}badge">h: ${lch[2].toFixed(0)}°</span>
        `;
			}

			function getTargetLCH() {
				if (!baseColor) return null;
				let h = baseColor.h + modifiers.H;
				while (h < 0) h += 360;
				while (h >= 360) h -= 360;
				return {
					L: baseColor.L + modifiers.L,
					C: Math.max(0, baseColor.C + modifiers.C),
					h: h
				};
			}

			function performSearch() {
				if (!baseColor || loadedColors.length === 0) return;
				const target = getTargetLCH();
				let results = [];

				if (searchMode === 'closest') {
					results = loadedColors.map(item => {
						const pLCH = ColorMath.parseLCHString(item.LCH);
						return {
							...item,
							_delta: ColorMath.calcDeltaE(target, pLCH),
							_pLCH: pLCH
						};
					}).sort((a, b) => a._delta - b._delta).slice(0, 15);
				} else if (searchMode === 'byBrand') {
					const best = {};
					loadedColors.forEach(item => {
						const pLCH = ColorMath.parseLCHString(item.LCH);
						const delta = ColorMath.calcDeltaE(target, pLCH);
						const key = item.collection || item.name;
						if (!best[key] || delta < best[key]._delta) {
							best[key] = {
								...item,
								_delta: delta,
								_pLCH: pLCH
							};
						}
					});
					results = Object.values(best).sort((a, b) => a._delta - b._delta);
				} else if (searchMode === 'related') {
					results = loadedColors.filter(item => {
						const pLCH = ColorMath.parseLCHString(item.LCH);
						let dH = Math.abs(pLCH[2] - target.h);
						if (dH > 180) dH = 360 - dH;
						return dH < 25;
					}).map(item => {
						const pLCH = ColorMath.parseLCHString(item.LCH);
						return {
							...item,
							_delta: ColorMath.calcDeltaE(target, pLCH),
							_pLCH: pLCH
						};
					}).sort((a, b) => Math.abs(a._pLCH[0] - target.L) - Math.abs(b._pLCH[0] - target.L)).slice(0, 15);
				}

				renderResults(results);
			}

			function renderResults(list) {
				els.results.innerHTML = '';
				if (!list.length) {
					els.results.innerHTML = `<div class="${pfx}status-msg">Совпадений не найдено.</div>`;
					return;
				}
				list.forEach(item => {
					const card = document.createElement('div');
					card.className = `${pfx}card`;
					const deltaHtml = item._delta !== undefined ? `<div class="${pfx}delta-badge">ΔE: ${item._delta.toFixed(1)}</div>` : '';
					const series = item.series || item.collection || 'Palette';

					card.innerHTML = `
                <div class="${pfx}card-swatch" style="background-color: ${item.hex}">${deltaHtml}</div>
                <div class="${pfx}card-body">
                    <div class="${pfx}card-name" title="${item.name}">${item.name}</div>
                    <div class="${pfx}card-series" title="${series}">${series}</div>
                    <div class="${pfx}card-meta">${item.hex}</div>
                </div>
            `;
					card.onclick = () => {
						els.inpHex.value = item.hex;
						updateBaseColorUI(item.hex);
						resetModifiers();
						performSearch();
					};
					els.results.appendChild(card);
				});
			}

			function resetModifiers() {
				modifiers = {
					L: 0,
					C: 0,
					H: 0
				};
				els.rngL.value = 0;
				els.lblL.textContent = '0';
				els.rngC.value = 0;
				els.lblC.textContent = '0';
				els.rngH.value = 0;
				els.lblH.textContent = '0°';
			}

			// --- ЗАГРУЗКА ДАННЫХ ---

			async function loadIndex() {
				try {
					const res = await fetch(CONFIG.indexUrl);
					if (!res.ok) throw new Error(`HTTP ${res.status}`);
					paletteIndex = await res.json();

					if (!Array.isArray(paletteIndex)) throw new Error('Индекс не является массивом');

					renderPaletteList();
				} catch (e) {
					console.error(e);
					els.paletteList.innerHTML = `<div class="${pfx}error-msg">Ошибка загрузки индекса:<br>${CONFIG.indexUrl}<br>${e.message}</div>`;
				}
			}

			function renderPaletteList() {
				els.paletteList.innerHTML = '';
				if (paletteIndex.length === 0) {
					els.paletteList.innerHTML = '<div style="padding:10px;">Список пуст</div>';
					return;
				}

				paletteIndex.forEach((item, idx) => {
					const name = item.name || item.collection || `Palette ${idx+1}`;
					const div = document.createElement('div');
					div.className = `${pfx}p-item`;
					div.innerHTML = `
                <input type="checkbox" class="${pfx}checkbox" value="${idx}" checked>
                <span>${name}</span>
            `;
					els.paletteList.appendChild(div);
				});
				els.btnLoad.disabled = false;
				els.btnLoad.textContent = `Загрузить (${paletteIndex.length})`;
			}

			async function loadSelectedPalettes() {
				const checked = Array.from(shadow.querySelectorAll(`.${pfx}checkbox:checked`));
				if (checked.length === 0) return alert('Выберите хотя бы одну палитру');

				els.btnLoad.classList.add(`${pfx}btn-loading`);
				els.btnLoad.textContent = 'Загрузка...';
				els.loadLog.innerHTML = '';
				els.results.innerHTML = '<div class="${pfx}status-msg">Загрузка файлов палитр...</div>';

				loadedColors = [];
				const promises = [];

				checked.forEach(chk => {
					const idx = parseInt(chk.value);
					const item = paletteIndex[idx];
					const url = item.url || item.file; // Поддержка разных ключей

					if (!url) {
						log(`⚠️ Нет URL для ${item.name}`);
						return;
					}

					const p = fetch(url)
						.then(res => {
							if (!res.ok) throw new Error(`HTTP ${res.status}`);
							return res.json();
						})
						.then(data => {
							// Ожидаем массив цветов. Если объект, пробуем найти массив внутри
							let colors = Array.isArray(data) ? data : (data.colors || data.palettes || []);

							// Добавляем метку источника, если её нет
							colors = colors.map(c => ({
								...c,
								collection: c.collection || item.name || url
							}));

							loadedColors = [...loadedColors, ...colors];
							log(`✅ Загружено: ${item.name} (${colors.length} цветов)`);
						})
						.catch(err => {
							log(`❌ Ошибка ${item.name}: ${err.message}`);
						});

					promises.push(p);
				});

				await Promise.all(promises);

				els.btnLoad.classList.remove(`${pfx}btn-loading`);
				els.btnLoad.textContent = `Загружено (${loadedColors.length} цветов)`;

				if (loadedColors.length === 0) {
					els.results.innerHTML = '<div class="${pfx}error-msg">Не удалось загрузить ни одного цвета. Проверьте консоль и логи.</div>';
				} else {
					els.results.innerHTML = `<div class="${pfx}status-msg">В базе <strong>${loadedColors.length}</strong> цветов.<br>Введите цвет для поиска.</div>`;
					if (baseColor) performSearch();
				}
			}

			// --- СОБЫТИЯ ---
			els.btnApply.onclick = () => {
				const v = els.inpHex.value.trim();
				if (ColorMath.isValidHex(v)) {
					updateBaseColorUI(v);
					resetModifiers();
					performSearch();
				} else alert('Неверный HEX');
			};

			els.btnReset.onclick = () => {
				resetModifiers();
				if (baseColor) performSearch();
			};

			[els.rngL, els.rngC, els.rngH].forEach(inp => {
				inp.oninput = (e) => {
					const type = e.target.id.includes('L') ? 'L' : e.target.id.includes('C') ? 'C' : 'H';
					modifiers[type] = parseInt(e.target.value);
					const sign = modifiers[type] > 0 ? '+' : '';
					const unit = type === 'H' ? '°' : '';
					if (type === 'L') els.lblL.textContent = sign + modifiers[type];
					if (type === 'C') els.lblC.textContent = sign + modifiers[type];
					if (type === 'H') els.lblH.textContent = sign + modifiers[type] + unit;
					if (baseColor) performSearch();
				};
			});

			els.modBtns.forEach(btn => {
				btn.onclick = () => {
					const t = btn.dataset.type;
					const v = parseInt(btn.dataset.val);
					modifiers[t] += v;
					if (t === 'L') modifiers.L = Math.max(-40, Math.min(40, modifiers.L));
					if (t === 'C') modifiers.C = Math.max(-40, Math.min(40, modifiers.C));

					els.rngL.value = modifiers.L;
					els.lblL.textContent = (modifiers.L > 0 ? '+' : '') + modifiers.L;
					els.rngC.value = modifiers.C;
					els.lblC.textContent = (modifiers.C > 0 ? '+' : '') + modifiers.C;
					els.rngH.value = modifiers.H;
					els.lblH.textContent = (modifiers.H > 0 ? '+' : '') + modifiers.H + '°';

					if (baseColor) performSearch();
				};
			});

			els.selectAll.onclick = () => {
				const all = shadow.querySelectorAll(`.${pfx}checkbox`);
				const state = !Array.from(all).every(c => c.checked);
				all.forEach(c => c.checked = state);
			};

			els.btnLoad.onclick = loadSelectedPalettes;

			els.tabs.forEach(tab => {
				tab.onclick = () => {
					els.tabs.forEach(t => t.classList.remove('active'));
					tab.classList.add('active');
					searchMode = tab.dataset.mode;
					if (baseColor && loadedColors.length > 0) performSearch();
				};
			});

			// Старт
			updateBaseColorUI(els.inpHex.value);
			loadIndex();

		})();
	</script>
</p>
<p><strong>Подбор интерьерных цветов</strong> — важный этап при создании гармоничного дизайна помещения. Онлайн-сервис «Пэйнт.рф» позволяет быстро и точно подобрать нужный оттенок, сравнить его с популярными палитрами и найти ближайшие аналоги среди известных производителей.</p>
<p>С помощью инструмента вы можете загрузить исходный цвет в формате HEX и мгновенно получить подборку похожих оттенков из различных каталогов, включая RAL, Pantone, Sherwin-Williams, Dulux и другие. Это особенно удобно для дизайнеров интерьера, архитекторов и всех, кто занимается ремонтом или оформлением помещений. Сервис поддерживает мультизагрузку, что позволяет одновременно работать с несколькими цветами и ускоряет процесс подбора. Гибкие настройки дают возможность изменять параметры цвета — насыщенность, светлоту и тон — чтобы добиться максимально точного результата. Отдельное преимущество — поиск аналогов по разным брендам. Если у вас есть конкретный цвет, но он недоступен у нужного производителя, вы легко найдете максимально близкий вариант. Это помогает сэкономить время и избежать ошибок при выборе краски или отделочных материалов.</p>
<p>Инструмент также позволяет фильтровать результаты по популярности, бренду и характеристикам цвета. Благодаря этому вы получаете не просто список оттенков, а удобную систему для профессиональной работы с цветом. <em>Онлайн-подбор цветов </em>на «Пэйнт.рф» — это современное решение для точного выбора оттенков, которое подходит как для профессионалов, так и для новичков. Используйте сервис, чтобы создать идеальную цветовую палитру для вашего интерьера.</p>
            ]]>
        </content>
    </entry>
    <entry>
        <title>Поиск относительных цветов по интерьерным палитрам на CIE LCh</title>
        <author>
            <name>пэйнт.рф</name>
        </author>
        <link href="https://пэйнт.рф/servis.html"/>
        <id>https://пэйнт.рф/servis.html</id>

        <updated>2026-03-12T11:29:05+03:00</updated>
            <summary type="html">
                <![CDATA[
                    Relative Colors Поиск относительных цветов по интерьерным палитрам на CIE LCh Стандартные RGB и HEX плохо подходят для запроса "похожий, но немного другой". Здесь используется&hellip;
                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                <div class="wrap"><header class="topbar">
<div class="brand">
<div class="mark"> </div>
<div style="min-width: 0;">
<h1>Relative Colors</h1>
<p>Поиск относительных цветов по интерьерным палитрам на CIE LCh</p>
</div>
</div>
<div class="actions"><button id="btnReload" class="btn" type="button">Перезагрузить палитры</button> <button id="btnSearchNearest" class="btn primary" type="button">Искать по HEX / Lab</button></div>
</header>
<section class="hero">
<div class="panel">
<div class="badge">Теплее, холоднее, светлее, темнее - по-человечески</div>
<h2>Найди цвет похожий, но чуть теплее и светлее.</h2>
<p class="lead">Стандартные RGB и HEX плохо подходят для запроса "похожий, но немного другой". Здесь используется <strong>CIE LCh</strong> - модель, которая ближе к тому, как глаз воспринимает светлоту, насыщенность и оттенок. Вы задаете понятный сдвиг, а сервис ищет совпадения в интерьерных палитрах.</p>
<div class="chips"><span class="chip"><i></i> L*: 0-100</span> <span class="chip"><i style="background: var(--accent2);"></i> C*: насыщенность</span> <span class="chip"><i style="background: var(--accent3);"></i> h*: 0-360°</span></div>
<div id="loadStatus" class="status">Палитры еще не загружены.</div>
<div class="help">Отдельный режим ищет ближайший интерьерный цвет по HEX или Lab. Это полезно, когда у вас уже есть исходный образец.</div>
</div>
<aside class="side">
<div class="mini">
<h3>Как это работает</h3>
<ol class="steps">
<li>
<div class="n">1</div>
<div><strong>Берем базовый цвет</strong>HEX или Lab → перевод в Lab/LCh</div>
</li>
<li>
<div class="n">2</div>
<div><strong>Понимаем запрос</strong>Теплее, холоднее, светлее, темнее, насыщеннее</div>
</li>
<li>
<div class="n">3</div>
<div><strong>Ищем в палитрах</strong>Сравниваем с готовыми интерьерными цветами</div>
</li>
</ol>
</div>
</aside>
</section>
<section class="grid">
<div class="card">
<h3>Относительный поиск</h3>
<div class="tabs" role="tablist" aria-label="Режим поиска"><button id="tabRelative" class="tab" role="tab" type="button" aria-selected="true">Словами</button> <button id="tabNearest" class="tab" role="tab" type="button" aria-selected="false">По HEX / Lab</button></div>
<div id="relativePane" class="stack" style="margin-top: 14px;">
<div class="two">
<div class="field"><label for="baseHex">Базовый HEX</label><input id="baseHex" value="#C8B39B" autocomplete="off" spellcheck="false" type="text">
<div class="help">Формат: <code>#RRGGBB</code>.</div>
</div>
<div class="field"><label for="baseLab">Или Lab</label><input id="baseLab" placeholder="74, 2, 18" autocomplete="off" spellcheck="false" type="text">
<div class="help">Формат: <code>L, a, b</code>. Lab приоритетнее HEX.</div>
</div>
</div>
<div class="field"><label for="prompt">Человеческий запрос</label> <textarea id="prompt" placeholder="Например: чуть теплее и светлее, но спокойнее и менее насыщенно"></textarea>
<div class="help">Поддерживаются слова: <code>теплее</code>, <code>холоднее</code>, <code>светлее</code>, <code>темнее</code>, <code>насыщеннее</code>, <code>приглушеннее</code>. Усилители: <code>чуть</code>, <code>слегка</code>, <code>немного</code>, <code>сильно</code>, <code>очень</code>.</div>
</div>
<div class="ranges">
<div class="range-row">
<div class="range-head">Светлота<strong id="outDL">0</strong></div>
<input id="rL" type="range" min="-25" max="25" step="1" value="0"></div>
<div class="range-row">
<div class="range-head">Насыщенность<strong id="outDC">0</strong></div>
<input id="rC" type="range" min="-35" max="35" step="1" value="0"></div>
<div class="range-row">
<div class="range-head">Теплее / холоднее<strong id="outDH">0°</strong></div>
<input id="rH" type="range" min="-45" max="45" step="1" value="0"></div>
</div>
<div class="quick"><button class="btn" type="button" data-nudge="L:+5">Светлее</button> <button class="btn" type="button" data-nudge="L:-5">Темнее</button> <button class="btn" type="button" data-nudge="H:+10">Теплее</button> <button class="btn" type="button" data-nudge="H:-10">Холоднее</button> <button class="btn" type="button" data-nudge="C:+8">Насыщеннее</button> <button class="btn" type="button" data-nudge="C:-8">Спокойнее</button> <button id="btnReset" class="btn" type="button">Сброс</button></div>
<div class="swatches">
<div class="swatch">
<div id="baseBar" class="bar"></div>
<div class="meta">
<div class="meta-top"><strong>База</strong><code id="baseOutHex">#C8B39B</code></div>
<div class="sub">LCh: <code id="baseOutLch">—</code></div>
</div>
</div>
<div class="swatch">
<div id="targetBar" class="bar"></div>
<div class="meta">
<div class="meta-top"><strong>Цель</strong><code id="targetOutHex">—</code></div>
<div class="sub">LCh: <code id="targetOutLch">—</code></div>
</div>
</div>
</div>
<div id="relativeStatus" class="status">Задайте базовый цвет и описание сдвига, затем смотрите совпадения ниже.</div>
</div>
<div id="nearestPane" class="stack hidden" style="margin-top: 14px;">
<div class="two">
<div class="field"><label for="nearestHex">Искомый HEX</label><input id="nearestHex" value="#C8B39B" autocomplete="off" spellcheck="false" type="text"></div>
<div class="field"><label for="nearestLab">Или Lab</label><input id="nearestLab" placeholder="74, 2, 18" autocomplete="off" spellcheck="false" type="text"></div>
</div>
<div class="help">Этот режим не сдвигает исходный цвет, а ищет ближайшие варианты в базе палитр.</div>
<div class="quick"><button id="btnRunNearest" class="btn primary" type="button">Найти ближайшие цвета</button></div>
<div id="nearestStatus" class="status">Вставьте HEX или Lab и запустите поиск.</div>
</div>
</div>
<div class="card">
<h3>Совпадения по палитрам</h3>
<div class="help">Показываются ближайшие цвета по загруженным интерьерным палитрам. Если локальные данные не найдены, включается демонстрационная база.</div>
<div id="paletteList" class="palette-list"></div>
</div>
</section>
<section class="grid" style="padding-top: 18px;">
<div class="card">
<h3>Топ цветов</h3>
<div class="help">Ближайшие отдельные цвета по текущей цели.</div>
<div id="colorResults" class="results"></div>
</div>
</section>
</div>
<p>
<script>
		"use strict";
		const DATA_ROOT = new URL("./data/", location.href);
		const ROOT_DATA_DIR = new URL("./data/data/", location.href);
		const DEMO_PALETTES = [{
				name: "Nordic Warm Neutrals",
				colors: [
					["Linen Chalk", "#E8DDCA"],
					["Oat Silk", "#DCC9AC"],
					["Warm Putty", "#C8B39B"],
					["Clay Mist", "#B89479"],
					["Terracotta Dusk", "#A45F3B"]
				]
			},
			{
				name: "Stone & Plaster",
				colors: [
					["Bone White", "#EFE7D8"],
					["Dune Beige", "#D5C2A7"],
					["Greige Drift", "#BDB2A6"],
					["Graphite Plaster", "#6D6A66"],
					["Charcoal Mortar", "#3A3938"]
				]
			},
			{
				name: "Sage & Sea",
				colors: [
					["Pale Sage", "#C9D1C0"],
					["Dusty Olive", "#8E9A7D"],
					["Deep Moss", "#3F5141"],
					["Salt Teal", "#8AB7B0"],
					["Deep Teal", "#1F6F74"]
				]
			},
			{
				name: "Dusty Blues",
				colors: [
					["Powder Blue", "#B5C7D6"],
					["Slate Blue", "#60748B"],
					["Night Indigo", "#1B2A3A"],
					["Blush Plaster", "#E1C6C1"],
					["Muted Rose", "#C79C96"]
				]
			}
		];
		const $ = id => document.getElementById(id),
			clamp = (v, a, b) => Math.min(b, Math.max(a, v)),
			round = (v, p = 1) => Math.round(v * 10 ** p) / 10 ** p,
			uniq = list => [...new Set(list.filter(Boolean))];

		function parseHex(str) {
			const m = /^#?([0-9a-fA-F]{6})$/.exec(String(str || "").trim());
			if (!m) return null;
			const hex = "#" + m[1].toLowerCase();
			return {
				hex,
				r: parseInt(hex.slice(1, 3), 16),
				g: parseInt(hex.slice(3, 5), 16),
				b: parseInt(hex.slice(5, 7), 16)
			}
		}

		function hexFromRgb({
			r,
			g,
			b
		}) {
			const to2 = n => clamp(Math.round(n), 0, 255).toString(16).padStart(2, "0");
			return "#" + to2(r) + to2(g) + to2(b)
		}

		function srgbToLinear(v) {
			const x = v / 255;
			return x <= 0.04045 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4)
		}

		function linearToSrgb01(x) {
			const v = x <= 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055;
			return clamp(v, 0, 1)
		}
		const REF_X = 0.95047,
			REF_Y = 1,
			REF_Z = 1.08883

		function rgbToXyz({
			r,
			g,
			b
		}) {
			const R = srgbToLinear(r),
				G = srgbToLinear(g),
				B = srgbToLinear(b);
			return {
				X: R * 0.4124564 + G * 0.3575761 + B * 0.1804375,
				Y: R * 0.2126729 + G * 0.7151522 + B * 0.072175,
				Z: R * 0.0193339 + G * 0.119192 + B * 0.9503041
			}
		}

		function xyzToRgb({
			X,
			Y,
			Z
		}) {
			const R = X * 3.2404542 + Y * -1.5371385 + Z * -0.4985314,
				G = X * -0.969266 + Y * 1.8760108 + Z * 0.041556,
				B = X * 0.0556434 + Y * -0.2040259 + Z * 1.0572252;
			return {
				r: Math.round(linearToSrgb01(R) * 255),
				g: Math.round(linearToSrgb01(G) * 255),
				b: Math.round(linearToSrgb01(B) * 255)
			}
		}

		function fLab(t) {
			return t > 0.008856451679 ? Math.cbrt(t) : (903.296296296 * t + 16) / 116
		}

		function finvLab(t) {
			const t3 = t * t * t;
			return t3 > 0.008856451679 ? t3 : (116 * t - 16) / 903.296296296
		}

		function xyzToLab({
			X,
			Y,
			Z
		}) {
			const x = fLab(X / REF_X),
				y = fLab(Y / REF_Y),
				z = fLab(Z / REF_Z);
			return {
				L: 116 * y - 16,
				a: 500 * (x - y),
				b: 200 * (y - z)
			}
		}

		function labToXyz({
			L,
			a,
			b
		}) {
			const y = (L + 16) / 116,
				x = y + a / 500,
				z = y - b / 200;
			return {
				X: REF_X * finvLab(x),
				Y: REF_Y * finvLab(y),
				Z: REF_Z * finvLab(z)
			}
		}

		function labToLch({
			L,
			a,
			b
		}) {
			const C = Math.sqrt(a * a + b * b);
			let h = Math.atan2(b, a) * 180 / Math.PI;
			if (h < 0) h += 360;
			return {
				L,
				C,
				h
			}
		}

		function lchToLab({
			L,
			C,
			h
		}) {
			const hr = h * Math.PI / 180;
			return {
				L,
				a: C * Math.cos(hr),
				b: C * Math.sin(hr)
			}
		}

		function safeParseLab(str) {
			const p = String(str || "").trim().split(/[\s,]+/).filter(Boolean).map(Number);
			if (p.length < 3 || !p.slice(0, 3).every(Number.isFinite)) return null;
			return {
				L: p[0],
				a: p[1],
				b: p[2]
			}
		}
		const labDistance = (a, b) => Math.sqrt((a.L - b.L) ** 2 + (a.a - b.a) ** 2 + (a.b - b.b) ** 2),
			formatLch = l => `L* ${round(l.L,1)}  C* ${round(l.C,1)}  h* ${round(l.h,1)}°`;

		function escapeHtml(s) {
			return String(s).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;")
		}

		function colorFromAny(v, fallback = "") {
			if (typeof v === "string") {
				const rgb = parseHex(v);
				if (!rgb) return null;
				const lab = xyzToLab(rgbToXyz(rgb));
				return {
					name: fallback || v,
					hex: rgb.hex,
					rgb,
					lab,
					lch: labToLch(lab)
				}
			}
			if (!v || typeof v !== "object") return null;
			const rgb = parseHex(v.hex || v.color || v.value || v.code || v.rgbHex);
			if (!rgb) return null;
			const lab = xyzToLab(rgbToXyz(rgb));
			return {
				name: v.name || v.title || fallback || rgb.hex,
				hex: rgb.hex,
				rgb,
				lab,
				lch: labToLch(lab)
			}
		}

		function normalizePalettePayload(payload, fallbackName) {
			if (Array.isArray(payload)) return {
				name: fallbackName || "Palette",
				colors: payload.map((item, i) => typeof item === "string" ? colorFromAny(item, `Color ${i+1}`) : colorFromAny(item, item?.name || `Color ${i+1}`)).filter(Boolean)
			};
			if (!payload || typeof payload !== "object") return {
				name: fallbackName || "Palette",
				colors: []
			};
			const raw = payload.colors || payload.items || payload.paints || payload.swatches || payload.palette || [];
			return {
				name: payload.name || payload.title || fallbackName || "Palette",
				brand: payload.brand || payload.collection || "",
				colors: Array.isArray(raw) ? raw.map((item, i) => colorFromAny(item, item?.name || `Color ${i+1}`)).filter(Boolean) : []
			}
		}
		async function fetchJson(url) {
			const res = await fetch(url, {
				cache: "no-store"
			});
			if (!res.ok) throw new Error(res.statusText);
			return await res.json()
		}

		function normalizePaletteList(raw) {
			if (Array.isArray(raw)) return raw;
			if (raw && Array.isArray(raw.palettes)) return raw.palettes;
			if (raw && Array.isArray(raw.items)) return raw.items;
			if (raw && Array.isArray(raw.data)) return raw.data;
			return []
		}

		function parsePaletteDescriptor(entry, i) {
			if (typeof entry === "string") return {
				name: entry.replace(/\.json$/i, ""),
				file: entry
			};
			if (!entry || typeof entry !== "object") return {
				name: `Palette ${i+1}`,
				file: ""
			};
			return {
				name: entry.name || entry.title || entry.slug || `Palette ${i+1}`,
				file: entry.file || entry.path || entry.slug || entry.name || "",
				raw: entry
			}
		}

		function normalizeEntryPath(raw) {
			return String(raw || "").trim().replace(/^\.?\//, "").replace(/^data%2F/i, "").replace(/^data\//i, "")
		}

		function candidateUrlsForEntry(entry) {
			const raw = typeof entry === "string" ? entry : entry?.file || entry?.path || entry?.slug || entry?.name;
			if (!raw) return [];
			const base = normalizeEntryPath(raw);
			const stem = base.endsWith(".json") ? base : `${base}.json`;
			return uniq([new URL(stem, DATA_ROOT).href, new URL(encodeURIComponent(stem), DATA_ROOT).href, new URL(base, DATA_ROOT).href, new URL(encodeURIComponent(base), DATA_ROOT).href])
		}

		function candidateListUrls() {
			return uniq([new URL("palettes-list.json", DATA_ROOT).href, new URL("palettes-list.json", ROOT_DATA_DIR).href, new URL("data/palettes-list.json", ROOT_DATA_DIR).href])
		}
		async function loadPalettes() {
			let rawList = null;
			for (const url of candidateListUrls()) {
				try {
					rawList = await fetchJson(url);
					break
				} catch (e) {}
			}
			if (!rawList) return [];
			const descriptors = normalizePaletteList(rawList).map(parsePaletteDescriptor);
			const palettes = [];
			for (const desc of descriptors) {
				if (desc.raw && (Array.isArray(desc.raw.colors) || Array.isArray(desc.raw.items) || Array.isArray(desc.raw.swatches))) {
					palettes.push(normalizePalettePayload(desc.raw, desc.name));
					continue
				}
				let loaded = null;
				for (const url of candidateUrlsForEntry(desc.file || desc.name)) {
					try {
						loaded = await fetchJson(url);
						break
					} catch (e) {}
				}
				if (loaded) palettes.push(normalizePalettePayload(loaded, desc.name))
			}
			return palettes.filter(p => p.colors && p.colors.length)
		}

		function flattenColors(palettes) {
			const items = [];
			for (const palette of palettes)
				for (const color of palette.colors) items.push({
					palette: palette.name,
					brand: palette.brand || "",
					...color
				});
			return items
		}

		function parseNudgeText(text) {
			const t = String(text || "").toLowerCase();
			let scale = 1;
			if (/(чуть|слегка|немного)/.test(t)) scale = .5;
			if (/(сильно|очень)/.test(t)) scale = 1.5;
			let dL = 0,
				dC = 0,
				dH = 0;
			if (/светлее/.test(t)) dL += 8 * scale;
			if (/темнее/.test(t)) dL -= 8 * scale;
			if (/теплее/.test(t)) dH += 10 * scale;
			if (/холоднее/.test(t)) dH -= 10 * scale;
			if (/(насыщеннее|ярче|сочнее)/.test(t)) dC += 8 * scale;
			if (/(приглушеннее|спокойнее|мягче|пыльнее)/.test(t)) dC -= 8 * scale;
			return {
				dL,
				dC,
				dH
			}
		}
		const state = {
			mode: "relative",
			dL: 0,
			dC: 0,
			dH: 0,
			palettes: [],
			colors: [],
			lastTarget: null
		}

		function getBaseLab() {
			const fromLab = safeParseLab($("baseLab").value);
			if (fromLab) return fromLab;
			const fromHex = parseHex($("baseHex").value);
			return fromHex ? xyzToLab(rgbToXyz(fromHex)) : null
		}

		function targetFromRelative() {
			const base = getBaseLab();
			if (!base) return null;
			const baseLch = labToLch(base);
			const targetLch = {
				L: clamp(baseLch.L + state.dL, 0, 100),
				C: Math.max(0, baseLch.C + state.dC),
				h: (baseLch.h + state.dH + 360) % 360
			};
			return {
				base,
				baseLch,
				targetLch,
				targetLab: lchToLab(targetLch)
			}
		}

		function targetFromNearest() {
			const lab = safeParseLab($("nearestLab").value);
			if (lab) return {
				targetLab: lab,
				source: "Lab"
			};
			const hex = parseHex($("nearestHex").value);
			if (hex) return {
				targetLab: xyzToLab(rgbToXyz(hex)),
				source: "HEX"
			};
			return null
		}

		function setMode(mode) {
			state.mode = mode;
			$("tabRelative").setAttribute("aria-selected", String(mode === "relative"));
			$("tabNearest").setAttribute("aria-selected", String(mode === "nearest"));
			$("relativePane").classList.toggle("hidden", mode !== "relative");
			$("nearestPane").classList.toggle("hidden", mode !== "nearest")
		}

		function nearestColor(color, target) {
			return {
				...color,
				score: labDistance(color.lab, target)
			}
		}

		function rankPalette(palette, target) {
			const ranked = palette.colors.map(c => ({
				...c,
				score: labDistance(c.lab, target)
			})).sort((a, b) => a.score - b.score);
			return {
				...palette,
				best: ranked[0] || null,
				ranked,
				score: ranked[0] ? ranked[0].score : Infinity
			}
		}

		function renderTopLists(targetLab) {
			const ranked = state.colors.map(c => nearestColor(c, targetLab)).sort((a, b) => a.score - b.score);
			const topColors = ranked.slice(0, 10);
			const topPalettes = state.palettes.map(p => rankPalette(p, targetLab)).sort((a, b) => a.score - b.score).slice(0, 4);
			const colorRoot = $("colorResults");
			colorRoot.innerHTML = "";
			if (!topColors.length) {
				colorRoot.innerHTML = '<div class="status">Нет цветов для показа. Проверьте загрузку данных или откройте страницу через сервер.</div>'
			} else
				for (const item of topColors) {
					const node = document.createElement("div");
					node.className = "result";
					node.innerHTML = `<div class="sw" style="background:${item.hex}"></div><div class="name"><b>${escapeHtml(item.name||item.hex)}</b><span>${escapeHtml(item.palette)}${item.brand?" · "+escapeHtml(item.brand):""} · ${item.hex.toUpperCase()}</span></div><div class="score">${round(item.score,1)}<small>${item.score<8?"почти точное совпадение":item.score<16?"очень близко":"близко"}</small></div>`;
					colorRoot.appendChild(node)
				}
			const paletteRoot = $("paletteList");
			paletteRoot.innerHTML = "";
			if (!topPalettes.length) {
				paletteRoot.innerHTML = '<div class="status">Палитры еще не загружены. Нажмите "Перезагрузить палитры" или проверьте файл <code>data/palettes-list.json</code>.</div>';
				return
			}
			for (const palette of topPalettes) {
				const box = document.createElement("div");
				box.className = "palette-card";
				box.innerHTML = `<div class="palette-top"><b>${escapeHtml(palette.name)}</b><span>${palette.best?round(palette.best.score,1)+" pts":"no data"}</span></div><div class="swatch-row"></div>`;
				const row = box.querySelector(".swatch-row");
				for (const color of palette.ranked.slice(0, 4)) {
					const tile = document.createElement("div");
					tile.className = "tiny";
					tile.innerHTML = `<div class="c" style="background:${color.hex}"></div><div class="t">${escapeHtml(color.name||color.hex)}<br><span style="color:rgba(29,26,22,.62)">${color.hex.toUpperCase()}</span></div>`;
					row.appendChild(tile)
				}
				paletteRoot.appendChild(box)
			}
		}

		function renderRelative() {
			const parsed = targetFromRelative();
			if (!parsed) {
				$("relativeStatus").textContent = "Не получилось распознать базовый цвет. Введите корректный HEX (#RRGGBB) или Lab (L, a, b).";
				$("baseOutHex").textContent = $("targetOutHex").textContent = "—";
				$("baseOutLch").textContent = $("targetOutLch").textContent = "—";
				$("baseBar").style.background = $("targetBar").style.background = "transparent";
				return
			}
			const baseHex = hexFromRgb(xyzToRgb(labToXyz(parsed.base))),
				targetHex = hexFromRgb(xyzToRgb(labToXyz(parsed.targetLab)));
			$("outDL").textContent = (state.dL >= 0 ? "+" : "") + state.dL;
			$("outDC").textContent = (state.dC >= 0 ? "+" : "") + state.dC;
			$("outDH").textContent = (state.dH >= 0 ? "+" : "") + state.dH + "°";
			$("baseOutHex").textContent = baseHex.toUpperCase();
			$("baseOutLch").textContent = formatLch(parsed.baseLch);
			$("targetOutHex").textContent = targetHex.toUpperCase();
			$("targetOutLch").textContent = formatLch(labToLch(parsed.targetLab));
			$("baseBar").style.background = baseHex;
			$("targetBar").style.background = targetHex;
			$("relativeStatus").textContent = "Цель построена в CIE LCh. Теперь сервис сравнивает ее с загруженными интерьерными цветами.";
			state.lastTarget = parsed.targetLab;
			renderTopLists(parsed.targetLab)
		}

		function renderNearest() {
			const parsed = targetFromNearest();
			if (!parsed) {
				$("nearestStatus").textContent = "Не получилось распознать HEX или Lab. Попробуйте формат #RRGGBB или L, a, b.";
				return
			}
			$("nearestStatus").textContent = `Ищем ближайшие цвета к ${parsed.source}. Результат обновлен по текущей базе палитр.`;
			state.lastTarget = parsed.targetLab;
			renderTopLists(parsed.targetLab)
		}

		function syncSlidersFromPrompt() {
			const d = parseNudgeText($("prompt").value);
			state.dL = clamp(d.dL, -25, 25);
			state.dC = clamp(d.dC, -35, 35);
			state.dH = clamp(d.dH, -45, 45);
			$("rL").value = String(state.dL);
			$("rC").value = String(state.dC);
			$("rH").value = String(state.dH);
			renderRelative()
		}

		function bind() {
			$("tabRelative").addEventListener("click", () => setMode("relative"));
			$("tabNearest").addEventListener("click", () => setMode("nearest"));
			$("btnSearchNearest").addEventListener("click", () => setMode("nearest"));
			$("btnRunNearest").addEventListener("click", renderNearest);
			$("btnReload").addEventListener("click", () => bootstrap());
			$("baseHex").addEventListener("input", renderRelative);
			$("baseLab").addEventListener("input", renderRelative);
			$("nearestHex").addEventListener("input", renderNearest);
			$("nearestLab").addEventListener("input", renderNearest);
			$("prompt").addEventListener("input", syncSlidersFromPrompt);
			$("rL").addEventListener("input", e => {
				state.dL = Number(e.target.value);
				renderRelative()
			});
			$("rC").addEventListener("input", e => {
				state.dC = Number(e.target.value);
				renderRelative()
			});
			$("rH").addEventListener("input", e => {
				state.dH = Number(e.target.value);
				renderRelative()
			});
			document.querySelectorAll("[data-nudge]").forEach(btn => btn.addEventListener("click", () => {
				const m = /^(L|C|H):([+-]?\d+)$/.exec(btn.getAttribute("data-nudge") || "");
				if (!m) return;
				const axis = m[1],
					delta = Number(m[2]);
				if (axis === "L") state.dL = clamp(state.dL + delta, -25, 25);
				if (axis === "C") state.dC = clamp(state.dC + delta, -35, 35);
				if (axis === "H") state.dH = clamp(state.dH + delta, -45, 45);
				$("rL").value = String(state.dL);
				$("rC").value = String(state.dC);
				$("rH").value = String(state.dH);
				renderRelative()
			}));
			$("btnReset").addEventListener("click", () => {
				state.dL = 0;
				state.dC = 0;
				state.dH = 0;
				$("prompt").value = "";
				$("rL").value = "0";
				$("rC").value = "0";
				$("rH").value = "0";
				renderRelative()
			})
		}
		async function bootstrap() {
			$("loadStatus").textContent = "Подключаюсь к /DB/colors.sqlite... Загружаю палитры  /DB/full-palitres.db3...";
			try {
				const palettes = await loadPalettes();
				state.palettes = palettes.filter(p => p.colors && p.colors.length);
				if (!state.palettes.length) throw new Error("no palettes");
				state.colors = flattenColors(state.palettes);
				$("loadStatus").textContent = `Загружено палитр: ${state.palettes.length}. Цветов в базе: ${state.colors.length}.`;
				renderRelative()
			} catch (e) {
				state.palettes = DEMO_PALETTES.map(p => normalizePalettePayload(p, p.name));
				state.colors = flattenColors(state.palettes);
				$("loadStatus").textContent = "Не удалось прочитать локальные data-файлы, поэтому включена встроенная демонстрационная база.";
				renderRelative()
			}
		}
		bind();
		bootstrap();
	</script>
</p>
            ]]>
        </content>
    </entry>
    <entry>
        <title>Карточка товара</title>
        <author>
            <name>пэйнт.рф</name>
        </author>
        <link href="https://пэйнт.рф/kartochka-tovara.html"/>
        <id>https://пэйнт.рф/kartochka-tovara.html</id>

        <updated>2026-03-03T17:38:34+03:00</updated>
            <summary type="html">
                <![CDATA[
                    Интерьерная краска Premium Моющаяся акриловая краска для стен и потолков. Высокая укрывистость, устойчивость к истиранию, подходит для жилых и коммерческих помещений. 1 × 60 000&hellip;
                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                <div class="paint-card"><!-- Картинка -->
<div class="paint-image"><img loading="lazy" src="/media/website/samplepaint.png" alt="Интерьерная краска Premium" sizes="(max-width: 1920px) 100vw, 1920px" srcset="/media/website/responsive/samplepaint-xs.png 640w ,/media/website/responsive/samplepaint-sm.png 768w ,/media/website/responsive/samplepaint-md.png 1024w ,/media/website/responsive/samplepaint-lg.png 1366w ,/media/website/responsive/samplepaint-xl.png 1600w ,/media/website/responsive/samplepaint-2xl.png 1920w"></div>
<!-- Информация -->
<div class="paint-info">
<h3 class="paint-title">Интерьерная краска Premium</h3>
<p class="paint-description">Моющаяся акриловая краска для стен и потолков. Высокая укрывистость, устойчивость к истиранию, подходит для жилых и коммерческих помещений.</p>
<!-- Выбранный цвет -->
<div class="color-selected"><span class="color-label">Выбранный цвет:</span> <span id="selectedColorName" class="color-name">Не выбран</span> <span id="selectedColorPreview" class="color-dot"></span></div>
<!-- Количество и кнопки -->
<div class="paint-controls">
<div class="qty-block"><span class="quantity-label">КОЛИЧЕСТВО:</span> <input type="number" id="paintQty" class="quantity-input" value="1" min="1"></div>
<div class="paint-buttons"><button id="selectColorBtn" class="btn-secondary">ВЫБРАТЬ ЦВЕТ</button> <button id="buyWithColorBtn" class="btn-primary">КУПИТЬ</button></div>
</div>
</div>
</div>
<!-- Попап выбора цвета -->
<div id="colorPopupOverlay" class="popup-overlay"></div>
<div id="colorPopup" class="popup popup-large"><button id="closeColorPopup" class="popup-close">×</button>
<h2 class="popup-title">Выберите цвет</h2>
<div class="color-search"><input type="text" id="colorSearch" placeholder="Поиск палитры..."></div>
<div id="colorGrid" class="color-grid"></div>
<button id="confirmColorBtn" class="popup-button" disabled="disabled">Подтвердить выбор</button></div>
<!-- Попап покупки -->
<div id="buyPopupOverlay" class="popup-overlay"></div>
<div id="buyPopup" class="popup"><button id="closeBuyPopup" class="popup-close">×</button>
<h2 class="popup-title">Оформление заказа</h2>
<p id="buyPopupPrice" class="popup-subtitle">1 × 60 000 ₽</p>
<div class="selected-color-info"><span class="color-label">Выбранный цвет:</span> <span id="buySelectedColorName" class="color-name">Не выбран</span> <span id="buySelectedColorPreview" class="color-dot"></span></div>
<form id="buyForm"><input type="text" id="buyName" placeholder="Ваше имя" required=""> <input type="tel" id="buyPhone" placeholder="+7 (999) 123-45-67" required=""> <input type="hidden" id="buySelectedColorId"> <input type="hidden" id="buySelectedColorNameHidden"> <input type="hidden" id="buyQuantity"> <button class="popup-button" type="submit">Отправить заказ</button>
<p class="popup-agreement">Нажимая кнопку, вы соглашаетесь с обработкой персональных данных</p>
</form>
<div id="buySuccessMessage" class="popup-success">
<div class="success-icon">✓</div>
<h3>Заказ отправлен!</h3>
<p>Мы свяжемся с вами</p>
</div>
</div>
<p>
<script src="/js/tovar.js"></script>
</p>
            ]]>
        </content>
    </entry>
    <entry>
        <title>Карточка цвета со слайдером</title>
        <author>
            <name>пэйнт.рф</name>
        </author>
        <link href="https://пэйнт.рф/color-details.html"/>
        <id>https://пэйнт.рф/color-details.html</id>

        <updated>2026-03-02T17:38:47+03:00</updated>
            <summary type="html">
                <![CDATA[
                    Гостинная 1 Описание первого слайда Описание второго слайда Описание третьего слайда Описание четвертого слайда ← Назад к палитре
                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                <div id="colorCard" class="color-card">
<div class="color-preview-container">
<div id="colorPreview" class="color-preview">
<div class="slider-container">
<div class="slider">
<div class="slide"><img loading="lazy" src="https://kraski-matus.ru/media/color-slider/slide1.png" alt="Гостинная" data-is-external-image="true">
<div class="slide-content">
<h3>Гостинная 1</h3>
<p>Описание первого слайда</p>
</div>
</div>
<div class="slide"><img loading="lazy" src="https://kraski-matus.ru/media/color-slider/slide2.png" alt="Кухня" data-is-external-image="true">
<div class="slide-content">
<h3>Кухня</h3>
<p>Описание второго слайда</p>
</div>
</div>
<div class="slide"><img loading="lazy" src="https://kraski-matus.ru/media/color-slider/slide3.png" alt="Гостинная 2" data-is-external-image="true">
<div class="slide-content">
<h3>Гостинная 2</h3>
<p>Описание третьего слайда</p>
</div>
</div>
<div class="slide"><img loading="lazy" src="https://kraski-matus.ru/media/color-slider/slide4.png" alt="Ванная комната" data-is-external-image="true">
<div class="slide-content">
<h3>Ванная комната</h3>
<p>Описание четвертого слайда</p>
</div>
</div>
</div>
<div class="navigation">
<div class="nav-dot"> </div>
<div class="nav-dot"> </div>
<div class="nav-dot"> </div>
<div class="nav-dot"> </div>
</div>
</div>
<div class="color-header">
<div class="color-info">
<h1 id="colorName" class="color-name"><span id="colorNameText"></span></h1>
<h2><span id="paletteInfo" class="palette-info"></span></h2>
<div class="color-id">ID: <span id="colorId"></span></div>
<div id="colorSeries"></div>
<div class="color-meta">
<div class="meta-row">
<div class="meta-label">HEX:</div>
<div id="colorHex" class="meta-value"></div>
</div>
<div class="meta-row">
<div class="meta-label">RGB:</div>
<div id="colorRgb" class="meta-value"></div>
</div>
<div class="meta-row">
<div class="meta-label">LRV:</div>
<div id="colorLrv" class="meta-value"></div>
</div>
<div class="meta-row">
<div class="meta-label">LAB:</div>
<div id="colorLab" class="meta-value"></div>
</div>
<div class="meta-row">
<div class="meta-label">LCH:</div>
<div id="colorLch" class="meta-value"></div>
</div>
<div class="meta-row">
<div class="meta-label">Цветовая семья:</div>
<div id="colorFamily" class="meta-value"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="similar-colors">
<h3>Похожие цвета</h3>
<div id="similarColorsList" class="similar-colors-list"></div>
</div>
</div>
<p><a href="javascript:history.back()" class="back-link">← Назад к палитре</a></p>
<p> </p>
            ]]>
        </content>
    </entry>
    <entry>
        <title>Палитра цветов</title>
        <author>
            <name>пэйнт.рф</name>
        </author>
        <link href="https://пэйнт.рф/palette.html"/>
        <id>https://пэйнт.рф/palette.html</id>

        <updated>2026-03-02T17:34:02+03:00</updated>
            <summary type="html">
                <![CDATA[
                    ЦВЕТОВЫЕ ПАЛИТРЫ ДЛЯ ВАШЕГО БИЗНЕСА Палитра цветов Загрузка... (function(m,e,t,r,i,k,a){ m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)}; m[i].l=1*new Date(); for (var j = 0; j
                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                <h1>ЦВЕТОВЫЕ ПАЛИТРЫ<br>ДЛЯ ВАШЕГО БИЗНЕСА</h1>
<div class="palette-container">
<h1 id="paletteTitle">Палитра цветов</h1>
<div id="colorCards" class="color-cards" style="display: grid; grid-template-columns: repeat(auto-fill,minmax(120px,1fr)); gap: 1rem; font-size: 0.7rem; text-align: center;">
<p>Загрузка...</p>
</div>
</div>
<p>
<script>

// Обновление мета-тегов
function updateMetaTags(paletteName) {
    document.title = `Палитра цветов ${paletteName} | Matus Paint`;

    let metaDescription = document.querySelector('meta[name="description"]');
    if (!metaDescription) {
        metaDescription = document.createElement('meta');
        metaDescription.name = "description";
        document.head.appendChild(metaDescription);
    }

    metaDescription.content =
        `Каталог цветов ${paletteName} с HEX-кодами и описаниями. Полная профессиональная палитра.`;
}

// Формирование URL цвета
function getColorDetailsUrl(color) {
    return `color-details.html?collection=${encodeURIComponent(color.collection)}&id=${encodeURIComponent(color.id)}`;
}

const urlParams = new URLSearchParams(window.location.search);
const collectionName = urlParams.get('collection');

if (!collectionName) {
    document.getElementById('colorCards').innerHTML =
        `<p style="color:red;">Ошибка: не указана палитра.</p>`;
} else {

    const formattedPaletteName = collectionName
        .replace(/-/g, ' ')
        .replace(/\b\w/g, l => l.toUpperCase());

    document.getElementById('paletteTitle').textContent =
        `Палитра цветов — ${formattedPaletteName}`;

    updateMetaTags(formattedPaletteName);

    fetch(`/data/${collectionName}.json`)
        .then(response => {
            if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
            return response.json();
        })
        .then(colors => {

            if (!colors || !colors.length)
                throw new Error('Палитра пуста или данные некорректны.');

            const container = document.getElementById('colorCards');
            container.innerHTML = '';

            colors.forEach(color => {

                const card = document.createElement('a');
                card.href = getColorDetailsUrl(color);
                card.className = 'color-card';
                card.style.textDecoration = 'none';
                card.style.color = 'inherit';

                card.innerHTML = `
                    <div style="
                        background:${color.hex};
                        height:60px;
                        margin-bottom:0.5rem;
                        border:1px solid #eee;">
                    </div>
                    <div>
                        <div style="font-weight:bold;">${color.name}</div>
                        ${color.series ? `<div style="color:#666;">${color.series}</div>` : ''}
                    </div>
                `;

                container.appendChild(card);
            });

        })
        .catch(error => {
            document.getElementById('colorCards').innerHTML =
                `<p style="color:red;">
                    Ошибка загрузки: ${error.message}<br>
                    <a href="/data/${collectionName}.json" target="_blank">
                        Проверить JSON
                    </a>
                </p>`;
        });
}
</script>
<script>
        lucide.createIcons();

        function toggleMenu() {
            document.getElementById('navLinks').classList.toggle('active');
        }

        // Анимация счетчиков
        const counters = document.querySelectorAll('.counter');
        const speed = 200;

        const startCounters = () => {
            counters.forEach(counter => {
                const updateCount = () => {
                    const target = +counter.getAttribute('data-target');
                    const count = +counter.innerText;
                    const inc = target / speed;

                    if (count < target) {
                        counter.innerText = Math.ceil(count + inc);
                        setTimeout(updateCount, 15);
                    } else {
                        counter.innerText = target + (target === 170 ? '+' : '+');
                    }
                };
                updateCount();
            });
        };

        // Запуск анимации при загрузке
        window.addEventListener('DOMContentLoaded', startCounters);
    </script>
<script>
    // Универсальная функция переключения
    function toggleMenu() {
        const nav = document.getElementById('navLinks');
        const burger = document.getElementById('burgerBtn');
        
        nav.classList.toggle('active');
        burger.classList.toggle('active');
        
        // Блокируем скролл основной страницы при открытом меню
        if (nav.classList.contains('active')) {
            document.body.style.overflow = 'hidden';
        } else {
            document.body.style.overflow = 'initial';
        }
    }

    // Функция закрытия (для кликов по ссылкам)
    function closeMenu() {
        const nav = document.getElementById('navLinks');
        const burger = document.getElementById('burgerBtn');
        
        nav.classList.remove('active');
        burger.classList.remove('active');
        document.body.style.overflow = 'initial';
    }

    // Закрытие при клике вне меню (на область контента)
    window.addEventListener('click', (e) => {
        const nav = document.getElementById('navLinks');
        const burger = document.getElementById('burgerBtn');
        if (nav.classList.contains('active') && !nav.contains(e.target) && !burger.contains(e.target)) {
            closeMenu();
        }
    });
</script>
</p>
<!-- Yandex.Metrika counter -->
<p>
<script type="text/javascript">
    (function(m,e,t,r,i,k,a){
        m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
        m[i].l=1*new Date();
        for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
        k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)
    })(window, document,'script','https://mc.yandex.ru/metrika/tag.js?id=107033938', 'ym');

    ym(107033938, 'init', {ssr:true, webvisor:true, clickmap:true, ecommerce:"dataLayer", referrer: document.referrer, url: location.href, accurateTrackBounce:true, trackLinks:true});
</script>
</p>
<noscript><div><img loading="lazy" src="https://mc.yandex.ru/watch/107033938" style="position:absolute; left:-9999px;" alt=""  data-is-external-image="true"></div></noscript><!-- /Yandex.Metrika counter -->
            ]]>
        </content>
    </entry>
</feed>
